(base): Añade componente Pager

Incluye soporte HTMX vía `sort_link()`/`hx_pager` para navegación sin
recarga.
This commit is contained in:
Manuel Cillero 2026-08-06 22:37:28 +02:00
parent e8bba0803d
commit 3b01894024
14 changed files with 881 additions and 31 deletions

View file

@ -1,4 +1,17 @@
//! Soporte HTMX al componente [`Table`].
//!
//! [`Table`] no conoce HTMX ni ninguna otra librería de interactividad. En su lugar, cada pieza que
//! necesita comportamiento interactivo ([`table::Row`], [`table::Cell`], [`table::SortLink`])
//! expone su propio [`Props`] que admite cualquier atributo HTML por nombre y valor mediante
//! [`PropsOp::set()`], el mismo mecanismo que usa el resto de PageTop para adoptar HTMX sin
//! depender de él. Basta con encadenar `.with_prop(PropsOp::set(hx::GET, ...))` sobre el elemento
//! que corresponda.
//!
//! Las cabeceras ordenables ([`table::Column::with_sort`]) ya llevan resuelta la parte que no
//! depende de HTMX: el enlace (`<a href>`), la semántica accesible (`aria-sort`) y el indicador
//! visual de dirección (clases CSS). Sólo falta (si se quiere navegación sin recarga) añadir los
//! atributos `hx-*` con [`table::SortLink::with_prop()`], o usar directamente [`sort_link()`] para
//! no repetirlos en cada columna.
use pagetop::prelude::*;
@ -6,15 +19,13 @@ use crate::hx;
// **< sort_link() >********************************************************************************
/// Construye un [`SortLink`](pagetop::base::component::table::SortLink) para actualizar el orden de
/// la tabla sin recargar la página.
/// Construye un [`SortLink`] para actualizar el orden de la tabla sin recargar la página.
///
/// [`Table`] y `SortLink` no requieren HTMX. Cada extensión que quiera aplicar una navegación sin
/// recarga debe añadir sus propios atributos `hx-*` usando
/// [`SortLink::with_prop()`](pagetop::base::component::table::SortLink::with_prop). Como esos
/// recarga debe añadir sus propios atributos `hx-*` usando [`SortLink::with_prop()`]. Como esos
/// cuatro atributos son siempre los mismos para cualquier cabecera ordenable (`hx-get` igual al
/// `href`, `hx-swap="outerHTML"` y `hx-push-url="true"`, y sólo `hx-target` cambia según la tabla),
/// [`sort_link()`] evita reescribirlos en cada columna de cada listado.
/// `href`, `hx-swap="outerHTML scroll:top"` y `hx-push-url="true"`, y sólo `hx-target` cambia según
/// la tabla), [`sort_link()`] evita reescribirlos en cada columna de cada listado.
///
/// El enlace resultante funciona igual con o sin HTMX: `href` es siempre la URL real del nuevo
/// estado de orden, así que navega correctamente aunque HTMX no esté disponible en el cliente.
@ -23,13 +34,12 @@ use crate::hx;
///
/// - `href`: URL completa hacia el nuevo estado de orden, reflejando ya el campo y la dirección
/// que resultarán de pulsar esta cabecera. Acepta cualquier tipo convertible a [`RoutePath`],
/// normalmente el resultado de [`Context::route()`](pagetop::core::component::Context::route),
/// para que el enlace preserve el parámetro `lang` cuando corresponda.
/// normalmente el resultado de [`Context::route()`], para que el enlace preserve el parámetro
/// `lang` cuando corresponda.
/// - `target`: selector CSS del elemento que HTMX debe reemplazar (`hx-target`), típicamente el
/// contenedor que envuelve la tabla completa.
/// - `dir`: dirección de orden vigente de esta columna, o `None` si la tabla está ordenada
/// actualmente por otra columna. Se traslada tal cual a
/// [`SortLink::with_dir()`](pagetop::base::component::table::SortLink::with_dir).
/// actualmente por otra columna. Se traslada tal cual a [`SortLink::with_dir()`].
///
/// # Ejemplo
///
@ -56,6 +66,11 @@ use crate::hx;
/// .with_sort(hx_table::sort_link(href, "#user-table-wrapper", active))
/// # }
/// ```
///
/// [`SortLink`]: pagetop::base::component::table::SortLink
/// [`SortLink::with_prop()`]: pagetop::base::component::table::SortLink::with_prop
/// [`SortLink::with_dir()`]: pagetop::base::component::table::SortLink::with_dir
/// [`Context::route()`]: pagetop::core::component::Context::route
pub fn sort_link(
href: impl Into<RoutePath>,
target: impl AsRef<str>,
@ -69,6 +84,6 @@ pub fn sort_link(
.with_dir(dir)
.with_prop(PropsOp::set(hx::GET, href))
.with_prop(PropsOp::set(hx::TARGET, target))
.with_prop(PropsOp::set(hx::SWAP, hx::swap::OUTER_HTML))
.with_prop(PropsOp::set(hx::SWAP, hx::swap::OUTER_HTML_SCROLL_TOP))
.with_prop(PropsOp::set(hx::PUSH_URL, "true"))
}