diff --git a/examples/hello-name.rs b/examples/hello-name.rs index e2904c6f..b6f9c113 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) - .with_child(Html::with(move |_| { + .add_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 e6127af9..74727ac2 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) - .with_child(Html::with(|_| { + .add_child(Html::with(|_| { html! { h1 style="text-align: center;" { "Hello World!" } } diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs index 15065131..a2ae76d0 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) - .with_item(navbar::Item::nav( + .add_item(navbar::Item::nav( Nav::new() - .with_item(nav::Item::link(L10n::l("sample_menus_item_link"), |cx| { + .add_item(nav::Item::link(L10n::l("sample_menus_item_link"), |cx| { cx.route("/") })) - .with_item(nav::Item::link_blank( + .add_item(nav::Item::link_blank( L10n::l("sample_menus_item_blank"), |_| "https://docs.rs/pagetop".into(), )) - .with_item(nav::Item::dropdown( + .add_item(nav::Item::dropdown( Dropdown::new() .with_title(L10n::l("sample_menus_test_title")) - .with_item(dropdown::Item::header(L10n::l("sample_menus_dev_header"))) - .with_item(dropdown::Item::link( + .add_item(dropdown::Item::header(L10n::l("sample_menus_dev_header"))) + .add_item(dropdown::Item::link( L10n::l("sample_menus_dev_getting_started"), |cx| cx.route("/dev/getting-started"), )) - .with_item(dropdown::Item::link( + .add_item(dropdown::Item::link( L10n::l("sample_menus_dev_guides"), |cx| cx.route("/dev/guides"), )) - .with_item(dropdown::Item::link_blank( + .add_item(dropdown::Item::link_blank( L10n::l("sample_menus_dev_forum"), |_| "https://forum.example.dev".into(), )) - .with_item(dropdown::Item::divider()) - .with_item(dropdown::Item::header(L10n::l("sample_menus_sdk_header"))) - .with_item(dropdown::Item::link( + .add_item(dropdown::Item::divider()) + .add_item(dropdown::Item::header(L10n::l("sample_menus_sdk_header"))) + .add_item(dropdown::Item::link( L10n::l("sample_menus_sdk_rust"), |cx| cx.route("/dev/sdks/rust"), )) - .with_item(dropdown::Item::link(L10n::l("sample_menus_sdk_js"), |cx| { + .add_item(dropdown::Item::link(L10n::l("sample_menus_sdk_js"), |cx| { cx.route("/dev/sdks/js") })) - .with_item(dropdown::Item::link( + .add_item(dropdown::Item::link( L10n::l("sample_menus_sdk_python"), |cx| cx.route("/dev/sdks/python"), )) - .with_item(dropdown::Item::divider()) - .with_item(dropdown::Item::header(L10n::l( + .add_item(dropdown::Item::divider()) + .add_item(dropdown::Item::header(L10n::l( "sample_menus_plugin_header", ))) - .with_item(dropdown::Item::link( + .add_item(dropdown::Item::link( L10n::l("sample_menus_plugin_auth"), |cx| cx.route("/dev/sdks/rust/plugins/auth"), )) - .with_item(dropdown::Item::link( + .add_item(dropdown::Item::link( L10n::l("sample_menus_plugin_cache"), |cx| cx.route("/dev/sdks/rust/plugins/cache"), )) - .with_item(dropdown::Item::divider()) - .with_item(dropdown::Item::label(L10n::l("sample_menus_item_label"))) - .with_item(dropdown::Item::link_disabled( + .add_item(dropdown::Item::divider()) + .add_item(dropdown::Item::label(L10n::l("sample_menus_item_label"))) + .add_item(dropdown::Item::link_disabled( L10n::l("sample_menus_item_disabled"), |cx| cx.route("#"), )), )) - .with_item(nav::Item::link_disabled( + .add_item(nav::Item::link_disabled( L10n::l("sample_menus_item_disabled"), |cx| cx.route("#"), )), )) - .with_item(navbar::Item::nav( + .add_item(navbar::Item::nav( Nav::new() .with_classes( ClassesOp::Add, classes::Margin::with(Side::Start, ScaleSize::Auto).to_class(), ) - .with_item(nav::Item::link( + .add_item(nav::Item::link( L10n::l("sample_menus_item_sign_up"), |cx| cx.route("/auth/sign-up"), )) - .with_item(nav::Item::link(L10n::l("sample_menus_item_login"), |cx| { + .add_item(nav::Item::link(L10n::l("sample_menus_item_login"), |cx| { cx.route("/auth/login") })), )); - InRegion::Global(&DefaultRegion::Header).add( + InRegion::Global(&DefaultRegion::Header).add(Child::with( Container::new() .with_width(container::Width::FluidMax(UnitValue::RelRem(75.0))) - .with_child(navbar_menu), - ); + .add_child(navbar_menu), + )); } } diff --git a/extensions/pagetop-aliner/README.md b/extensions/pagetop-aliner/README.md index 65f91f94..f3849122 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 e0421305..30621b21 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) - .with_child( + .add_child( Block::new() .with_title(L10n::l("sample_title")) - .with_child(Html::with(|cx| html! { + .add_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(PoweredBy::new().into()), + ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), ); } } diff --git a/extensions/pagetop-bootsier/README.md b/extensions/pagetop-bootsier/README.md index a48bfd82..e7a3ea79 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 e23b391f..0281fe7a 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) - .with_child( + .add_child( Block::new() .with_title(L10n::l("sample_title")) - .with_child(Html::with(|cx| html! { + .add_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, )) - .with_child(Html::with(|cx| { + .add_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(PoweredBy::new().into()), + ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), ); } } diff --git a/extensions/pagetop-bootsier/src/theme/container/component.rs b/extensions/pagetop-bootsier/src/theme/container/component.rs index 0635ad18..ad828c48 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, Clone, Debug, Getters)] +#[derive(AutoDefault, Debug, Getters)] pub struct Container { #[getters(skip)] id: AttrId, @@ -29,11 +29,11 @@ impl Component for Container { self.id.get() } - fn setup(&mut self, _cx: &Context) { + fn setup_before_prepare(&mut self, _cx: &mut Context) { self.alter_classes(ClassesOp::Prepend, self.container_width().to_class()); } - fn prepare(&self, cx: &mut Context) -> Result { + fn prepare_component(&self, cx: &mut Context) -> Result { let output = self.children().render(cx); if output.is_empty() { return Ok(html! {}); @@ -150,11 +150,17 @@ impl Container { self } - /// Añade un nuevo componente al contenedor o modifica la lista de componentes (`children`) con - /// una operación [`ChildOp`]. + /// 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`]. #[builder_fn] - pub fn with_child(mut self, op: impl Into) -> Self { - self.children.alter_child(op.into()); + pub fn with_child(mut self, op: ChildOp) -> Self { + self.children.alter_child(op); self } } diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs index 213756c7..ec62c531 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) -//! .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"))); +//! .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"))); //! ``` mod props; diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index 833cf40b..cb721fca 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, Clone, Debug, Getters)] +#[derive(AutoDefault, Debug, Getters)] pub struct Dropdown { #[getters(skip)] id: AttrId, @@ -56,14 +56,14 @@ impl Component for Dropdown { self.id.get() } - fn setup(&mut self, _cx: &Context) { + fn setup_before_prepare(&mut self, _cx: &mut Context) { self.alter_classes( ClassesOp::Prepend, self.direction().class_with(*self.button_grouped()), ); } - fn prepare(&self, cx: &mut Context) -> Result { + fn prepare_component(&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,22 +240,17 @@ impl Dropdown { self } - /// 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(), - /// ])); - /// ``` + /// 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`]. #[builder_fn] - pub fn with_item(mut self, op: impl Into) -> Self { - self.items.alter_child(op.into()); + pub fn with_items(mut self, op: TypedOp) -> Self { + self.items.alter_typed(op); self } } diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs index 379a16c9..ac252d1b 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, Clone, Debug)] +#[derive(AutoDefault, 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, Clone, Debug, Getters)] +#[derive(AutoDefault, Debug, Getters)] pub struct Item { #[getters(skip)] id: AttrId, @@ -62,7 +62,7 @@ impl Component for Item { self.id.get() } - fn prepare(&self, cx: &mut Context) -> Result { + fn prepare_component(&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 10dd803c..6ed05938 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") -/// .with_child(Input::new().with_name("q")); +/// .add_child(Input::new().with_name("q")); /// ``` -#[derive(AutoDefault, Clone, Debug, Getters)] +#[derive(AutoDefault, Debug, Getters)] pub struct Form { #[getters(skip)] id: AttrId, @@ -48,11 +48,11 @@ impl Component for Form { self.id.get() } - fn setup(&mut self, _cx: &Context) { + fn setup_before_prepare(&mut self, _cx: &mut Context) { self.alter_classes(ClassesOp::Prepend, "form"); } - fn prepare(&self, cx: &mut Context) -> Result { + fn prepare_component(&self, cx: &mut Context) -> Result { let method = match self.method() { form::Method::Post => Some("post"), form::Method::Get => None, @@ -114,11 +114,17 @@ impl Form { self } - /// Añade un nuevo componente al formulario o modifica la lista de de componentes (`children`) - /// con una operación [`ChildOp`]. + /// 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`]. #[builder_fn] - pub fn with_child(mut self, op: impl Into) -> Self { - self.children.alter_child(op.into()); + pub fn with_child(mut self, op: ChildOp) -> Self { + self.children.alter_child(op); self } } diff --git a/extensions/pagetop-bootsier/src/theme/form/fieldset.rs b/extensions/pagetop-bootsier/src/theme/form/fieldset.rs index 02567373..536218e9 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, Clone, Debug, Getters)] +#[derive(AutoDefault, Debug, Getters)] pub struct Fieldset { #[getters(skip)] id: AttrId, @@ -22,7 +22,7 @@ impl Component for Fieldset { self.id.get() } - fn prepare(&self, cx: &mut Context) -> Result { + fn prepare_component(&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,11 +65,17 @@ impl Fieldset { self } - /// Añade un nuevo componente al `fieldset` o modifica la lista de de componentes (`children`) - /// con una operación [`ChildOp`]. + /// 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`]. #[builder_fn] - pub fn with_child(mut self, op: impl Into) -> Self { - self.children.alter_child(op.into()); + pub fn with_child(mut self, op: ChildOp) -> Self { + self.children.alter_child(op); self } } diff --git a/extensions/pagetop-bootsier/src/theme/form/input.rs b/extensions/pagetop-bootsier/src/theme/form/input.rs index 409fc816..1872f806 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, Clone, Debug, Getters)] +#[derive(AutoDefault, Debug, Getters)] pub struct Input { classes: Classes, input_type: form::InputType, @@ -29,14 +29,14 @@ impl Component for Input { Self::default() } - fn setup(&mut self, _cx: &Context) { + fn setup_before_prepare(&mut self, _cx: &mut Context) { self.alter_classes( ClassesOp::Prepend, util::join!("form-item form-type-", self.input_type().to_string()), ); } - fn prepare(&self, cx: &mut Context) -> Result { + fn prepare_component(&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 935aa847..e3c0fe56 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, Clone)] +#[derive(AutoDefault)] pub enum IconKind { #[default] None, @@ -13,7 +13,7 @@ pub enum IconKind { }, } -#[derive(AutoDefault, Clone, Debug, Getters)] +#[derive(AutoDefault, 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(&mut self, _cx: &Context) { + fn setup_before_prepare(&mut self, _cx: &mut Context) { if !matches!(self.icon_kind(), IconKind::None) { self.alter_classes(ClassesOp::Prepend, "icon"); } @@ -35,7 +35,7 @@ impl Component for Icon { } } - fn prepare(&self, cx: &mut Context) -> Result { + fn prepare_component(&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 898fb573..f9f04d26 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, Clone, Debug, Getters)] +#[derive(AutoDefault, Debug, Getters)] pub struct Image { #[getters(skip)] id: AttrId, @@ -32,11 +32,11 @@ impl Component for Image { self.id.get() } - fn setup(&mut self, _cx: &Context) { + fn setup_before_prepare(&mut self, _cx: &mut Context) { self.alter_classes(ClassesOp::Prepend, self.source().to_class()); } - fn prepare(&self, cx: &mut Context) -> Result { + fn prepare_component(&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 16b3d2d7..b5ae84a5 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) -//! .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( +//! .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( //! Dropdown::new() //! .with_title(L10n::n("Options")) -//! .with_item(ChildOp::AddMany(vec![ -//! dropdown::Item::link(L10n::n("Action"), |_| "/action".into()).into(), -//! dropdown::Item::link(L10n::n("Another"), |_| "/another".into()).into(), +//! .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(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())); +//! .add_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 0b7797c8..fd849a9c 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, Clone, Debug, Getters)] +#[derive(AutoDefault, Debug, Getters)] pub struct Nav { #[getters(skip)] id: AttrId, @@ -33,7 +33,7 @@ impl Component for Nav { self.id.get() } - fn setup(&mut self, _cx: &Context) { + fn setup_before_prepare(&mut self, _cx: &mut 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(&self, cx: &mut Context) -> Result { + fn prepare_component(&self, cx: &mut Context) -> Result { let items = self.items().render(cx); if items.is_empty() { return Ok(html! {}); @@ -102,21 +102,16 @@ impl Nav { self } - /// 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(), - /// ])); - /// ``` + /// 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`]. #[builder_fn] - pub fn with_item(mut self, op: impl Into) -> Self { - self.items.alter_child(op.into()); + pub fn with_items(mut self, op: TypedOp) -> Self { + self.items.alter_typed(op); self } } diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index 956ccdcd..45418021 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, Clone, Debug)] +#[derive(AutoDefault, 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(Embed), + Html(Typed), /// Elemento que despliega un menú [`Dropdown`]. - Dropdown(Embed), + Dropdown(Typed), } 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, Clone, Debug, Getters)] +#[derive(AutoDefault, Debug, Getters)] pub struct Item { #[getters(skip)] id: AttrId, @@ -95,11 +95,11 @@ impl Component for Item { self.id.get() } - fn setup(&mut self, _cx: &Context) { + fn setup_before_prepare(&mut self, _cx: &mut Context) { self.alter_classes(ClassesOp::Prepend, self.item_kind().to_class()); } - fn prepare(&self, cx: &mut Context) -> Result { + fn prepare_component(&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.get() { + if let Some(dd) = menu.borrow() { 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(Embed::with(html)), + item_kind: ItemKind::Html(Typed::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(Embed::with(menu)), + item_kind: ItemKind::Dropdown(Typed::with(menu)), ..Default::default() } } diff --git a/extensions/pagetop-bootsier/src/theme/navbar.rs b/extensions/pagetop-bootsier/src/theme/navbar.rs index 31a16ccc..717ec679 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() -//! .with_item(navbar::Item::nav( +//! .add_item(navbar::Item::nav( //! Nav::new() -//! .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())) +//! .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())) //! )); //! ``` //! @@ -30,11 +30,11 @@ //! # use pagetop_bootsier::prelude::*; //! let navbar = Navbar::simple_toggle() //! .with_expand(BreakPoint::MD) -//! .with_item(navbar::Item::nav( +//! .add_item(navbar::Item::nav( //! Nav::new() -//! .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())) +//! .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())) //! )); //! ``` //! @@ -48,20 +48,20 @@ //! .with_route(Some(|cx| cx.route("/"))); //! //! let navbar = Navbar::brand_left(brand) -//! .with_item(navbar::Item::nav( +//! .add_item(navbar::Item::nav( //! Nav::new() -//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .with_item(nav::Item::dropdown( +//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .add_item(nav::Item::dropdown( //! Dropdown::new() //! .with_title(L10n::n("Tools")) -//! .with_item(dropdown::Item::link( +//! .add_item(dropdown::Item::link( //! L10n::n("Generator"), |_| "/tools/gen".into()) //! ) -//! .with_item(dropdown::Item::link( +//! .add_item(dropdown::Item::link( //! L10n::n("Reports"), |_| "/tools/reports".into()) //! ) //! )) -//! .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())) +//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())) //! )); //! ``` //! @@ -76,10 +76,10 @@ //! //! let navbar = Navbar::brand_right(brand) //! .with_expand(BreakPoint::LG) -//! .with_item(navbar::Item::nav( +//! .add_item(navbar::Item::nav( //! Nav::pills() -//! .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard".into())) -//! .with_item(nav::Item::link(L10n::n("Users"), |_| "/users".into())) +//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard".into())) +//! .add_item(nav::Item::link(L10n::n("Users"), |_| "/users".into())) //! )); //! ``` //! @@ -95,15 +95,15 @@ //! .with_backdrop(offcanvas::Backdrop::Enabled); //! //! let navbar = Navbar::offcanvas(oc) -//! .with_item(navbar::Item::nav( +//! .add_item(navbar::Item::nav( //! Nav::new() -//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .with_item(nav::Item::link(L10n::n("Profile"), |_| "/profile".into())) -//! .with_item(nav::Item::dropdown( +//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .add_item(nav::Item::link(L10n::n("Profile"), |_| "/profile".into())) +//! .add_item(nav::Item::dropdown( //! Dropdown::new() //! .with_title(L10n::n("More")) -//! .with_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings".into())) -//! .with_item(dropdown::Item::link(L10n::n("Help"), |_| "/help".into())) +//! .add_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings".into())) +//! .add_item(dropdown::Item::link(L10n::n("Help"), |_| "/help".into())) //! )) //! )); //! ``` @@ -119,11 +119,11 @@ //! //! let navbar = Navbar::brand_left(brand) //! .with_position(navbar::Position::FixedTop) -//! .with_item(navbar::Item::nav( +//! .add_item(navbar::Item::nav( //! Nav::new() -//! .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())) +//! .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())) //! )); //! ``` diff --git a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs index 4d575e03..5c22195a 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, Clone, Debug, Getters)] +#[derive(AutoDefault, Debug, Getters)] pub struct Brand { #[getters(skip)] id: AttrId, /// Devuelve la imagen de marca (si la hay). - image: Embed, + image: Typed, /// 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(&self, cx: &mut Context) -> Result { + fn prepare_component(&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 3319b79e..6f0ecf54 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, Clone, Debug, Getters)] +#[derive(AutoDefault, Debug, Getters)] pub struct Navbar { #[getters(skip)] id: AttrId, @@ -39,7 +39,7 @@ impl Component for Navbar { self.id.get() } - fn setup(&mut self, _cx: &Context) { + fn setup_before_prepare(&mut self, _cx: &mut 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(&self, cx: &mut Context) -> Result { + fn prepare_component(&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.get() { + @if let Some(oc) = offcanvas.borrow() { (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.get() { + @if let Some(oc) = offcanvas.borrow() { (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.get() { + @if let Some(oc) = offcanvas.borrow() { (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(Embed::with(brand))) + Self::default().with_layout(navbar::Layout::SimpleBrandLeft(Typed::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(Embed::with(brand))) + Self::default().with_layout(navbar::Layout::BrandLeft(Typed::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(Embed::with(brand))) + Self::default().with_layout(navbar::Layout::BrandRight(Typed::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(Embed::with(oc))) + Self::default().with_layout(navbar::Layout::Offcanvas(Typed::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( - Embed::with(brand), - Embed::with(oc), + Typed::with(brand), + Typed::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( - Embed::with(brand), - Embed::with(oc), + Typed::with(brand), + Typed::with(oc), )) } @@ -255,21 +255,17 @@ impl Navbar { self } - /// 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(), - /// ])); - /// ``` + /// 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`]. #[builder_fn] - pub fn with_item(mut self, op: impl Into) -> Self { - self.items.alter_child(op.into()); + pub fn with_items(mut self, op: TypedOp) -> Self { + self.items.alter_typed(op); self } } diff --git a/extensions/pagetop-bootsier/src/theme/navbar/item.rs b/extensions/pagetop-bootsier/src/theme/navbar/item.rs index caba4e7d..28e74cae 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, Clone, Debug)] +#[derive(AutoDefault, 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(Embed), + Brand(Typed), /// Representa un menú de navegación [`Nav`](crate::theme::Nav). - Nav(Embed