# llimphi-widget-scroll *Read this in English: [README.md](README.md).* Área de scroll vertical reutilizable. Hasta ahora cada app rearmaba el scroll a mano: `App::on_wheel` + un offset en el `Model` + `clip` + virtualización. Este widget empaqueta ese patrón en un solo builder, **sin estado propio** (el offset sigue viviendo en el `Model`, fiel al bucle Elm): - **viewport clipeado** de alto fijo (`viewport_len`), - **contenido desplazado** `-offset` px (overflow recortado), - **barra de scroll arrastrable** a la derecha (sólo si el contenido excede el viewport), - **rueda autocontenida** vía `View::on_scroll`: girar la rueda con el cursor sobre el área emite un `Msg` sin que la app rutee nada por su `on_wheel` global. El caller debe conocer el **alto total del contenido** (`content_len`) y el **alto visible** (`viewport_len`) — igual que `list`/`grid` ya piden la ventana visible. Para contenido de filas uniformes es `n_filas * alto_fila`. ## Convención del callback `on_scroll` `on_scroll` recibe el **delta en px** a sumar al offset (no el offset absoluto): tanto la rueda como el arrastre de la barra emiten deltas, y el caller acumula + clampea en su `update` con `clamp_offset`. Es la misma idea que el `splitter` (el handler de drag se reusa durante todo el arrastre, así que un offset absoluto capturado se quedaría viejo; el delta-por-evento siempre es correcto). ```ignore // view: scroll_y( model.offset, model.rows.len() as f32 * ROW_H, panel_h, lista_view, Msg::ScrollBy, // Fn(f32) -> Msg, arg = delta px &ScrollPalette::default(), ) // update: Msg::ScrollBy(d) => { m.offset = clamp_offset(m.offset + d, content_len, viewport_len); } ``` Para llevar una selección a la vista (teclado), ver `ensure_visible`; para scroll suave/inercia, ver `approach`. --- Parte de **llimphi** — ver [llimphi](../../LEEME.md).