♻️ (base): El resumen de Pager usa PagerVisibility

Sustituye el bool de `summary` por `PagerVisibility`, con `Auto` como
valor por defecto: se activa igual que los botones de navegación y el
formulario de salto, sólo cuando el listado se trunca.
This commit is contained in:
Manuel Cillero 2026-08-07 23:47:14 +02:00
parent ee57d6cacf
commit 825283caa3
2 changed files with 91 additions and 30 deletions

View file

@ -31,8 +31,7 @@ pub enum PagerAlign {
/// `Pager` permite navegar por las páginas de un listado de ítems cuando supera el número máximo de /// `Pager` permite navegar por las páginas de un listado de ítems cuando supera el número máximo de
/// ítems admitidos por página. Resuelve el enlace para acceder a cada página del listado a partir /// ítems admitidos por página. Resuelve el enlace para acceder a cada página del listado a partir
/// de una ruta base, un conjunto de parámetros de consulta adicionales (orden, búsqueda, etc.) y el /// de una ruta base, un conjunto de parámetros de consulta adicionales (orden, búsqueda, etc.) y el
/// estado actual ([`current_page`](Self::current_page), [`items_per_page`](Self::items_per_page), /// estado actual ([`current_page`], [`items_per_page`], [`total_items`]).
/// [`total_items`](Self::total_items)).
/// ///
/// El componente se renderiza sólo si el listado requiere más de una página. /// El componente se renderiza sólo si el listado requiere más de una página.
/// ///
@ -41,33 +40,31 @@ pub enum PagerAlign {
/// ///
/// El listado de páginas se flanquea con dos botones de navegación: página anterior y página /// El listado de páginas se flanquea con dos botones de navegación: página anterior y página
/// siguiente (las páginas primera y última ya están siempre disponibles como números, así que no /// siguiente (las páginas primera y última ya están siempre disponibles como números, así que no
/// llevan un botón dedicado). Su visibilidad, junto a la del formulario de salto a página, se /// llevan un botón dedicado). Su visibilidad, junto a la del formulario de salto a página y la del
/// controla con [`PagerVisibility`] a través de [`with_prev_next()`](Self::with_prev_next) y /// resumen de páginas, se controla con [`PagerVisibility`] a través de [`with_prev_next()`],
/// [`with_jump()`](Self::with_jump): `Never` los oculta siempre, `Always` los muestra siempre (en /// [`with_jump()`] y [`with_summary()`]: `Never` los oculta siempre, `Always` los muestra siempre,
/// el caso de los botones, con el extremo correspondiente desactivado en vez de oculto), y `Auto` /// y el valor por defecto `Auto` los muestra sólo cuando el paginador necesita truncar el listado
/// -el valor por defecto de ambos- los muestra sólo cuando el número total de páginas supera al /// de páginas por superar el número máximo de páginas visible.
/// número de páginas que se muestra en el paginador; en ese caso, si la página actual coincide con
/// un extremo, el botón correspondiente se muestra igualmente, pero desactivado.
/// ///
/// Un elemento `<nav>` envuelve todo el paginador. Lleva un `aria-label` por defecto que puede /// Un elemento `<nav>` envuelve todo el paginador. Lleva un `aria-label` por defecto que puede
/// sustituirse por otro más específico usando [`with_aria_label()`](Self::with_aria_label), por /// sustituirse por otro más específico usando [`with_aria_label()`], por ejemplo cuando una misma
/// ejemplo cuando una misma página tiene varios paginadores. /// página tiene varios paginadores.
/// ///
/// La alineación horizontal del paginador dentro de este contenedor se controla con [`PagerAlign`] /// La alineación horizontal del paginador dentro de este contenedor se controla con [`PagerAlign`]
/// a través de [`with_align()`](Self::with_align). Por defecto es [`PagerAlign::Center`]. /// a través de [`with_align()`]. Por defecto es [`PagerAlign::Center`].
/// ///
/// # Acotando el número de ítems del paginador /// # Acotando el número de ítems del paginador
/// ///
/// Con listados largos, mostrar un número por cada página real puede desbordar la interfaz. Con /// Con listados largos, mostrar un número por cada página real puede desbordar la interfaz. Con
/// [`with_window()`](Self::with_window) se puede limitar el número de páginas que se muestran a /// [`with_window()`] se puede limitar el número de páginas que se muestran a cada lado de la página
/// cada lado de la página actual. Por defecto vale `2`, que limita la vista a `9` celdas en total /// actual. Por defecto vale `2`, que limita la vista a `9` celdas en total (sin contar los botones
/// (sin contar los botones de navegación anterior/siguiente). En general, el número máximo de /// de navegación anterior/siguiente). En general, el número máximo de celdas mostradas para un
/// celdas mostradas para un `window` dado es `2 * window + 5`. /// `window` dado es `2 * window + 5`.
/// ///
/// Si el valor de la ventana es mayor que `0`, `Pager` siempre muestra la primera y la última /// Incluso cuando se trunca el paginador, `Pager` siempre muestra la primera y la última página
/// página como números, más la ventana indicada antes y después de la página actual, sustituyendo /// como números, más la ventana indicada antes y después de la página actual, sustituyendo por una
/// por una elipsis (`…`) cualquier tramo oculto de dos o más páginas. Si el tramo oculto es de una /// elipsis (`…`) cualquier tramo oculto de dos o más páginas. Si el tramo oculto es de una sola
/// sola página, se muestra directamente en vez de la elipsis, porque ocultarla no ahorra espacio. /// página, se muestra directamente en vez de la elipsis, porque ocultarla no ahorra espacio.
/// ///
/// Por ejemplo, con `with_window(3)`, página actual `34` y con `200` páginas en total, el paginador /// Por ejemplo, con `with_window(3)`, página actual `34` y con `200` páginas en total, el paginador
/// se mostraría así: /// se mostraría así:
@ -83,8 +80,8 @@ pub enum PagerAlign {
/// para saltar directamente a una página escribiendo su número, sin depender de JavaScript. Un /// para saltar directamente a una página escribiendo su número, sin depender de JavaScript. Un
/// único campo numérico (`min`/`max` según el total de páginas) y un botón de envío. /// único campo numérico (`min`/`max` según el total de páginas) y un botón de envío.
/// ///
/// Con [`with_summary()`](Self::with_summary) se puede añadir un texto que resume la vista de las /// Con [`with_summary()`] se puede añadir un texto que resume la vista de las páginas que se
/// páginas que se muestran en ese momento. Por defecto está oculto. /// muestran en ese momento (ver más arriba su visibilidad según [`PagerVisibility`]).
/// ///
/// # Clases CSS /// # Clases CSS
/// ///
@ -122,6 +119,16 @@ pub enum PagerAlign {
/// .with_items_per_page(20) /// .with_items_per_page(20)
/// .with_total_items(97); /// .with_total_items(97);
/// ``` /// ```
///
/// [`current_page`]: Self::current_page
/// [`items_per_page`]: Self::items_per_page
/// [`total_items`]: Self::total_items
/// [`with_window()`]: Self::with_window
/// [`with_align()`]: Self::with_align
/// [`with_summary()`]: Self::with_summary
/// [`with_prev_next()`]: Self::with_prev_next
/// [`with_jump()`]: Self::with_jump
/// [`with_aria_label()`]: Self::with_aria_label
#[derive(AutoDefault, Clone, Debug, Getters)] #[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Pager { pub struct Pager {
/// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente. /// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente.
@ -146,8 +153,8 @@ pub struct Pager {
window: u64, window: u64,
/// Devuelve la alineación horizontal del paginador dentro de su contenedor. /// Devuelve la alineación horizontal del paginador dentro de su contenedor.
align: PagerAlign, align: PagerAlign,
/// Devuelve si se muestra el resumen ("Showing 1-20 of 97") antes del listado de páginas. /// Devuelve la visibilidad del resumen de las páginas que se muestran en cada vista.
summary: bool, summary: PagerVisibility,
/// Devuelve la visibilidad de los botones de página anterior/siguiente. /// Devuelve la visibilidad de los botones de página anterior/siguiente.
prev_next: PagerVisibility, prev_next: PagerVisibility,
/// Devuelve la visibilidad del formulario para saltar directamente a una página. /// Devuelve la visibilidad del formulario para saltar directamente a una página.
@ -218,10 +225,15 @@ impl Component for Pager {
PagerVisibility::Always => true, PagerVisibility::Always => true,
PagerVisibility::Auto => truncated, PagerVisibility::Auto => truncated,
}; };
let show_summary = match self.summary() {
PagerVisibility::Never => false,
PagerVisibility::Always => true,
PagerVisibility::Auto => truncated,
};
Ok(html! { Ok(html! {
nav (self.props()) aria-label=(self.aria_label().using(cx)) { nav (self.props()) aria-label=(self.aria_label().using(cx)) {
@if *self.summary() { @if show_summary {
@let items_per_page = self.items_per_page().max(1); @let items_per_page = self.items_per_page().max(1);
@let first = (page - 1) * items_per_page + 1; @let first = (page - 1) * items_per_page + 1;
@let last = (page * items_per_page).min(self.total_items()); @let last = (page * items_per_page).min(self.total_items());
@ -393,9 +405,15 @@ impl Pager {
self self
} }
/// Establece si se muestra el resumen de las páginas que se muestran en ese momento. /// Establece la visibilidad del resumen de las páginas que se muestran en cada vista. Por
/// defecto es `PagerVisibility::Auto`: sólo se muestra cuando el número total de páginas supera
/// al número de páginas que se muestra en el paginador, igual que [`with_prev_next()`] y
/// [`with_jump()`].
///
/// [`with_prev_next()`]: Self::with_prev_next
/// [`with_jump()`]: Self::with_jump
#[builder_fn] #[builder_fn]
pub fn with_summary(mut self, summary: bool) -> Self { pub fn with_summary(mut self, summary: PagerVisibility) -> Self {
self.summary = summary; self.summary = summary;
self self
} }

View file

@ -41,7 +41,9 @@ async fn with_aria_label_overrides_the_default() {
} }
#[pagetop::test] #[pagetop::test]
async fn summary_is_hidden_by_default() { async fn summary_is_hidden_by_default_when_not_truncated() {
// 97 items at 20 per page is only 5 pages: with the default window (2) that fits without
// truncation, so `PagerVisibility::Auto` keeps the summary hidden.
let mut pager = Pager::new() let mut pager = Pager::new()
.with_base_path("/list") .with_base_path("/list")
.with_current_page(1) .with_current_page(1)
@ -53,6 +55,19 @@ async fn summary_is_hidden_by_default() {
assert!(!html.contains("pager-summary")); assert!(!html.contains("pager-summary"));
} }
#[pagetop::test]
async fn summary_is_shown_by_default_when_truncated() {
let mut pager = Pager::new()
.with_base_path("/list")
.with_current_page(10)
.with_items_per_page(1)
.with_total_items(20);
let html = pager.render(&mut Context::default()).await.into_string();
assert!(html.contains("pager-summary"));
}
#[pagetop::test] #[pagetop::test]
async fn summary_shows_the_range_of_the_current_page() { async fn summary_shows_the_range_of_the_current_page() {
let mut first_page = Pager::new() let mut first_page = Pager::new()
@ -60,7 +75,7 @@ async fn summary_shows_the_range_of_the_current_page() {
.with_current_page(1) .with_current_page(1)
.with_items_per_page(20) .with_items_per_page(20)
.with_total_items(97) .with_total_items(97)
.with_summary(true); .with_summary(PagerVisibility::Always);
let html = first_page let html = first_page
.render(&mut Context::default()) .render(&mut Context::default())
.await .await
@ -73,7 +88,7 @@ async fn summary_shows_the_range_of_the_current_page() {
.with_current_page(5) .with_current_page(5)
.with_items_per_page(20) .with_items_per_page(20)
.with_total_items(97) .with_total_items(97)
.with_summary(true); .with_summary(PagerVisibility::Always);
let html = last_page let html = last_page
.render(&mut Context::default()) .render(&mut Context::default())
.await .await
@ -81,6 +96,34 @@ async fn summary_shows_the_range_of_the_current_page() {
assert!(html.contains(r#"<span class="pager-summary">Showing 81-97 of 97</span>"#)); assert!(html.contains(r#"<span class="pager-summary">Showing 81-97 of 97</span>"#));
} }
#[pagetop::test]
async fn summary_can_be_forced_to_always_show_even_when_not_truncated() {
let mut pager = Pager::new()
.with_base_path("/list")
.with_current_page(1)
.with_items_per_page(20)
.with_total_items(97)
.with_summary(PagerVisibility::Always);
let html = pager.render(&mut Context::default()).await.into_string();
assert!(html.contains("pager-summary"));
}
#[pagetop::test]
async fn summary_never_shows_it_even_when_truncated() {
let mut pager = Pager::new()
.with_base_path("/list")
.with_current_page(10)
.with_items_per_page(1)
.with_total_items(20)
.with_summary(PagerVisibility::Never);
let html = pager.render(&mut Context::default()).await.into_string();
assert!(!html.contains("pager-summary"));
}
#[pagetop::test] #[pagetop::test]
async fn renders_page_links_and_current_page() { async fn renders_page_links_and_current_page() {
let mut pager = Pager::new() let mut pager = Pager::new()