CanvasEvents
ui.eventsEventos a nivel de lienzo: scroll, gesto de zoom, arrastre y un viaje de ida y vuelta de PNG por el portapapeles.
BEHAVIOR CONTRACT
Los payloads de eventos en bruto llegan al nodo sin un componente de por medio.
RECORDED INTERACTION
El harness hace scroll en el lienzo; el registro de eventos se actualiza en vivo.
pub fn buildCanvasEvents(scope: *ui.Scope, cx: *ui.Cx) anyerror!*ui.Node {
const a = cx.allocator;
const c = try col(cx, 12);
try c.appendChild(a, try label(cx, "Canvas events — scroll modifiers / magnify / drag / clipboard image"));
const canvas = try ui.box(cx, .{
.width = .{ .px = 420 },
.height = .{ .px = 160 },
.background = light.color.bg_secondary,
}, .{});
canvas.meta.ownership.meta.test_id = "story.canvasevents.canvas";
const scroll_label = try ui.text(cx, "scroll (none)", .{ .font_size = 13, .color = light.color.fg_primary });
scroll_label.meta.ownership.meta.test_id = "story.canvasevents.scroll";
const magnify_label = try ui.text(cx, "magnify (none)", .{ .font_size = 13, .color = light.color.fg_primary });
magnify_label.meta.ownership.meta.test_id = "story.canvasevents.magnify";
const drag_label = try ui.text(cx, "drag (none)", .{ .font_size = 13, .color = light.color.fg_primary });
drag_label.meta.ownership.meta.test_id = "story.canvasevents.drag";
const clip_label = try ui.text(cx, "clip (none)", .{ .font_size = 13, .color = light.color.fg_primary });
clip_label.meta.ownership.meta.test_id = "story.canvasevents.clip";
const st = try cx.bindState(CanvasEventsStory, .{
.cx = cx,
.scroll_label = scroll_label,
.magnify_label = magnify_label,
.drag_label = drag_label,
.clip_label = clip_label,
});
canvas.behavior.events.on_scroll = CanvasEventsStory.onScroll;
canvas.behavior.events.on_event = CanvasEventsStory.onEvent;
canvas.behavior.events.event_context = st;
try c.appendChild(a, canvas);
try c.appendChild(a, scroll_label);
try c.appendChild(a, magnify_label);
try c.appendChild(a, drag_label);
try c.appendChild(a, clip_label);
const clip_btn = try W.Button(.{ .label = "Clipboard PNG roundtrip", .on_click = cx.on(CanvasEventsStory, st, CanvasEventsStory.clipboardRoundtrip) }).mount(scope, cx);
clip_btn.meta.ownership.meta.test_id = "story.canvasevents.clipbtn";
try c.appendChild(a, clip_btn);
return c;
}El código literal de la story en el Storybook (zenit 5f9add5+wip 2026-09-30). W es ui.widgets; col / row / label son pequeños helpers de layout del Storybook.