♻️ 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:
Manuel Cillero 2026-03-26 20:36:32 +01:00
parent 04e3d5b3c2
commit 54f990b11c
33 changed files with 740 additions and 314 deletions

View file

@ -6,7 +6,7 @@ use crate::prelude::*;
///
/// Envuelve un contenido con la etiqueta HTML indicada por [`container::Kind`]. Sólo se renderiza
/// si existen componentes hijos (*children*).
#[derive(AutoDefault, Debug, Getters)]
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Container {
#[getters(skip)]
id: AttrId,
@ -29,11 +29,11 @@ impl Component for Container {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
fn setup(&mut self, _cx: &Context) {
self.alter_classes(ClassesOp::Prepend, self.container_width().to_class());
}
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
let output = self.children().render(cx);
if output.is_empty() {
return Ok(html! {});

View file

@ -19,7 +19,7 @@ use crate::LOCALES_BOOTSIER;
///
/// Ver ejemplo en el módulo [`dropdown`].
/// Si no contiene elementos, el componente **no se renderiza**.
#[derive(AutoDefault, Debug, Getters)]
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Dropdown {
#[getters(skip)]
id: AttrId,
@ -56,14 +56,14 @@ impl Component for Dropdown {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
fn setup(&mut self, _cx: &Context) {
self.alter_classes(
ClassesOp::Prepend,
self.direction().class_with(*self.button_grouped()),
);
}
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
// Si no hay elementos en el menú, no se prepara.
let items = self.items().render(cx);
if items.is_empty() {
@ -247,10 +247,20 @@ impl Dropdown {
self
}
/// Modifica la lista de elementos (`children`) aplicando una operación [`TypedOp`].
/// Modifica la lista de elementos del menú aplicando una operación [`ChildOp`].
///
/// Para añadir elementos usa [`Child::with(item)`](Child::with):
///
/// ```rust,ignore
/// dropdown.with_items(ChildOp::Add(Child::with(dropdown::Item::link(...))));
/// dropdown.with_items(ChildOp::AddMany(vec![
/// Child::with(dropdown::Item::link(...)),
/// Child::with(dropdown::Item::divider()),
/// ]));
/// ```
#[builder_fn]
pub fn with_items(mut self, op: TypedOp<dropdown::Item>) -> Self {
self.items.alter_typed(op);
pub fn with_items(mut self, op: ChildOp) -> Self {
self.items.alter_child(op);
self
}
}

View file

@ -7,7 +7,7 @@ use pagetop::prelude::*;
///
/// Define internamente la naturaleza del elemento y su comportamiento al mostrarse o interactuar
/// con él.
#[derive(AutoDefault, Debug)]
#[derive(AutoDefault, Clone, Debug)]
pub enum ItemKind {
/// Elemento vacío, no produce salida.
#[default]
@ -43,7 +43,7 @@ pub enum ItemKind {
///
/// Permite definir el identificador, las clases de estilo adicionales y el tipo de interacción
/// asociada, manteniendo una interfaz común para renderizar todos los elementos del menú.
#[derive(AutoDefault, Debug, Getters)]
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Item {
#[getters(skip)]
id: AttrId,
@ -62,7 +62,7 @@ impl Component for Item {
self.id.get()
}
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
Ok(match self.item_kind() {
ItemKind::Void => html! {},

View file

@ -27,7 +27,7 @@ use crate::theme::form;
/// .with_classes(ClassesOp::Add, "mb-3")
/// .add_child(Input::new().with_name("q"));
/// ```
#[derive(AutoDefault, Debug, Getters)]
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Form {
#[getters(skip)]
id: AttrId,
@ -48,11 +48,11 @@ impl Component for Form {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
fn setup(&mut self, _cx: &Context) {
self.alter_classes(ClassesOp::Prepend, "form");
}
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
let method = match self.method() {
form::Method::Post => Some("post"),
form::Method::Get => None,

View file

@ -3,7 +3,7 @@ use pagetop::prelude::*;
/// Agrupa controles relacionados de un formulario (`<fieldset>`).
///
/// Se usa para mejorar la accesibilidad cuando se acompaña de una leyenda que encabeza el grupo.
#[derive(AutoDefault, Debug, Getters)]
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Fieldset {
#[getters(skip)]
id: AttrId,
@ -22,7 +22,7 @@ impl Component for Fieldset {
self.id.get()
}
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
Ok(html! {
fieldset id=[self.id()] class=[self.classes().get()] disabled[*self.disabled()] {
@if let Some(legend) = self.legend().lookup(cx) {

View file

@ -3,7 +3,7 @@ use pagetop::prelude::*;
use crate::theme::form;
use crate::LOCALES_BOOTSIER;
#[derive(AutoDefault, Debug, Getters)]
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Input {
classes: Classes,
input_type: form::InputType,
@ -29,14 +29,14 @@ impl Component for Input {
Self::default()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
fn setup(&mut self, _cx: &Context) {
self.alter_classes(
ClassesOp::Prepend,
util::join!("form-item form-type-", self.input_type().to_string()),
);
}
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
let id = self.name().get().map(|name| util::join!("edit-", name));
Ok(html! {
div class=[self.classes().get()] {

View file

@ -2,7 +2,7 @@ use crate::prelude::*;
const DEFAULT_VIEWBOX: &str = "0 0 16 16";
#[derive(AutoDefault)]
#[derive(AutoDefault, Clone)]
pub enum IconKind {
#[default]
None,
@ -13,7 +13,7 @@ pub enum IconKind {
},
}
#[derive(AutoDefault, Debug, Getters)]
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Icon {
/// Devuelve las clases CSS asociadas al icono.
classes: Classes,
@ -26,7 +26,7 @@ impl Component for Icon {
Self::default()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
fn setup(&mut self, _cx: &Context) {
if !matches!(self.icon_kind(), IconKind::None) {
self.alter_classes(ClassesOp::Prepend, "icon");
}
@ -35,7 +35,7 @@ impl Component for Icon {
}
}
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
Ok(match self.icon_kind() {
IconKind::None => html! {},
IconKind::Font(_) => {

View file

@ -9,7 +9,7 @@ use crate::prelude::*;
/// ([`classes::Border`](crate::theme::classes::Border)) y **redondeo de esquinas**
/// ([`classes::Rounded`](crate::theme::classes::Rounded)).
/// - Resuelve el texto alternativo `alt` con **localización** mediante [`L10n`].
#[derive(AutoDefault, Debug, Getters)]
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Image {
#[getters(skip)]
id: AttrId,
@ -32,11 +32,11 @@ impl Component for Image {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
fn setup(&mut self, _cx: &Context) {
self.alter_classes(ClassesOp::Prepend, self.source().to_class());
}
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
let dimensions = self.size().to_style();
let alt_text = self.alternative().lookup(cx).unwrap_or_default();
let is_decorative = alt_text.is_empty();

View file

@ -19,9 +19,9 @@
//! .add_item(nav::Item::dropdown(
//! Dropdown::new()
//! .with_title(L10n::n("Options"))
//! .with_items(TypedOp::AddMany(vec![
//! Typed::with(dropdown::Item::link(L10n::n("Action"), |_| "/action".into())),
//! Typed::with(dropdown::Item::link(L10n::n("Another"), |_| "/another".into())),
//! .with_items(ChildOp::AddMany(vec![
//! Child::with(dropdown::Item::link(L10n::n("Action"), |_| "/action".into())),
//! Child::with(dropdown::Item::link(L10n::n("Another"), |_| "/another".into())),
//! ])),
//! ))
//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into()));

View file

@ -10,7 +10,7 @@ use crate::prelude::*;
///
/// Ver ejemplo en el módulo [`nav`].
/// Si no contiene elementos, el componente **no se renderiza**.
#[derive(AutoDefault, Debug, Getters)]
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Nav {
#[getters(skip)]
id: AttrId,
@ -33,7 +33,7 @@ impl Component for Nav {
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 = "nav".to_string();
self.nav_kind().push_class(&mut classes);
@ -42,7 +42,7 @@ impl Component for Nav {
});
}
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
let items = self.items().render(cx);
if items.is_empty() {
return Ok(html! {});
@ -108,10 +108,22 @@ impl Nav {
self
}
/// Modifica la lista de elementos (`children`) aplicando una operación [`TypedOp`].
/// Modifica la lista de elementos del menú aplicando una operación [`ChildOp`].
///
/// Para añadir elementos usa [`Child::with(item)`](Child::with):
///
/// ```rust,ignore
/// nav.with_items(ChildOp::Add(Child::with(nav::Item::link(...))));
/// nav.with_items(ChildOp::AddMany(vec![
/// Child::with(nav::Item::link(...)),
/// Child::with(nav::Item::link_disabled(...)),
/// ]));
/// ```
///
/// 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<nav::Item>) -> Self {
self.items.alter_typed(op);
pub fn with_items(mut self, op: ChildOp) -> Self {
self.items.alter_child(op);
self
}
}

View file

@ -10,7 +10,7 @@ use crate::LOCALES_BOOTSIER;
///
/// Define internamente la naturaleza del elemento y su comportamiento al mostrarse o interactuar
/// con él.
#[derive(AutoDefault, Debug)]
#[derive(AutoDefault, Clone, Debug)]
pub enum ItemKind {
/// Elemento vacío, no produce salida.
#[default]
@ -28,9 +28,9 @@ pub enum ItemKind {
},
/// Contenido HTML arbitrario. El componente [`Html`] se renderiza tal cual como elemento del
/// menú, sin añadir ningún comportamiento de navegación adicional.
Html(Typed<Html>),
Html(Slot<Html>),
/// Elemento que despliega un menú [`Dropdown`].
Dropdown(Typed<Dropdown>),
Dropdown(Slot<Dropdown>),
}
impl ItemKind {
@ -76,7 +76,7 @@ impl ItemKind {
///
/// Permite definir el identificador, las clases de estilo adicionales y el tipo de interacción
/// asociada, manteniendo una interfaz común para renderizar todos los elementos del menú.
#[derive(AutoDefault, Debug, Getters)]
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Item {
#[getters(skip)]
id: AttrId,
@ -95,11 +95,11 @@ impl Component for Item {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
fn setup(&mut self, _cx: &Context) {
self.alter_classes(ClassesOp::Prepend, self.item_kind().to_class());
}
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
Ok(match self.item_kind() {
ItemKind::Void => html! {},
@ -159,7 +159,7 @@ impl Component for Item {
},
ItemKind::Dropdown(menu) => {
if let Some(dd) = menu.borrow() {
if let Some(dd) = menu.get() {
let items = dd.items().render(cx);
if items.is_empty() {
return Ok(html! {});
@ -264,7 +264,7 @@ impl Item {
/// con las clases de navegación asociadas a [`Item`].
pub fn html(html: Html) -> Self {
Self {
item_kind: ItemKind::Html(Typed::with(html)),
item_kind: ItemKind::Html(Slot::with(html)),
..Default::default()
}
}
@ -276,7 +276,7 @@ impl Item {
/// a su representación en [`Nav`].
pub fn dropdown(menu: Dropdown) -> Self {
Self {
item_kind: ItemKind::Dropdown(Typed::with(menu)),
item_kind: ItemKind::Dropdown(Slot::with(menu)),
..Default::default()
}
}

View file

@ -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() {

View file

@ -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
}
}

View file

@ -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.

View file

@ -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 >***********************************************************************************

View file

@ -21,7 +21,7 @@ use crate::LOCALES_BOOTSIER;
///
/// Ver ejemplo en el módulo [`offcanvas`].
/// Si no contiene elementos, el componente **no se renderiza**.
#[derive(AutoDefault, Debug, Getters)]
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Offcanvas {
#[getters(skip)]
id: AttrId,
@ -52,7 +52,7 @@ impl Component for Offcanvas {
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 = "offcanvas".to_string();
self.breakpoint().push_class(&mut classes, "offcanvas", "");
@ -62,7 +62,7 @@ impl Component for Offcanvas {
});
}
fn prepare_component(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
Ok(self.render_offcanvas(cx, None))
}
}