Event
Event specification
What Event Is
Section titled “What Event Is”Event is Proto UI’s User → Component information channel. It carries user operations, input, and interaction intent into a component so the component can respond during runtime callbacks.
Without the Event channel, a component cannot know when interaction has happened, and it cannot react to User interaction.
Setup Registration And Runtime Dispatch
Section titled “Setup Registration And Runtime Dispatch”Event follows the same setup / runtime split used across Proto UI, but Event itself has no prototype-author runtime API. You can only plan interaction subscriptions during setup; the callbacks created by those subscriptions run later during runtime.
Prototype authors declare which interactions they care about during setup:
def.event.on('press.commit', (run, ev) => { run.update();});
def.event.onGlobal('key.down', (run, ev) => { if (ev.key === 'Escape') run.update();});def.event.on, def.event.onGlobal, def.event.off, and token.desc are setup-only. Registration APIs do not call callbacks immediately. off(token) removes the exact registration represented by that token; it is not a runtime unsubscribe API.
When an interaction event is dispatched during runtime, the registered callback receives:
(run, ev) => voidrun is bound to the current callback invocation. The Event module itself does not construct or validate that handle; binding the correct handle is runtime responsibility. The payload ev carries portable event information where the event type promises it.
Active event triggering, runtime dynamic subscription, or runtime unsubscribe would be future escape hatches, not part of the v0 Event channel.
Binding Targets
Section titled “Binding Targets”Event registrations bind to adapter-provided interaction targets:
def.event.on('press.commit', onCommit); // root-scopeddef.event.onGlobal('key.down', onKeyDown); // global-scopeddef.event.on(...) binds by default to the current component instance’s root interaction target. def.event.onGlobal(...) binds to an adapter-defined global interaction target. Prototype authors do not receive those concrete targets and do not choose whether the host uses DOM nodes, window, framework roots, native views, or another mechanism.
Bindings are demand-driven. If a component has no event registrations, the event binding step is a no-op and must not require root or global targets. A root target is required only when root-scoped registrations exist; a global target is required only when global-scoped registrations exist.
If every setup-time registration is removed before runtime, the component still has no event registrations and must not pay runtime event binding cost. This lets setup logic temporarily enable and then close an event plan without leaving a runtime listener behind.
The root-scoped target defaults to the component Root Node and cannot be changed during runtime. During setup, privileged infrastructure may redirect it to another interaction target. The current public use case is asTrigger, which lets a trigger-like substructure forward event ownership to a parent trigger target; this redirection is not exposed as a general prototype-author API.
Event Type Layers
Section titled “Event Type Layers”EventTypeV0 is a semantic model, not a list of DOM event names.
type EventTypeV0 = CoreEventType | OptionalEventType | `host:${string}`;The layers are:
| Layer | Examples | Portability |
|---|---|---|
| protocol core | press.commit, key.down, key.up | strongest portable semantics |
| optional medium | pointer.down, context.menu, input | semantic if the adapter supports the medium |
| host-bound escape hatch | host:click, host:pointerdown | lifecycle guarantees only; semantics are host-defined |
Core events express portable interaction intent. press.commit means a valid activation has been confirmed; it is not a synonym for DOM click. key.down and key.up describe an input channel press and release; they are not limited to physical keyboard events.
Optional medium events sit closer to interaction media such as pointer, focus, text input, or context menus. Adapters may choose not to support every optional event, but if they claim support, they must preserve the semantics of that event family.
host:* is the explicit escape hatch. It keeps Event guarantees such as setup registration, runtime callback dispatch, lifecycle cleanup, and target binding, but it gives up cross-host event semantics. Payload shape, naming, trigger conditions, and concrete host mapping are adapter-defined.
Payload Shape
Section titled “Payload Shape”Portable Event payloads must expose at least the portable event type that produced the dispatch:
def.event.on('press.commit', (run, ev) => { ev.type; // 'press.commit'});For key.*, the payload also exposes a portable key string:
def.event.onGlobal('key.down', (run, ev) => { if (ev.key === 'Enter') { run.update(); }});Adapters may expose host-local escape hatches such as native event objects, concrete targets, preventDefault, or stopPropagation, but those fields are not portable Event guarantees. Code that depends on them is depending on a host profile, not the cross-host Event contract.
Lifecycle And Cleanup
Section titled “Lifecycle And Cleanup”Event bindings are lifecycle-managed. When a component instance unmounts or is disposed, bindings must detach so leftover host events cannot invoke old callbacks.
If an adapter remounts or replaces the concrete host object backing an already-planned target, bindings must follow that replacement transparently. Prototype authors should not need to hold target references or update subscriptions manually.
Duplicate registrations are not deduplicated. If a prototype registers the same callback for the same event type twice, there are two independent registrations and two callback deliveries. Removal is token-based, so each registration can be removed precisely.
Contract Previews
Section titled “Contract Previews”Test Mapping
Section titled “Test Mapping”Event coverage is mapped through the event test entities:
| Test entity | Main coverage |
|---|---|
T-EVENT-0001 | setup registration, runtime dispatch, target binding, no-op binding, no deduplication, token removal, cleanup/rebind |
T-EVENT-0002 | EventTypeV0 validation, portable payload shape, host:*, runtime API boundary |
These tests turn Event from “whatever the host fires” into a verifiable input channel with explicit portability layers and lifecycle behavior.
Related Specs
Section titled “Related Specs”Coredefines information channels, setup/runtime, and run handle foundations; Event is the User → Component channel.Lifecycledefines runtime callback availability, cleanup, and the no-implicit-render boundary; Event callbacks must respect it.Feedbackcarries Component → User output caused by events; Event itself does not express what the user perceives.State,Props, andContextmay be read or changed inside event callbacks through their runtime APIs, but they remain separate information channels.