Events

Declaring events

Events declare typed callback slots that the parent binds at instantiation:

tape
component Button {
    prop label: *u8;
    event on_click();
    event on_hover(x: i64, y: i64);
}

Events can have up to 8 parameters.

Firing events

Use the fire keyword inside a component method to invoke the bound handler:

tape
component Button {
    prop label: *u8;
    event on_click();

    fn handle_pointer(ev: *ui.PointerEvent) {
        if (ev.kind == ui.PointerKind.Up) {
            fire on_click();
        }
    }
}

If no handler is bound, fire is a no-op (null-guarded function pointer call).

Binding handlers (parent side)

The parent binds a function to the event by name:

tape
fn save_file() {
    io.println("saving...");
}

component Toolbar {
    view {
        widgets.Button { label: "Save"; on_click: save_file; }
    }
}

The handler must match the event’s parameter types exactly.

Passing data through events

Declare parameters in the event signature to carry data to the parent:

tape
component TodoItem {
    prop index: i64;
    event on_delete(index: i64);

    fn handle_click() {
        fire on_delete(index);
    }
}

Parent side:

tape
fn delete_item(index: i64) { /* ... */ }
TodoItem { index: 3; on_delete: delete_item; }

Auto-setters as event handlers

Generated set_<var> methods are commonly bound to change events:

tape
component SearchBar {
    var query: *u8 = "";

    view {
        widgets.TextInput { value: query; on_change: set_query; }
    }
}

Design: no closures

Events are single function pointers — not closures, not listener lists. This means:

  • No hidden heap allocations
  • No capture analysis
  • No multi-subscriber patterns (use explicit fan-out if needed)
  • Predictable, zero-overhead dispatch (null check + indirect call)

Last modified: