Event
Event 规范
Event 是什么?
Section titled “Event 是什么?”Event 是 Proto UI 的 User → Component 信息通路。它把用户操作、输入与交互意图带入组件,让组件能在 runtime callback 中响应用户行为。
没有 Event 通路,组件无法得知何时发生了交互,也无法对 User 的交互产生反应。
Setup Registration 与 Runtime Dispatch
Section titled “Setup Registration 与 Runtime Dispatch”Event 遵守 Proto UI 通用的 setup / runtime 分离,但 Event 本身没有原型作者侧 runtime API。你只能在 setup 期计划如何订阅交互事件,而这些订阅回调只会在 runtime 期间执行。
原型作者在 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 与 token.desc 都是 setup-only。registration API 不会在注册时立即执行 callback。off(token) 只移除该 token 代表的 registration;它不是 runtime unsubscribe API。
当 interaction event 在 runtime 期间被 dispatch 时,已注册 callback 接收:
(run, ev) => voidrun 绑定到当前 callback invocation。Event module 自身不构造也不校验这个 handle;绑定正确 handle 是 runtime 的职责。payload ev 则在 event type 承诺的范围内携带 portable event 信息。
主动触发 event、runtime 动态订阅或 runtime 取消订阅如果未来提供,也会是独立 escape hatch,不属于 v0 Event channel。
Binding Targets
Section titled “Binding Targets”Event registration 绑定到 adapter 提供的 interaction target:
def.event.on('press.commit', onCommit); // root-scopeddef.event.onGlobal('key.down', onKeyDown); // global-scopeddef.event.on(...) 默认绑定到当前组件实例的 root interaction target。def.event.onGlobal(...) 绑定到 adapter-defined global interaction target。原型作者不会拿到这些具体 target,也不决定 host 使用 DOM node、window、framework root、native view 还是其他机制。
binding 是按需的。若组件没有任何 event registration,event binding step 就是 no-op,不得要求 root 或 global target 可用。只有存在 root-scoped registration 时才要求 root target;只有存在 global-scoped registration 时才要求 global target。
如果 setup 期间先注册某个事件、随后又通过 token 将其关闭,最终进入 runtime 时仍然等价于没有任何 event registration,也不应产生运行时 event binding 开销。这让 setup logic 可以临时建立并撤销 event plan,而不会留下 runtime listener。
root-scoped target 默认指向组件 Root Node,且进入 runtime 后不可变更。setup 期间,特权基础设施可以把它转移到另一个 interaction target。当前公开使用场景是 asTrigger:它允许类似 trigger 的子结构把 event ownership 转移给父级 trigger target;这个转移能力并不作为通用原型作者 API 暴露。
Event Type 分层
Section titled “Event Type 分层”EventTypeV0 是语义模型,不是 DOM event name 列表。
type EventTypeV0 = CoreEventType | OptionalEventType | `host:${string}`;分层如下:
| Layer | Examples | Portability |
|---|---|---|
| protocol core | press.commit, key.down, key.up | 最强 portable semantics |
| optional medium | pointer.down, context.menu, input | adapter 支持该 medium 时具备语义 |
| host-bound escape hatch | host:click, host:pointerdown | 只保留 lifecycle 保证;语义由 host 定义 |
Core event 表达 portable interaction intent。press.commit 表示一次有效 activation 已确认完成;它不是 DOM click 的同义词。key.down 与 key.up 描述 input channel 的按下与释放,也不被限定为物理键盘事件。
Optional medium event 更接近 pointer、focus、text input 或 context menu 这样的交互媒介。adapter 可以不支持所有 optional event;但一旦宣称支持,就必须保持对应 event family 的语义。
host:* 是明确的 escape hatch。它保留 setup registration、runtime callback dispatch、lifecycle cleanup 与 target binding 等 Event 保证,但放弃跨 host event semantics。payload shape、命名、触发条件与具体 host mapping 都由 adapter 定义。
Payload Shape
Section titled “Payload Shape”portable Event payload 至少必须暴露产生该 dispatch 的 portable event type:
def.event.on('press.commit', (run, ev) => { ev.type; // 'press.commit'});对 key.* 而言,payload 还必须暴露 portable key string:
def.event.onGlobal('key.down', (run, ev) => { if (ev.key === 'Enter') { run.update(); }});adapter 可以暴露 native event object、具体 target、preventDefault 或 stopPropagation 这类 host-local escape hatch,但这些字段不是 portable Event guarantee。依赖它们的代码是在依赖某个 host profile,而不是跨 host Event 契约。
Lifecycle 与 Cleanup
Section titled “Lifecycle 与 Cleanup”Event binding 由 lifecycle 管理。当组件实例 unmount 或 dispose 时,binding 必须 detach,确保残留 host event 不能继续调用旧 callback。
如果 adapter 重新挂载或替换了某个已计划 target 背后的具体 host object,binding 必须透明跟随。原型作者不应该需要持有 target 引用,也不需要手动更新 subscription。
重复 registration 不会被去重。如果原型对同一个 event type 注册同一个 callback 两次,就存在两条独立 registration,也会有两次 callback delivery。移除是 token-based,因此每条 registration 都能被精确移除。
契约实体预览
Section titled “契约实体预览”与测试的关系
Section titled “与测试的关系”Event 覆盖映射到以下测试实体:
| 测试实体 | 主要覆盖 |
|---|---|
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 |
这些测试把 Event 从“host 发了什么就是什么”收敛为一个具备明确 portability layer 与 lifecycle 行为的可验证输入通路。
与其他规范的关系
Section titled “与其他规范的关系”Core定义信息通路、setup/runtime 与 run handle 基础;Event 是其中 User → Component 的具体通路。Lifecycle定义 runtime callback 可用性、cleanup 与 no-implicit-render 边界;Event callback 必须遵守它。Feedback承载 event 之后组件向 User 产生的输出;Event 本身不表达用户感知到什么。State、Props、Context可以在 event callback 中通过各自 runtime API 被读取或修改,但它们仍是独立信息通路。