diff --git a/examples/hello-name.rs b/examples/hello-name.rs index b6f9c113..e2904c6f 100644 --- a/examples/hello-name.rs +++ b/examples/hello-name.rs @@ -14,7 +14,7 @@ async fn hello_name( ) -> ResultPage { let name = path.into_inner(); Page::new(request) - .add_child(Html::with(move |_| { + .with_child(Html::with(move |_| { html! { h1 style="text-align: center;" { "Hello " (name) "!" } } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 74727ac2..e6127af9 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -10,7 +10,7 @@ impl Extension for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage { Page::new(request) - .add_child(Html::with(|_| { + .with_child(Html::with(|_| { html! { h1 style="text-align: center;" { "Hello World!" } } diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs index a2ae76d0..15065131 100644 --- a/examples/navbar-menus.rs +++ b/examples/navbar-menus.rs @@ -12,88 +12,88 @@ impl Extension for SuperMenu { fn initialize(&self) { let navbar_menu = Navbar::brand_left(navbar::Brand::new()) .with_expand(BreakPoint::LG) - .add_item(navbar::Item::nav( + .with_item(navbar::Item::nav( Nav::new() - .add_item(nav::Item::link(L10n::l("sample_menus_item_link"), |cx| { + .with_item(nav::Item::link(L10n::l("sample_menus_item_link"), |cx| { cx.route("/") })) - .add_item(nav::Item::link_blank( + .with_item(nav::Item::link_blank( L10n::l("sample_menus_item_blank"), |_| "https://docs.rs/pagetop".into(), )) - .add_item(nav::Item::dropdown( + .with_item(nav::Item::dropdown( Dropdown::new() .with_title(L10n::l("sample_menus_test_title")) - .add_item(dropdown::Item::header(L10n::l("sample_menus_dev_header"))) - .add_item(dropdown::Item::link( + .with_item(dropdown::Item::header(L10n::l("sample_menus_dev_header"))) + .with_item(dropdown::Item::link( L10n::l("sample_menus_dev_getting_started"), |cx| cx.route("/dev/getting-started"), )) - .add_item(dropdown::Item::link( + .with_item(dropdown::Item::link( L10n::l("sample_menus_dev_guides"), |cx| cx.route("/dev/guides"), )) - .add_item(dropdown::Item::link_blank( + .with_item(dropdown::Item::link_blank( L10n::l("sample_menus_dev_forum"), |_| "https://forum.example.dev".into(), )) - .add_item(dropdown::Item::divider()) - .add_item(dropdown::Item::header(L10n::l("sample_menus_sdk_header"))) - .add_item(dropdown::Item::link( + .with_item(dropdown::Item::divider()) + .with_item(dropdown::Item::header(L10n::l("sample_menus_sdk_header"))) + .with_item(dropdown::Item::link( L10n::l("sample_menus_sdk_rust"), |cx| cx.route("/dev/sdks/rust"), )) - .add_item(dropdown::Item::link(L10n::l("sample_menus_sdk_js"), |cx| { + .with_item(dropdown::Item::link(L10n::l("sample_menus_sdk_js"), |cx| { cx.route("/dev/sdks/js") })) - .add_item(dropdown::Item::link( + .with_item(dropdown::Item::link( L10n::l("sample_menus_sdk_python"), |cx| cx.route("/dev/sdks/python"), )) - .add_item(dropdown::Item::divider()) - .add_item(dropdown::Item::header(L10n::l( + .with_item(dropdown::Item::divider()) + .with_item(dropdown::Item::header(L10n::l( "sample_menus_plugin_header", ))) - .add_item(dropdown::Item::link( + .with_item(dropdown::Item::link( L10n::l("sample_menus_plugin_auth"), |cx| cx.route("/dev/sdks/rust/plugins/auth"), )) - .add_item(dropdown::Item::link( + .with_item(dropdown::Item::link( L10n::l("sample_menus_plugin_cache"), |cx| cx.route("/dev/sdks/rust/plugins/cache"), )) - .add_item(dropdown::Item::divider()) - .add_item(dropdown::Item::label(L10n::l("sample_menus_item_label"))) - .add_item(dropdown::Item::link_disabled( + .with_item(dropdown::Item::divider()) + .with_item(dropdown::Item::label(L10n::l("sample_menus_item_label"))) + .with_item(dropdown::Item::link_disabled( L10n::l("sample_menus_item_disabled"), |cx| cx.route("#"), )), )) - .add_item(nav::Item::link_disabled( + .with_item(nav::Item::link_disabled( L10n::l("sample_menus_item_disabled"), |cx| cx.route("#"), )), )) - .add_item(navbar::Item::nav( + .with_item(navbar::Item::nav( Nav::new() .with_classes( ClassesOp::Add, classes::Margin::with(Side::Start, ScaleSize::Auto).to_class(), ) - .add_item(nav::Item::link( + .with_item(nav::Item::link( L10n::l("sample_menus_item_sign_up"), |cx| cx.route("/auth/sign-up"), )) - .add_item(nav::Item::link(L10n::l("sample_menus_item_login"), |cx| { + .with_item(nav::Item::link(L10n::l("sample_menus_item_login"), |cx| { cx.route("/auth/login") })), )); - InRegion::Global(&DefaultRegion::Header).add(Child::with( + InRegion::Global(&DefaultRegion::Header).add( Container::new() .with_width(container::Width::FluidMax(UnitValue::RelRem(75.0))) - .add_child(navbar_menu), - )); + .with_child(navbar_menu), + ); } } diff --git a/extensions/pagetop-aliner/README.md b/extensions/pagetop-aliner/README.md index f3849122..65f91f94 100644 --- a/extensions/pagetop-aliner/README.md +++ b/extensions/pagetop-aliner/README.md @@ -59,7 +59,7 @@ Y **selecciona el tema en la configuración** de la aplicación: theme = "Aliner" ``` -…o **fuerza el tema por código** en una página concreta: +o **fuerza el tema por código** en una página concreta: ```rust,no_run use pagetop::prelude::*; diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index 30621b21..e0421305 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -60,7 +60,7 @@ Y **selecciona el tema en la configuración** de la aplicación: theme = "Aliner" ``` -…o **fuerza el tema por código** en una página concreta: +o **fuerza el tema por código** en una página concreta: ```rust,no_run use pagetop::prelude::*; @@ -69,10 +69,10 @@ use pagetop_aliner::Aliner; async fn homepage(request: HttpRequest) -> ResultPage { Page::new(request) .with_theme(&Aliner) - .add_child( + .with_child( Block::new() .with_title(L10n::l("sample_title")) - .add_child(Html::with(|cx| html! { + .with_child(Html::with(|cx| html! { p { (L10n::l("sample_content").using(cx)) } })), ) @@ -122,7 +122,7 @@ impl Theme for Aliner { )) .alter_child_in( &DefaultRegion::Footer, - ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), + ChildOp::AddIfEmpty(PoweredBy::new().into()), ); } } diff --git a/extensions/pagetop-bootsier/README.md b/extensions/pagetop-bootsier/README.md index e7a3ea79..a48bfd82 100644 --- a/extensions/pagetop-bootsier/README.md +++ b/extensions/pagetop-bootsier/README.md @@ -59,7 +59,7 @@ Y **selecciona el tema en la configuración** de la aplicación: theme = "Bootsier" ``` -…o **fuerza el tema por código** en una página concreta: +o **fuerza el tema por código** en una página concreta: ```rust,no_run use pagetop::prelude::*; diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index 0281fe7a..e23b391f 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -60,7 +60,7 @@ Y **selecciona el tema en la configuración** de la aplicación: theme = "Bootsier" ``` -…o **fuerza el tema por código** en una página concreta: +o **fuerza el tema por código** en una página concreta: ```rust,no_run use pagetop::prelude::*; @@ -69,10 +69,10 @@ use pagetop_bootsier::Bootsier; async fn homepage(request: HttpRequest) -> ResultPage { Page::new(request) .with_theme(&Bootsier) - .add_child( + .with_child( Block::new() .with_title(L10n::l("sample_title")) - .add_child(Html::with(|cx| html! { + .with_child(Html::with(|cx| html! { p { (L10n::l("sample_content").using(cx)) } })), ) @@ -118,7 +118,7 @@ impl Template for BootsierTemplate { .with_width(theme::container::Width::FluidMax( config::SETTINGS.bootsier.max_width, )) - .add_child(Html::with(|cx| { + .with_child(Html::with(|cx| { html! { (DefaultRegion::Header.render(cx)) (DefaultRegion::Content.render(cx)) @@ -163,7 +163,7 @@ impl Theme for Bootsier { )) .alter_child_in( &DefaultRegion::Footer, - ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), + ChildOp::AddIfEmpty(PoweredBy::new().into()), ); } } diff --git a/extensions/pagetop-bootsier/src/theme/container/component.rs b/extensions/pagetop-bootsier/src/theme/container/component.rs index ad828c48..0635ad18 100644 --- a/extensions/pagetop-bootsier/src/theme/container/component.rs +++ b/extensions/pagetop-bootsier/src/theme/container/component.rs @@ -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 { + fn prepare(&self, cx: &mut Context) -> Result { let output = self.children().render(cx); if output.is_empty() { return Ok(html! {}); @@ -150,17 +150,11 @@ impl Container { self } - /// Añade un nuevo componente hijo al contenedor. - #[inline] - pub fn add_child(mut self, component: impl Component) -> Self { - self.children.add(Child::with(component)); - self - } - - /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. + /// Añade un nuevo componente al contenedor o modifica la lista de componentes (`children`) con + /// una operación [`ChildOp`]. #[builder_fn] - pub fn with_child(mut self, op: ChildOp) -> Self { - self.children.alter_child(op); + pub fn with_child(mut self, op: impl Into) -> Self { + self.children.alter_child(op.into()); self } } diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs index ec62c531..213756c7 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown.rs @@ -17,11 +17,11 @@ //! .with_button_color(ButtonColor::Background(Color::Secondary)) //! .with_auto_close(dropdown::AutoClose::ClickableInside) //! .with_direction(dropdown::Direction::Dropend) -//! .add_item(dropdown::Item::link(L10n::n("Home"), |_| "/".into())) -//! .add_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://google.es".into())) -//! .add_item(dropdown::Item::divider()) -//! .add_item(dropdown::Item::header(L10n::n("User session"))) -//! .add_item(dropdown::Item::button(L10n::n("Sign out"))); +//! .with_item(dropdown::Item::link(L10n::n("Home"), |_| "/".into())) +//! .with_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://docs.rs".into())) +//! .with_item(dropdown::Item::divider()) +//! .with_item(dropdown::Item::header(L10n::n("User session"))) +//! .with_item(dropdown::Item::button(L10n::n("Sign out"))); //! ``` mod props; diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index cb721fca..833cf40b 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -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 { + fn prepare(&self, cx: &mut Context) -> Result { // Si no hay elementos en el menú, no se prepara. let items = self.items().render(cx); if items.is_empty() { @@ -240,17 +240,22 @@ impl Dropdown { self } - /// Añade un nuevo elemento hijo al menú. - #[inline] - pub fn add_item(mut self, item: dropdown::Item) -> Self { - self.items.add(Child::with(item)); - self - } - - /// Modifica la lista de elementos (`children`) aplicando una operación [`TypedOp`]. + /// Añade un nuevo elemento al menú o modifica la lista de elementos del menú con una operación + /// [`ChildOp`]. + /// + /// # Ejemplo + /// + /// ```rust,ignore + /// dropdown.with_item(dropdown::Item::link("Opción", "/ruta")); + /// dropdown.with_item(ChildOp::AddMany(vec![ + /// dropdown::Item::link(...).into(), + /// dropdown::Item::divider().into(), + /// dropdown::Item::link(...).into(), + /// ])); + /// ``` #[builder_fn] - pub fn with_items(mut self, op: TypedOp) -> Self { - self.items.alter_typed(op); + pub fn with_item(mut self, op: impl Into) -> Self { + self.items.alter_child(op.into()); self } } diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs index ac252d1b..379a16c9 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs @@ -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 { + fn prepare(&self, cx: &mut Context) -> Result { Ok(match self.item_kind() { ItemKind::Void => html! {}, diff --git a/extensions/pagetop-bootsier/src/theme/form/component.rs b/extensions/pagetop-bootsier/src/theme/form/component.rs index 6ed05938..10dd803c 100644 --- a/extensions/pagetop-bootsier/src/theme/form/component.rs +++ b/extensions/pagetop-bootsier/src/theme/form/component.rs @@ -25,9 +25,9 @@ use crate::theme::form; /// .with_action("/search") /// .with_method(form::Method::Get) /// .with_classes(ClassesOp::Add, "mb-3") -/// .add_child(Input::new().with_name("q")); +/// .with_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 { + fn prepare(&self, cx: &mut Context) -> Result { let method = match self.method() { form::Method::Post => Some("post"), form::Method::Get => None, @@ -114,17 +114,11 @@ impl Form { self } - /// Añade un nuevo componente hijo al formulario. - #[inline] - pub fn add_child(mut self, component: impl Component) -> Self { - self.children.add(Child::with(component)); - self - } - - /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. + /// Añade un nuevo componente al formulario o modifica la lista de de componentes (`children`) + /// con una operación [`ChildOp`]. #[builder_fn] - pub fn with_child(mut self, op: ChildOp) -> Self { - self.children.alter_child(op); + pub fn with_child(mut self, op: impl Into) -> Self { + self.children.alter_child(op.into()); self } } diff --git a/extensions/pagetop-bootsier/src/theme/form/fieldset.rs b/extensions/pagetop-bootsier/src/theme/form/fieldset.rs index 536218e9..02567373 100644 --- a/extensions/pagetop-bootsier/src/theme/form/fieldset.rs +++ b/extensions/pagetop-bootsier/src/theme/form/fieldset.rs @@ -3,7 +3,7 @@ use pagetop::prelude::*; /// Agrupa controles relacionados de un formulario (`
`). /// /// 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 { + fn prepare(&self, cx: &mut Context) -> Result { Ok(html! { fieldset id=[self.id()] class=[self.classes().get()] disabled[*self.disabled()] { @if let Some(legend) = self.legend().lookup(cx) { @@ -65,17 +65,11 @@ impl Fieldset { self } - /// Añade un nuevo componente hijo al `fieldset`. - #[inline] - pub fn add_child(mut self, component: impl Component) -> Self { - self.children.add(Child::with(component)); - self - } - - /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. + /// Añade un nuevo componente al `fieldset` o modifica la lista de de componentes (`children`) + /// con una operación [`ChildOp`]. #[builder_fn] - pub fn with_child(mut self, op: ChildOp) -> Self { - self.children.alter_child(op); + pub fn with_child(mut self, op: impl Into) -> Self { + self.children.alter_child(op.into()); self } } diff --git a/extensions/pagetop-bootsier/src/theme/form/input.rs b/extensions/pagetop-bootsier/src/theme/form/input.rs index 1872f806..409fc816 100644 --- a/extensions/pagetop-bootsier/src/theme/form/input.rs +++ b/extensions/pagetop-bootsier/src/theme/form/input.rs @@ -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 { + fn prepare(&self, cx: &mut Context) -> Result { let id = self.name().get().map(|name| util::join!("edit-", name)); Ok(html! { div class=[self.classes().get()] { diff --git a/extensions/pagetop-bootsier/src/theme/icon.rs b/extensions/pagetop-bootsier/src/theme/icon.rs index e3c0fe56..935aa847 100644 --- a/extensions/pagetop-bootsier/src/theme/icon.rs +++ b/extensions/pagetop-bootsier/src/theme/icon.rs @@ -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 { + fn prepare(&self, cx: &mut Context) -> Result { Ok(match self.icon_kind() { IconKind::None => html! {}, IconKind::Font(_) => { diff --git a/extensions/pagetop-bootsier/src/theme/image/component.rs b/extensions/pagetop-bootsier/src/theme/image/component.rs index f9f04d26..898fb573 100644 --- a/extensions/pagetop-bootsier/src/theme/image/component.rs +++ b/extensions/pagetop-bootsier/src/theme/image/component.rs @@ -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 { + fn prepare(&self, cx: &mut Context) -> Result { let dimensions = self.size().to_style(); let alt_text = self.alternative().lookup(cx).unwrap_or_default(); let is_decorative = alt_text.is_empty(); diff --git a/extensions/pagetop-bootsier/src/theme/nav.rs b/extensions/pagetop-bootsier/src/theme/nav.rs index b5ae84a5..16b3d2d7 100644 --- a/extensions/pagetop-bootsier/src/theme/nav.rs +++ b/extensions/pagetop-bootsier/src/theme/nav.rs @@ -14,17 +14,17 @@ //! # use pagetop_bootsier::prelude::*; //! let nav = Nav::tabs() //! .with_layout(nav::Layout::End) -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .add_item(nav::Item::link_blank(L10n::n("External"), |_| "https://google.es".into())) -//! .add_item(nav::Item::dropdown( +//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .with_item(nav::Item::link_blank(L10n::n("External"), |_| "https://docs.rs".into())) +//! .with_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_item(ChildOp::AddMany(vec![ +//! dropdown::Item::link(L10n::n("Action"), |_| "/action".into()).into(), +//! dropdown::Item::link(L10n::n("Another"), |_| "/another".into()).into(), //! ])), //! )) -//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())); +//! .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())); //! ``` mod props; diff --git a/extensions/pagetop-bootsier/src/theme/nav/component.rs b/extensions/pagetop-bootsier/src/theme/nav/component.rs index fd849a9c..0b7797c8 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/component.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/component.rs @@ -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 { + fn prepare(&self, cx: &mut Context) -> Result { let items = self.items().render(cx); if items.is_empty() { return Ok(html! {}); @@ -102,16 +102,21 @@ impl Nav { self } - /// Añade un nuevo elemento hijo al menú. - pub fn add_item(mut self, item: nav::Item) -> Self { - self.items.add(Child::with(item)); - self - } - - /// Modifica la lista de elementos (`children`) aplicando una operación [`TypedOp`]. + /// Añade un nuevo elemento al menú o modifica la lista de elementos del menú con una operación + /// [`ChildOp`]. + /// + /// # Ejemplo + /// + /// ```rust,ignore + /// nav.with_item(nav::Item::link("Inicio", "/")); + /// nav.with_item(ChildOp::AddMany(vec![ + /// nav::Item::link(...).into(), + /// nav::Item::link_disabled(...).into(), + /// ])); + /// ``` #[builder_fn] - pub fn with_items(mut self, op: TypedOp) -> Self { - self.items.alter_typed(op); + pub fn with_item(mut self, op: impl Into) -> Self { + self.items.alter_child(op.into()); self } } diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index 45418021..956ccdcd 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -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(Embed), /// Elemento que despliega un menú [`Dropdown`]. - Dropdown(Typed), + Dropdown(Embed), } 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 { + fn prepare(&self, cx: &mut Context) -> Result { 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(Embed::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(Embed::with(menu)), ..Default::default() } } diff --git a/extensions/pagetop-bootsier/src/theme/navbar.rs b/extensions/pagetop-bootsier/src/theme/navbar.rs index 717ec679..31a16ccc 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar.rs @@ -15,11 +15,11 @@ //! # use pagetop::prelude::*; //! # use pagetop_bootsier::prelude::*; //! let navbar = Navbar::simple() -//! .add_item(navbar::Item::nav( +//! .with_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .add_item(nav::Item::link(L10n::n("About"), |_| "/about".into())) -//! .add_item(nav::Item::link(L10n::n("Contact"), |_| "/contact".into())) +//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .with_item(nav::Item::link(L10n::n("About"), |_| "/about".into())) +//! .with_item(nav::Item::link(L10n::n("Contact"), |_| "/contact".into())) //! )); //! ``` //! @@ -30,11 +30,11 @@ //! # use pagetop_bootsier::prelude::*; //! let navbar = Navbar::simple_toggle() //! .with_expand(BreakPoint::MD) -//! .add_item(navbar::Item::nav( +//! .with_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .add_item(nav::Item::link_blank(L10n::n("Docs"), |_| "https://sample.com".into())) -//! .add_item(nav::Item::link(L10n::n("Support"), |_| "/support".into())) +//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .with_item(nav::Item::link_blank(L10n::n("Docs"), |_| "https://docs.rs".into())) +//! .with_item(nav::Item::link(L10n::n("Support"), |_| "/support".into())) //! )); //! ``` //! @@ -48,20 +48,20 @@ //! .with_route(Some(|cx| cx.route("/"))); //! //! let navbar = Navbar::brand_left(brand) -//! .add_item(navbar::Item::nav( +//! .with_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .add_item(nav::Item::dropdown( +//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .with_item(nav::Item::dropdown( //! Dropdown::new() //! .with_title(L10n::n("Tools")) -//! .add_item(dropdown::Item::link( +//! .with_item(dropdown::Item::link( //! L10n::n("Generator"), |_| "/tools/gen".into()) //! ) -//! .add_item(dropdown::Item::link( +//! .with_item(dropdown::Item::link( //! L10n::n("Reports"), |_| "/tools/reports".into()) //! ) //! )) -//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())) +//! .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())) //! )); //! ``` //! @@ -76,10 +76,10 @@ //! //! let navbar = Navbar::brand_right(brand) //! .with_expand(BreakPoint::LG) -//! .add_item(navbar::Item::nav( +//! .with_item(navbar::Item::nav( //! Nav::pills() -//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard".into())) -//! .add_item(nav::Item::link(L10n::n("Users"), |_| "/users".into())) +//! .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard".into())) +//! .with_item(nav::Item::link(L10n::n("Users"), |_| "/users".into())) //! )); //! ``` //! @@ -95,15 +95,15 @@ //! .with_backdrop(offcanvas::Backdrop::Enabled); //! //! let navbar = Navbar::offcanvas(oc) -//! .add_item(navbar::Item::nav( +//! .with_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .add_item(nav::Item::link(L10n::n("Profile"), |_| "/profile".into())) -//! .add_item(nav::Item::dropdown( +//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .with_item(nav::Item::link(L10n::n("Profile"), |_| "/profile".into())) +//! .with_item(nav::Item::dropdown( //! Dropdown::new() //! .with_title(L10n::n("More")) -//! .add_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings".into())) -//! .add_item(dropdown::Item::link(L10n::n("Help"), |_| "/help".into())) +//! .with_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings".into())) +//! .with_item(dropdown::Item::link(L10n::n("Help"), |_| "/help".into())) //! )) //! )); //! ``` @@ -119,11 +119,11 @@ //! //! let navbar = Navbar::brand_left(brand) //! .with_position(navbar::Position::FixedTop) -//! .add_item(navbar::Item::nav( +//! .with_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/".into())) -//! .add_item(nav::Item::link(L10n::n("Donors"), |_| "/donors".into())) -//! .add_item(nav::Item::link(L10n::n("Stock"), |_| "/stock".into())) +//! .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/".into())) +//! .with_item(nav::Item::link(L10n::n("Donors"), |_| "/donors".into())) +//! .with_item(nav::Item::link(L10n::n("Stock"), |_| "/stock".into())) //! )); //! ``` diff --git a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs index 5c22195a..4d575e03 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs @@ -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: Embed, /// 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 { + fn prepare(&self, cx: &mut Context) -> Result { let image = self.image().render(cx); let title = self.title().using(cx); if title.is_empty() && image.is_empty() { diff --git a/extensions/pagetop-bootsier/src/theme/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/navbar/component.rs index 6f0ecf54..3319b79e 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/component.rs @@ -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 { + fn prepare(&self, cx: &mut Context) -> Result { // 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(Embed::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(Embed::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(Embed::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(Embed::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), + Embed::with(brand), + Embed::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), + Embed::with(brand), + Embed::with(oc), )) } @@ -255,17 +255,21 @@ impl Navbar { self } - /// Añade un nuevo contenido hijo. - #[inline] - pub fn add_item(mut self, item: navbar::Item) -> Self { - self.items.add(Child::with(item)); - self - } - - /// Modifica la lista de contenidos (`children`) aplicando una operación [`TypedOp`]. + /// Añade un nuevo contenido a la barra de navegación o modifica la lista de contenidos de la + /// barra con una operación [`ChildOp`]. + /// + /// # Ejemplo + /// + /// ```rust,ignore + /// navbar.with_item(navbar::Item::nav(...)); + /// navbar.with_item(ChildOp::AddMany(vec![ + /// navbar::Item::nav(...).into(), + /// navbar::Item::text(...).into(), + /// ])); + /// ``` #[builder_fn] - pub fn with_items(mut self, op: TypedOp) -> Self { - self.items.alter_typed(op); + pub fn with_item(mut self, op: impl Into) -> Self { + self.items.alter_child(op.into()); self } } diff --git a/extensions/pagetop-bootsier/src/theme/navbar/item.rs b/extensions/pagetop-bootsier/src/theme/navbar/item.rs index 28e74cae..caba4e7d 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/item.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/item.rs @@ -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), + Brand(Embed), /// Representa un menú de navegación [`Nav`](crate::theme::Nav). - Nav(Typed