♻️ Refactoriza la API de Children e InRegion
- Patrón prototipo en `InRegion`: cada petición recibe clones profundos. - `ComponentClone` habilita clonar `dyn Component` de forma segura. - `ChildTyped<C>` renombrado a `Slot<C>`, elimina `ChildTypedOp`. - `Mutex` en lugar de `Arc<RwLock>` en `Child` y `Slot`. - `is_renderable` y `setup_before_prepare` reciben `&Context`. - Nuevos tests para `Children`, `ChildOp` y `Slot`.
This commit is contained in:
parent
f1d3218a68
commit
2626234163
33 changed files with 740 additions and 314 deletions
|
|
@ -11,12 +11,12 @@ use crate::prelude::*;
|
|||
/// - Si no hay imagen ([`with_image()`](Self::with_image)) ni título
|
||||
/// ([`with_title()`](Self::with_title)), la marca de identidad no se renderiza.
|
||||
/// - El eslogan ([`with_slogan()`](Self::with_slogan)) es opcional; por defecto no tiene contenido.
|
||||
#[derive(AutoDefault, Debug, Getters)]
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Brand {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
/// Devuelve la imagen de marca (si la hay).
|
||||
image: Typed<Image>,
|
||||
image: Slot<Image>,
|
||||
/// Devuelve el título de la identidad de marca.
|
||||
#[default(_code = "L10n::n(&global::SETTINGS.app.name)")]
|
||||
title: L10n,
|
||||
|
|
@ -36,7 +36,7 @@ impl Component for Brand {
|
|||
self.id.get()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let image = self.image().render(cx);
|
||||
let title = self.title().using(cx);
|
||||
if title.is_empty() && image.is_empty() {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const TOGGLE_OFFCANVAS: &str = "offcanvas";
|
|||
///
|
||||
/// Ver ejemplos en el módulo [`navbar`].
|
||||
/// Si no contiene elementos, el componente **no se renderiza**.
|
||||
#[derive(AutoDefault, Debug, Getters)]
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Navbar {
|
||||
#[getters(skip)]
|
||||
id: AttrId,
|
||||
|
|
@ -39,7 +39,7 @@ impl Component for Navbar {
|
|||
self.id.get()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
self.alter_classes(ClassesOp::Prepend, {
|
||||
let mut classes = "navbar".to_string();
|
||||
self.expand().push_class(&mut classes, "navbar-expand", "");
|
||||
|
|
@ -48,7 +48,7 @@ impl Component for Navbar {
|
|||
});
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
// Botón de despliegue (colapso u offcanvas) para la barra.
|
||||
fn button(cx: &mut Context, data_bs_toggle: &str, id_content: &str) -> Markup {
|
||||
let id_content_target = util::join!("#", id_content);
|
||||
|
|
@ -133,7 +133,7 @@ impl Component for Navbar {
|
|||
@let id_content = offcanvas.id().unwrap_or_default();
|
||||
|
||||
(button(cx, TOGGLE_OFFCANVAS, &id_content))
|
||||
@if let Some(oc) = offcanvas.borrow() {
|
||||
@if let Some(oc) = offcanvas.get() {
|
||||
(oc.render_offcanvas(cx, Some(self.items())))
|
||||
}
|
||||
},
|
||||
|
|
@ -144,7 +144,7 @@ impl Component for Navbar {
|
|||
|
||||
(brand.render(cx))
|
||||
(button(cx, TOGGLE_OFFCANVAS, &id_content))
|
||||
@if let Some(oc) = offcanvas.borrow() {
|
||||
@if let Some(oc) = offcanvas.get() {
|
||||
(oc.render_offcanvas(cx, Some(self.items())))
|
||||
}
|
||||
},
|
||||
|
|
@ -155,7 +155,7 @@ impl Component for Navbar {
|
|||
|
||||
(button(cx, TOGGLE_OFFCANVAS, &id_content))
|
||||
(brand.render(cx))
|
||||
@if let Some(oc) = offcanvas.borrow() {
|
||||
@if let Some(oc) = offcanvas.get() {
|
||||
(oc.render_offcanvas(cx, Some(self.items())))
|
||||
}
|
||||
},
|
||||
|
|
@ -179,37 +179,37 @@ impl Navbar {
|
|||
|
||||
/// Crea una barra de navegación **con marca a la izquierda**, siempre visible.
|
||||
pub fn simple_brand_left(brand: navbar::Brand) -> Self {
|
||||
Self::default().with_layout(navbar::Layout::SimpleBrandLeft(Typed::with(brand)))
|
||||
Self::default().with_layout(navbar::Layout::SimpleBrandLeft(Slot::with(brand)))
|
||||
}
|
||||
|
||||
/// Crea una barra de navegación con **marca a la izquierda** y **botón a la derecha**.
|
||||
pub fn brand_left(brand: navbar::Brand) -> Self {
|
||||
Self::default().with_layout(navbar::Layout::BrandLeft(Typed::with(brand)))
|
||||
Self::default().with_layout(navbar::Layout::BrandLeft(Slot::with(brand)))
|
||||
}
|
||||
|
||||
/// Crea una barra de navegación con **botón a la izquierda** y **marca a la derecha**.
|
||||
pub fn brand_right(brand: navbar::Brand) -> Self {
|
||||
Self::default().with_layout(navbar::Layout::BrandRight(Typed::with(brand)))
|
||||
Self::default().with_layout(navbar::Layout::BrandRight(Slot::with(brand)))
|
||||
}
|
||||
|
||||
/// Crea una barra de navegación cuyo contenido se muestra en un **offcanvas**.
|
||||
pub fn offcanvas(oc: Offcanvas) -> Self {
|
||||
Self::default().with_layout(navbar::Layout::Offcanvas(Typed::with(oc)))
|
||||
Self::default().with_layout(navbar::Layout::Offcanvas(Slot::with(oc)))
|
||||
}
|
||||
|
||||
/// Crea una barra de navegación con **marca a la izquierda** y contenido en **offcanvas**.
|
||||
pub fn offcanvas_brand_left(brand: navbar::Brand, oc: Offcanvas) -> Self {
|
||||
Self::default().with_layout(navbar::Layout::OffcanvasBrandLeft(
|
||||
Typed::with(brand),
|
||||
Typed::with(oc),
|
||||
Slot::with(brand),
|
||||
Slot::with(oc),
|
||||
))
|
||||
}
|
||||
|
||||
/// Crea una barra de navegación con **marca a la derecha** y contenido en **offcanvas**.
|
||||
pub fn offcanvas_brand_right(brand: navbar::Brand, oc: Offcanvas) -> Self {
|
||||
Self::default().with_layout(navbar::Layout::OffcanvasBrandRight(
|
||||
Typed::with(brand),
|
||||
Typed::with(oc),
|
||||
Slot::with(brand),
|
||||
Slot::with(oc),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
@ -262,10 +262,22 @@ impl Navbar {
|
|||
self
|
||||
}
|
||||
|
||||
/// Modifica la lista de contenidos (`children`) aplicando una operación [`TypedOp`].
|
||||
/// Modifica la lista de contenidos de la barra aplicando una operación [`ChildOp`].
|
||||
///
|
||||
/// Para añadir elementos usa [`Child::with(item)`](Child::with):
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// navbar.with_items(ChildOp::Add(Child::with(navbar::Item::nav(...))));
|
||||
/// navbar.with_items(ChildOp::AddMany(vec![
|
||||
/// Child::with(navbar::Item::nav(...)),
|
||||
/// Child::with(navbar::Item::text(...)),
|
||||
/// ]));
|
||||
/// ```
|
||||
///
|
||||
/// Para la mayoría de los casos, [`add_item()`](Self::add_item) es más directo.
|
||||
#[builder_fn]
|
||||
pub fn with_items(mut self, op: TypedOp<navbar::Item>) -> Self {
|
||||
self.items.alter_typed(op);
|
||||
pub fn with_items(mut self, op: ChildOp) -> Self {
|
||||
self.items.alter_child(op);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::prelude::*;
|
|||
/// Cada variante determina qué se renderiza y cómo. Estos elementos se colocan **dentro del
|
||||
/// contenido** de la barra (la parte colapsable, el *offcanvas* o el bloque simple), por lo que son
|
||||
/// independientes de la marca o del botón que ya pueda definir el propio [`navbar::Layout`].
|
||||
#[derive(AutoDefault, Debug)]
|
||||
#[derive(AutoDefault, Clone, Debug)]
|
||||
pub enum Item {
|
||||
/// Sin contenido, no produce salida.
|
||||
#[default]
|
||||
|
|
@ -17,9 +17,9 @@ pub enum Item {
|
|||
/// Útil cuando el [`navbar::Layout`] no incluye marca, y se quiere incluir dentro del área
|
||||
/// colapsable/*offcanvas*. Si el *layout* ya muestra una marca, esta variante no la sustituye,
|
||||
/// sólo añade otra dentro del bloque de contenidos.
|
||||
Brand(Typed<navbar::Brand>),
|
||||
Brand(Slot<navbar::Brand>),
|
||||
/// Representa un menú de navegación [`Nav`](crate::theme::Nav).
|
||||
Nav(Typed<Nav>),
|
||||
Nav(Slot<Nav>),
|
||||
/// Representa un *texto localizado* libre.
|
||||
Text(L10n),
|
||||
}
|
||||
|
|
@ -38,20 +38,20 @@ impl Component for Item {
|
|||
}
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
fn setup(&mut self, _cx: &Context) {
|
||||
if let Self::Nav(nav) = self {
|
||||
if let Some(mut nav) = nav.borrow_mut() {
|
||||
if let Some(mut nav) = nav.get() {
|
||||
nav.alter_classes(ClassesOp::Prepend, "navbar-nav");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
Ok(match self {
|
||||
Self::Void => html! {},
|
||||
Self::Brand(brand) => html! { (brand.render(cx)) },
|
||||
Self::Nav(nav) => {
|
||||
if let Some(nav) = nav.borrow() {
|
||||
if let Some(nav) = nav.get() {
|
||||
let items = nav.items().render(cx);
|
||||
if items.is_empty() {
|
||||
return Ok(html! {});
|
||||
|
|
@ -80,12 +80,12 @@ impl Item {
|
|||
/// Pensado para barras colapsables u offcanvas donde se quiere que la marca aparezca en la zona
|
||||
/// desplegable.
|
||||
pub fn brand(brand: navbar::Brand) -> Self {
|
||||
Self::Brand(Typed::with(brand))
|
||||
Self::Brand(Slot::with(brand))
|
||||
}
|
||||
|
||||
/// Crea un elemento de tipo [`Nav`] para añadir al contenido de [`Navbar`].
|
||||
pub fn nav(item: Nav) -> Self {
|
||||
Self::Nav(Typed::with(item))
|
||||
Self::Nav(Slot::with(item))
|
||||
}
|
||||
|
||||
/// Crea un elemento con un *texto localizado*, mostrado sin interacción.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::prelude::*;
|
|||
// **< Layout >*************************************************************************************
|
||||
|
||||
/// Representa los diferentes tipos de presentación de una barra de navegación [`Navbar`].
|
||||
#[derive(AutoDefault, Debug)]
|
||||
#[derive(AutoDefault, Clone, Debug)]
|
||||
pub enum Layout {
|
||||
/// Barra simple, sin marca de identidad y sin botón de despliegue.
|
||||
///
|
||||
|
|
@ -19,24 +19,24 @@ pub enum Layout {
|
|||
/// Barra simple, con marca de identidad a la izquierda y sin botón de despliegue.
|
||||
///
|
||||
/// La barra de navegación no se colapsa.
|
||||
SimpleBrandLeft(Typed<navbar::Brand>),
|
||||
SimpleBrandLeft(Slot<navbar::Brand>),
|
||||
|
||||
/// Barra con marca de identidad a la izquierda y botón de despliegue a la derecha.
|
||||
BrandLeft(Typed<navbar::Brand>),
|
||||
BrandLeft(Slot<navbar::Brand>),
|
||||
|
||||
/// Barra con botón de despliegue a la izquierda y marca de identidad a la derecha.
|
||||
BrandRight(Typed<navbar::Brand>),
|
||||
BrandRight(Slot<navbar::Brand>),
|
||||
|
||||
/// Contenido en [`Offcanvas`], con botón de despliegue a la izquierda y sin marca de identidad.
|
||||
Offcanvas(Typed<Offcanvas>),
|
||||
Offcanvas(Slot<Offcanvas>),
|
||||
|
||||
/// Contenido en [`Offcanvas`], con marca de identidad a la izquierda y botón de despliegue a la
|
||||
/// derecha.
|
||||
OffcanvasBrandLeft(Typed<navbar::Brand>, Typed<Offcanvas>),
|
||||
OffcanvasBrandLeft(Slot<navbar::Brand>, Slot<Offcanvas>),
|
||||
|
||||
/// Contenido en [`Offcanvas`], con botón de despliegue a la izquierda y marca de identidad a la
|
||||
/// derecha.
|
||||
OffcanvasBrandRight(Typed<navbar::Brand>, Typed<Offcanvas>),
|
||||
OffcanvasBrandRight(Slot<navbar::Brand>, Slot<Offcanvas>),
|
||||
}
|
||||
|
||||
// **< Position >***********************************************************************************
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue