Skip to main content

Event Loop API

Kraken TUI provides an animation-aware async event loop via createLoop() that automatically adjusts polling behavior based on whether animations are active.

createLoop()

Create an animation-aware async event loop.
options
LoopOptions
required
Configuration for the event loop
Loop
object
Loop controller with start() and stop() methods

Basic Usage

import { Kraken, createLoop, KeyCode } from "kraken-tui";

const app = Kraken.init();
const root = new Box({ width: "100%", height: "100%" });
app.setRoot(root);

const loop = createLoop({
  app,
  onEvent: (event) => {
    if (event.type === "key" && event.keyCode === KeyCode.Escape) {
      loop.stop();
    }
  },
});

await loop.start(); // Blocks until loop.stop() is called
app.shutdown();

Animation-Aware Behavior

The loop automatically adapts its polling strategy:
  • When animations are active (~60fps mode):
    • Non-blocking input poll (readInput(0))
    • Sleep for frame duration (~16ms)
    • Ensures smooth animation updates
  • When idle (CPU-saving mode):
    • Blocking input poll (readInput(idleTimeout))
    • No unnecessary rendering
    • Saves CPU when nothing is happening
The loop checks app.getPerfCounter(PERF_ACTIVE_ANIMATIONS) to detect active animations automatically.

LoopOptions

Configuration interface for createLoop().
app
Kraken
required
The Kraken application instance
onEvent
(event: KrakenEvent) => void
Called for each event during drain. Fires before JSX handler dispatch.Use this for:
  • Global keyboard shortcuts
  • Analytics/logging
  • Custom event routing
onTick
() => void
Called each tick after events are drained, before render.Use this for:
  • Game logic updates
  • State computations
  • Custom animations
fps
number
default:"60"
FPS target when animating. Frame duration is calculated as 1000 / fps milliseconds.
idleTimeout
number
default:"100"
Input poll timeout (ms) when idle. Higher values save more CPU but increase input latency when idle.
disableJsxDispatch
boolean
default:"false"
Disable automatic dispatch to JSX event handler props. Set to true if using only imperative event handling.
mode
'onChange' | 'continuous'
default:"'onChange'"
Loop mode:
  • "onChange" (default): Auto-detects animations and switches between ~60fps and idle modes
  • "continuous": Forces fixed-fps rendering regardless of animation state

Advanced Example

const loop = createLoop({
  app,
  fps: 120, // High refresh rate
  idleTimeout: 50, // Faster idle response
  mode: "continuous", // Always render at fixed FPS
  onEvent: (event) => {
    // Log all events
    console.log(event.type, event.target);
  },
  onTick: () => {
    // Update game state every frame
    gameState.update();
  },
});

dispatchToJsxHandlers()

Dispatch an event to JSX event handler props registered on the target widget.
event
KrakenEvent
required
The event to dispatch
This function is exported for users running custom event loops outside of createLoop().

Usage with Custom Loop

import { dispatchToJsxHandlers } from "kraken-tui";

// Custom synchronous loop
let running = true;
while (running) {
  app.readInput(16);
  
  for (const event of app.drainEvents()) {
    // Dispatch to JSX handlers
    dispatchToJsxHandlers(event);
    
    // Your custom handling
    if (event.type === "key" && event.keyCode === KeyCode.Escape) {
      running = false;
    }
  }
  
  app.render();
}
The createLoop() function calls dispatchToJsxHandlers() automatically unless disableJsxDispatch: true is set.

Event Handler Mapping

Event types are mapped to JSX prop names:
Event TypeJSX Prop Name
keyonKey
mouseonMouse
focusonFocus
changeonChange
submitonSubmit
accessibilityonAccessibility

Performance Counters

PERF_ACTIVE_ANIMATIONS

Performance counter ID for active animation count.
import { PERF_ACTIVE_ANIMATIONS } from "kraken-tui";

const activeAnimations = app.getPerfCounter(PERF_ACTIVE_ANIMATIONS);
console.log(`Active animations: ${activeAnimations}`);
Used internally by createLoop() to detect when to switch between animation and idle modes.
You can use this counter to implement custom animation detection logic in your own event loops.

Loop Patterns

Pattern 1: Simple Event Loop

For basic applications with no special requirements:
const loop = createLoop({ app });
await loop.start();

Pattern 2: Global Shortcuts

Handle keyboard shortcuts before JSX handlers:
const loop = createLoop({
  app,
  onEvent: (event) => {
    // Ctrl+Q to quit
    if (event.type === "key" && 
        event.keyCode === 0x51 && // 'Q'
        event.modifiers & Modifier.Ctrl) {
      loop.stop();
    }
  },
});

Pattern 3: Game Loop

For games or simulations with per-frame updates:
const loop = createLoop({
  app,
  fps: 60,
  mode: "continuous",
  onTick: () => {
    // Update physics, AI, etc.
    gameWorld.update(1/60);
  },
});

Pattern 4: Custom Event Routing

Route events to specific handlers based on application state:
let currentMode: "normal" | "modal" = "normal";

const loop = createLoop({
  app,
  disableJsxDispatch: true, // Handle routing manually
  onEvent: (event) => {
    if (currentMode === "modal") {
      handleModalEvent(event);
    } else {
      dispatchToJsxHandlers(event); // Default JSX routing
    }
  },
});

Comparison with Kraken.run()

createLoop() is an alternative to Kraken.run():
FeatureKraken.run()createLoop()
Return valueNo return (blocking)Loop object with stop()
Animation detectionAutomaticAutomatic
JSX dispatchAutomaticAutomatic (configurable)
Custom event handlingVia onEvent callbackVia onEvent callback
Async/await supportNoYes
Stop mechanismReturn false from onEventCall loop.stop()
Use Kraken.run() for simple applications. Use createLoop() when you need explicit control over loop lifecycle or async patterns.

Source Reference

See ts/src/loop.ts for implementation details.

Kraken.run()

Alternative event loop API

Event Handling

Event handling patterns and examples

Animation

Animation system concepts

JSX Reconciler

JSX event handler registration

Build docs developers (and LLMs) love