tea-web
| Kind | kit |
|---|---|
| Categories | — |
| Keywords | — |
Browser runtime for Kit TEA applications
This package enables Kit TEA applications to run in web browsers using HTML5 Canvas for rendering and DOM events for input.
Files
| File | Description |
|---|---|
.editorconfig | Editor formatting configuration |
.gitignore | Git ignore rules for build artifacts and dependencies |
.tool-versions | asdf tool versions (Zig, Kit) |
examples/kit-counter/counter.kit | Example: Kit TEA counter |
examples/README.md | Examples documentation |
kit.toml | Package manifest with metadata and dependencies |
package.json | npm package manifest |
README.md | This file |
src/diff.js | Element diffing for optimized rendering |
src/events.d.ts | TypeScript declarations for event capture |
src/events.js | DOM event capture and Kit TEA Msg conversion |
src/hmr.js | Hot Module Replacement for development |
src/html-widgets.js | Native HTML form elements overlay |
src/index.d.ts | TypeScript declarations for main module |
src/index.js | Main module and public API |
src/loader.d.ts | TypeScript declarations for WASM loader |
src/loader.js | WASM interpreter loader |
src/main.kit | Main module |
src/renderer.d.ts | TypeScript declarations for canvas renderer |
src/renderer.js | Canvas 2D renderer for Kit TEA elements |
src/runtime.js | Main loop, event processing, and rendering runtime |
src/webgl-renderer.js | WebGL renderer for high-performance scenes |
Features
- Canvas 2D Renderer - Renders all Kit TEA Element types to Canvas
- Event Capture - Mouse and keyboard events mapped to Kit Msg format
- requestAnimationFrame Loop - Smooth 60fps rendering with proper timing
- WASM Integration - Works with Kit's WASM interpreter (optional)
- Pure JavaScript Mode - Can run TEA apps without WASM for prototyping
Installation
npm install kit-tea-webOr include directly:
<script type="module">
import { runTeaApp } from './kit-tea-web/src/index.js';
</script>Quick Start
Pure JavaScript TEA App
import { runTeaApp } from 'kit-tea-web';
const canvas = document.getElementById('app');
runTeaApp({
canvas,
// Initialize model
init: () => ({ count: 0 }),
// Update model based on messages
update: (msg, model) => {
if (msg.kind === 'key-pressed' && msg.key === 265) { // Up arrow
return { count: model.count + 1 };
}
return model;
},
// Render view from model
view: (model) => ({
type: 'Group',
elements: [
{ type: 'Rect-Fill', x: 0, y: 0, w: 400, h: 300, color: 0x1a1a2eff },
{ type: 'Text', x: 180, y: 130, size: 48, color: 0xffd700ff, content: String(model.count) }
]
})
});With Kit WASM
Run Kit source code directly in the browser using the WASM interpreter:
import { loadKitWasm, createTeaApp } from 'kit-tea-web';
import { renderElement } from 'kit-tea-web/renderer';
// Load Kit source (use \$ to escape string interpolation in JS template literals)
const kitSource = `
init = fn() => {count: 0}
update = fn(-msg, model) => {count: model.count + 1}
view = fn(model) => {
kind: "Text",
x: 100, y: 100,
size: 48, color: 0xffffffff,
content: "\${model.count}"
}
`;
// Load WASM and create TEA app
const wasm = await loadKitWasm('./kit-interpreter.wasm');
wasm.init();
const app = createTeaApp(wasm, kitSource);
// Render initial view
const canvas = document.getElementById('app');
const ctx = canvas.getContext('2d');
renderElement(ctx, app.getView());
// Handle events
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const view = app.mouseClicked(e.clientX - rect.left, e.clientY - rect.top);
ctx.clearRect(0, 0, canvas.width, canvas.height);
renderElement(ctx, view);
});Note: Kit uses kind instead of type for element types (since type is a reserved keyword). The renderer accepts both.
API Reference
Runtime Functions
runTeaApp(options)
Run a TEA application with JavaScript model management.
runTeaApp({
canvas: HTMLCanvasElement,
init: () => Model,
update: (msg: Msg, model: Model) => Model,
view: (model: Model) => Element,
config?: RuntimeConfig
})createRuntime(canvas, config)
Create a low-level runtime for custom control.
const runtime = createRuntime(canvas, {
targetFps: 60,
backgroundColor: 0x1a1a2eff,
autoClear: true,
captureKeyboard: true,
captureMouse: true,
debug: false
});
runtime.start(initialView, updateFn);
runtime.stop();Renderer
renderElement(ctx, element)
Render an Element tree to a Canvas 2D context.
const ctx = canvas.getContext('2d');
renderElement(ctx, {
type: 'Rect-Fill',
x: 10, y: 10, w: 100, h: 50,
color: 0xff0000ff
});intToColor(color)
Convert Kit RGBA integer to CSS color string.
intToColor(0xff0000ff) // => 'rgba(255,0,0,1)'Events
setupEventCapture(canvas, onEvent, options)
Set up DOM event capture for a canvas element.
const cleanup = setupEventCapture(canvas, (msg) => {
console.log(msg.kind, msg.x, msg.y);
}, {
captureKeyboard: true,
captureMouse: true,
preventDefaults: true
});
// Later: cleanup();Event Constructors
import {
frameTick, // (dt) => Msg
keyPressed, // (keyCode) => Msg
keyDown, // (keyCode) => Msg
keyReleased, // (keyCode) => Msg
mouseClicked, // (x, y, button) => Msg
mouseMoved, // (x, y) => Msg
mouseWheel // (delta) => Msg
} from 'kit-tea-web';Element Types
All Kit TEA Element types are supported:
Shapes
Rect-Fill- Filled rectangleRect-Outline- Rectangle outlineRect-Rounded- Rounded rectangleCircle-Fill- Filled circleCircle-Outline- Circle outlineLine- Line segmentTriangle- Filled trianglePolygon- Filled polygon
Text
Text- Basic textText-Styled- Styled text with font options
Gradients
Rect-Gradient-H- Horizontal gradientRect-Gradient-V- Vertical gradientRect-Gradient-D- Diagonal gradientRect-Gradient-Radial- Radial gradient
Layout
Group- Group of elementsRow- Horizontal layoutColumn- Vertical layoutContainer- Positioned containerScrollable- Scrollable regionEmpty- Empty elementSpacer- Spacing element
Widgets
Button- Clickable buttonSlider- Value sliderProgress-Bar- Progress indicatorCheckbox- Checkbox inputRadio- Radio buttonToggler- Toggle switchText-Input- Text input fieldPick-List- Dropdown selectText-Editor- Multi-line text editor
Key Codes
Key codes match Raylib conventions. Common codes:
| Key | Code |
|---|---|
| Up Arrow | 265 |
| Down Arrow | 264 |
| Left Arrow | 263 |
| Right Arrow | 262 |
| Space | 32 |
| Enter | 257 |
| Escape | 256 |
| A-Z | 65-90 |
| 0-9 | 48-57 |
Color Format
Colors are 32-bit integers in RGBA format: 0xRRGGBBAA
const red = 0xff0000ff; // Full red, full opacity
const semiBlue = 0x0000ff80; // Blue at 50% opacity
const white = 0xffffffff;Touch & Gamepad Support
Touch events are automatically mapped to mouse events for seamless mobile support:
touchstart->mouse-downtouchmove->mouse-movedtouchend->mouse-released+mouse-clicked
Gamepad support (opt-in):
runTeaApp({
canvas,
config: {
captureGamepad: true,
gamepadDeadzone: 0.15,
},
// ...
});Gamepad events:
gamepad-button-pressed- button index inmsg.buttongamepad-button-released- button index inmsg.buttongamepad-axis- axis index inmsg.button, value (-1 to 1) inmsg.dt
WebGL Renderer
For maximum performance with many shapes, use the WebGL renderer:
import { createHybridRenderer } from 'kit-tea-web';
const canvas = document.getElementById('app');
const renderer = createHybridRenderer(canvas);
function frame() {
const view = buildView();
renderer.render(view, 0x1a1a2eff); // backgroundColor
// Get performance stats
const stats = renderer.getStats();
console.log(`Triangles: ${stats.triangleCount}, Draw calls: ${stats.drawCalls}`);
requestAnimationFrame(frame);
}Renderers Available
| Renderer | Use Case | Text Support |
|---|---|---|
renderElement (Canvas 2D) | Simple UIs, full feature support | Native |
createWebGLRenderer | Maximum shapes, no text | None |
createHybridRenderer | Many shapes with text | Canvas 2D fallback |
Performance Tips
- WebGL batches similar shapes into single draw calls
- Use
createHybridRendererwhen you need both shapes and text - For 1000+ animated shapes, WebGL can be 5-10x faster
Optimized Rendering
For complex UIs, enable element diffing to minimize redraws:
runTeaApp({
canvas,
config: {
optimizedRendering: true, // Enable diffing
showDirtyRects: true, // Debug: show redrawn regions in red
},
// ...
});How it works:
- Compares previous and current element trees
- Calculates "dirty rectangles" for changed elements
- Only redraws regions that changed
- Merges overlapping dirty rects to reduce draw calls
Best for:
- UIs with many static elements
- Small, frequently-changing elements (cursors, animations)
- Complex scenes where full redraws are expensive
Access optimization stats:
const stats = app.runtime.getStats();
console.log(stats.diffing.efficiency); // "85.5%"
console.log(stats.diffing.partialRedraws); // 342
console.log(stats.diffing.fullRedraws); // 12Hot Module Replacement (HMR)
For development, use HMR to reload code while preserving model state:
import { createHmrApp, createHmrIndicator } from 'kit-tea-web';
const hmr = createHmrApp({
preserveState: true, // Keep model across reloads
verbose: true, // Log HMR events
onReload: (count) => console.log(`Reload #${count}`),
});
// Initial load
hmr.load({ init, update, view }, runtimeConfig, canvas);
// Hot reload with new view function
hmr.load({ init, update, view: newViewFn }, runtimeConfig, canvas);
// State is automatically preserved!
console.log(hmr.getModel()); // { count: 5, ... }
// Add visual indicator
createHmrIndicator(hmr, { position: 'bottom-right' });Automatic File Watching
For simple dev setups without a bundler:
import { setupDevMode } from 'kit-tea-web';
const dev = setupDevMode(canvas, './app.js', {
watchInterval: 1000,
runtimeConfig: { debug: true },
onReload: () => console.log('Reloaded!'),
});
// Later: dev.stop()HMR API
| Method | Description |
|---|---|
hmr.load(app, config, canvas) | Load/reload app |
hmr.updateView(viewFn) | Hot-swap just the view function |
hmr.getModel() | Get current model state |
hmr.saveState() | Manually save state |
hmr.clearState() | Clear saved state |
hmr.getStats() | Get reload statistics |
Debug Overlay
Enable with config.debug: true:
runTeaApp({
canvas,
config: {
debug: true,
debugOptions: {
showFps: true, // FPS counter
showFrameTime: true, // Frame time in ms
showEventCount: true, // Events per frame
showMemory: true, // JS heap usage (Chrome only)
position: 'top-left', // 'top-left', 'top-right', 'bottom-left', 'bottom-right'
},
},
// ...
});HTML Widgets
For accessible form inputs, use native HTML elements overlaid on the canvas:
import { createWidgetManager } from 'kit-tea-web';
const widgets = createWidgetManager(canvas, {
container: document.getElementById('canvas-container'), // Parent element
zIndex: 10,
});
// Create widgets
widgets.createTextInput('name', {
placeholder: 'Enter name',
onChange: (value) => updateModel({ name: value }),
});
widgets.createSelect('color', {
options: [
{ value: 'red', label: 'Red' },
{ value: 'blue', label: 'Blue' },
],
onChange: (value) => updateModel({ color: value }),
});
widgets.createRange('size', {
min: 10, max: 100, step: 5,
onChange: (value) => updateModel({ size: parseInt(value) }),
});
widgets.createCheckbox('filled', {
label: 'Filled',
onChange: (checked) => updateModel({ filled: checked }),
});
widgets.createButton('submit', {
label: 'Submit',
onClick: () => handleSubmit(),
});
// Position widgets (call after canvas layout)
widgets.updatePosition('name', 100, 50, 200, 30);
widgets.updatePosition('color', 100, 100, 150, 30);
// Get/set values
widgets.setValue('name', 'Default');
const name = widgets.getValue('name');
// Show/hide
widgets.hide('name');
widgets.show('name');
// Cleanup
widgets.dispose();Available Widget Types
| Method | Description |
|---|---|
createTextInput(id, opts) | Single-line text input |
createTextarea(id, opts) | Multi-line text area |
createSelect(id, opts) | Dropdown select (PickList) |
createCheckbox(id, opts) | Checkbox with label |
createRange(id, opts) | Slider/range input |
createButton(id, opts) | Clickable button |
createProgress(id, opts) | Progress bar indicator |
createToggler(id, opts) | Toggle switch (on/off) |
createRadio(id, opts) | Radio button group |
Benefits
- Accessibility: Native keyboard navigation, screen reader support
- Mobile: Native touch handling, auto-zoom on focus
- Familiar: Standard form behavior users expect
- Styling: Can be styled with CSS
Examples
See the examples/ directory:
counter/- Basic counter with buttonswidgets/- Interactive widget showcaseanimation/- Physics and frame timingpong/- Classic Pong gametouch/- Touch drawing and gamepad supportoptimized/- Compare standard vs optimized renderinghmr/- Hot Module Replacement demowebgl/- WebGL vs Canvas 2D performance comparisonhtml-widgets/- Native HTML form elements on canvas
To run examples locally:
cd examples/counter
python3 -m http.server 8000
# Open http://localhost:8000Browser Support
- Chrome 80+
- Firefox 75+
- Safari 13.1+
- Edge 80+
Requires ES modules support.
License
MIT