♻️ (pagetop): Incorpora Context al render de Props

This commit is contained in:
Manuel Cillero 2026-09-05 00:19:39 +02:00
parent a2b3ae2eb8
commit ab1d4f0efb
49 changed files with 212 additions and 133 deletions

View file

@ -27,7 +27,7 @@ pub(crate) async fn render(dialog: &Dialog, cx: &mut Context) -> Result<Markup,
Ok(html! {
div
(dialog.props())
(dialog.props().unpack(cx))
tabindex="-1"
aria-hidden="true"
aria-labelledby=[id_label.as_deref()]

View file

@ -121,7 +121,7 @@ pub(crate) async fn render(
if title.is_empty() {
// Sin título: menú contextual estático, sin botón ni comportamiento de apertura/cierre.
return Ok(html! {
div (dropdown.props()) {
div (dropdown.props().unpack(cx)) {
ul class="dropdown-menu" { (items) }
}
});
@ -173,7 +173,7 @@ pub(crate) async fn render(
};
Ok(html! {
div (dropdown.props()) {
div (dropdown.props().unpack(cx)) {
// Renderizado en modo split (dos botones) o simple (un botón).
@if *dropdown.button_split() {
// Botón principal (acción/etiqueta).

View file

@ -94,7 +94,7 @@ pub(crate) fn render(field: &Field, cx: &mut Context) -> Result<Markup, Componen
};
Ok(html! {
div (field.props()) {
div (field.props().unpack(cx)) {
@if !floating { (label) }
input
type=(field.kind())

View file

@ -80,7 +80,7 @@ pub(crate) fn render(field: &Field, cx: &mut Context) -> Result<Markup, Componen
None => html! {},
};
Ok(html! {
div (field.props()) {
div (field.props().unpack(cx)) {
@if !floating { (label) }
select
id=[select_id.as_deref()]

View file

@ -86,7 +86,7 @@ pub(crate) fn render(field: &Textarea, cx: &mut Context) -> Result<Markup, Compo
None => html! {},
};
Ok(html! {
div (field.props()) {
div (field.props().unpack(cx)) {
@if !floating { (label) }
textarea
id=[textarea_id.as_deref()]

View file

@ -48,7 +48,7 @@ impl Component for Icon {
let has_label = aria_label.is_some();
html! {
i
(self.props())
(self.props().unpack(cx))
role=[has_label.then_some("img")]
aria-label=[aria_label]
aria-hidden=[(!has_label).then_some("true")]
@ -65,7 +65,7 @@ impl Component for Icon {
viewBox=(viewbox)
fill="currentColor"
focusable="false"
(self.props())
(self.props().unpack(cx))
role=[has_label.then_some("img")]
aria-label=[aria_label]
aria-hidden=[(!has_label).then_some("true")]

View file

@ -101,7 +101,7 @@ pub(crate) async fn item_render(item: &Item, cx: &mut Context) -> Result<Markup,
ItemKind::Void => html! {},
ItemKind::Label(label) => html! {
li (item.props()) {
li (item.props().unpack(cx)) {
span class="nav-link disabled" aria-disabled="true" {
(label.using(cx))
}
@ -137,7 +137,7 @@ pub(crate) async fn item_render(item: &Item, cx: &mut Context) -> Result<Markup,
let aria_disabled = (*disabled).then_some("true");
html! {
li (item.props()) {
li (item.props().unpack(cx)) {
a
class=(classes)
href=[href]
@ -153,7 +153,7 @@ pub(crate) async fn item_render(item: &Item, cx: &mut Context) -> Result<Markup,
}
ItemKind::Html(html) => html! {
li (item.props()) {
li (item.props().unpack(cx)) {
(html.render(cx).await)
}
},
@ -170,7 +170,7 @@ pub(crate) async fn item_render(item: &Item, cx: &mut Context) -> Result<Markup,
.unwrap_or_else(|| "Dropdown".to_string())
});
html! {
li (item.props()) {
li (item.props().unpack(cx)) {
a
class="nav-link dropdown-toggle"
data-bs-toggle="dropdown"

View file

@ -261,7 +261,7 @@ pub(crate) async fn render(navbar: &Navbar, cx: &mut Context) -> Result<Markup,
.unwrap_or_else(|_| translate_layout(navbar.layout()));
Ok(html! {
nav (navbar.props()) {
nav (navbar.props().unpack(cx)) {
div class="container-fluid" {
@match layout {
// Barra más sencilla: sólo contenido.

View file

@ -187,7 +187,7 @@ impl Offcanvas {
html! {
div
(self.props())
(self.props().unpack(cx))
tabindex="-1"
data-bs-scroll=[body_scroll]
data-bs-backdrop=[backdrop]

View file

@ -77,8 +77,8 @@ async fn homepage(request: HttpRequest) -> Result<Markup, ErrorPage> {
.with_prop(PropsOp::set(hx::TARGET, "#result"));
Page::new(request)
.with_child(Html::with(move |_| html! {
button (props) { "Say hello" }
.with_child(Html::with(move |cx| html! {
button (props.unpack(cx)) { "Say hello" }
div #result {}
}))
.render().await

View file

@ -23,15 +23,15 @@
//! puedes usar [`Props`](pagetop::html::Props) combinado con las constantes de este módulo:
//!
//! ```rust,no_run
//! use pagetop::prelude::*;
//! use pagetop_htmx::prelude::*;
//!
//! # use pagetop::prelude::*;
//! # use pagetop_htmx::prelude::*;
//! # let cx = Context::default();
//! let props = Props::new(hx::GET, "/api/items")
//! .with_prop(PropsOp::set(hx::TARGET, "#list"))
//! .with_prop(PropsOp::set(hx::SWAP, hx::swap::OUTER_HTML));
//!
//! let markup = html! {
//! button (props) { "Load" }
//! button (props.unpack(&cx)) { "Load" }
//! };
//! ```
//!

View file

@ -78,8 +78,8 @@ async fn homepage(request: HttpRequest) -> Result<Markup, ErrorPage> {
.with_prop(PropsOp::set(hx::TARGET, "#result"));
Page::new(request)
.with_child(Html::with(move |_| html! {
button (props) { "Say hello" }
.with_child(Html::with(move |cx| html! {
button (props.unpack(cx)) { "Say hello" }
div #result {}
}))
.render().await

View file

@ -111,7 +111,7 @@ impl Component for RoleTable {
let new_href = waypoint.append_to(cx.route(format!("{ADMIN_ROLES_PATH}/new")));
Ok(html! {
div (self.props()) {
div (self.props().unpack(cx)) {
div.user-admin-actions {
a href=(new_href) {
(Lc::t("btn-create-role", &LOCALES_USER).using(cx))

View file

@ -87,7 +87,7 @@ impl Component for UserTable {
let new_href = waypoint.append_to(cx.route(format!("{ADMIN_USERS_PATH}/new")));
Ok(html! {
div (self.props()) {
div (self.props().unpack(cx)) {
div.user-admin-actions {
a href=(new_href) {
(Lc::t("btn-create-user", &LOCALES_USER).using(cx))