From dfff12881fa7c13ef4a475287c858a12a01a0e3e Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 13 Sep 2026 23:08:39 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20(pagetop):=20Comparte=20align=20?= =?UTF-8?q?entre=20Flex=20y=20Grid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extrae `Align`/`AlignContent`/`ItemAlign`/`Gap` de Flexbox a un nuevo módulo neutral `html::align` (`Items`/`Content`/`ItemSelf`/`Gap`) para que tambien lo use CSS Grid. --- examples/form-controls.rs | 6 +- examples/intro-flex.rs | 114 +++++++++--------- examples/intro-responsive.rs | 18 +-- examples/intro-spacing.rs | 2 +- src/html.rs | 4 + src/html/align.rs | 214 ++++++++++++++++++++++++++++++++++ tests/component_navbar.rs | 2 +- tests/html_props_flex_item.rs | 4 +- 8 files changed, 291 insertions(+), 73 deletions(-) create mode 100644 src/html/align.rs diff --git a/examples/form-controls.rs b/examples/form-controls.rs index 8bb3112d..e6f8ee29 100644 --- a/examples/form-controls.rs +++ b/examples/form-controls.rs @@ -144,7 +144,7 @@ async fn form_controls(request: HttpRequest) -> Result { .with_flex( Flex::new() .with_wrap(flex::Behavior::Wrap) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child( Button::submit(Lc::t("btn_submit", &LOC)) @@ -264,7 +264,7 @@ async fn form_controls(request: HttpRequest) -> Result { .with_flex( Flex::new() .with_wrap(flex::Behavior::Wrap) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child( Button::submit(Lc::t("btn_submit", &LOC)) @@ -444,7 +444,7 @@ fn form_lists() -> Form { .with_flex( Flex::new() .with_wrap(flex::Behavior::Wrap) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child( Button::submit(Lc::t("btn_submit", &LOC)) diff --git a/examples/intro-flex.rs b/examples/intro-flex.rs index b13e362a..1d2aba41 100644 --- a/examples/intro-flex.rs +++ b/examples/intro-flex.rs @@ -113,7 +113,7 @@ fn justify_block() -> Block { demo_row( Flex::new() .with_justify(justify) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child(demo_box(flex_item("1"))) .with_child(demo_box(flex_item("2"))) @@ -126,26 +126,26 @@ fn justify_block() -> Block { fn align_block() -> Block { let mut block = Block::new().with_title(Lc::t("flex_block_title_align", &LOC)); - let align_variants: [(&str, flex::Align, &str); 4] = [ + let align_variants: [(&str, align::Items, &str); 4] = [ ( "flex_title_align_start", - flex::Align::Start, - "Flex::new().with_align(Align::Start)", + align::Items::Start, + "Flex::new().with_align(align::Items::Start)", ), ( "flex_title_align_center", - flex::Align::Center, - "Flex::new().with_align(Align::Center)", + align::Items::Center, + "Flex::new().with_align(align::Items::Center)", ), ( "flex_title_align_end", - flex::Align::End, - "Flex::new().with_align(Align::End)", + align::Items::End, + "Flex::new().with_align(align::Items::End)", ), ( "flex_title_align_stretch", - flex::Align::Stretch, - "Flex::new().with_align(Align::Stretch)", + align::Items::Stretch, + "Flex::new().with_align(align::Items::Stretch)", ), ]; for (title_key, align, code) in align_variants { @@ -155,7 +155,7 @@ fn align_block() -> Block { demo_row( Flex::new() .with_align(align) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child(sized_box(Lc::t("flex_box_tall", &LOC), "2.5rem 1rem")) .with_child(demo_box(Lc::t("flex_box_medium", &LOC))) @@ -165,13 +165,13 @@ fn align_block() -> Block { block .with_child(caption( Lc::t("flex_title_align_baseline", &LOC), - Lc::n("Flex::new().with_align(Align::Baseline)"), + Lc::n("Flex::new().with_align(align::Items::Baseline)"), )) .with_child( demo_row( Flex::new() - .with_align(flex::Align::Baseline) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_align(align::Items::Baseline) + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child( sized_box(Lc::t("flex_box_tall", &LOC), "2.5rem 1rem") @@ -185,26 +185,26 @@ fn align_block() -> Block { fn align_self_block() -> Block { let mut block = Block::new().with_title(Lc::t("flex_block_title_align_self", &LOC)); - let align_self_variants: [(&str, flex::ItemAlign, &str); 4] = [ + let align_self_variants: [(&str, align::ItemSelf, &str); 4] = [ ( "flex_title_align_self_start", - flex::ItemAlign::Start, - "FlexItem::new().with_align_self(flex::ItemAlign::Start)", + align::ItemSelf::Start, + "FlexItem::new().with_align_self(align::ItemSelf::Start)", ), ( "flex_title_align_self_end", - flex::ItemAlign::End, - "FlexItem::new().with_align_self(flex::ItemAlign::End)", + align::ItemSelf::End, + "FlexItem::new().with_align_self(align::ItemSelf::End)", ), ( "flex_title_align_self_center", - flex::ItemAlign::Center, - "FlexItem::new().with_align_self(flex::ItemAlign::Center)", + align::ItemSelf::Center, + "FlexItem::new().with_align_self(align::ItemSelf::Center)", ), ( "flex_title_align_self_stretch", - flex::ItemAlign::Stretch, - "FlexItem::new().with_align_self(flex::ItemAlign::Stretch)", + align::ItemSelf::Stretch, + "FlexItem::new().with_align_self(align::ItemSelf::Stretch)", ), ]; for (title_key, align_self, code) in align_self_variants { @@ -213,8 +213,8 @@ fn align_self_block() -> Block { .with_child( demo_row( Flex::new() - .with_align(flex::Align::Start) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_align(align::Items::Start) + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child(sized_box(Lc::t("flex_box_tall", &LOC), "2.5rem 1rem")) .with_child( @@ -226,13 +226,13 @@ fn align_self_block() -> Block { block .with_child(caption( Lc::t("flex_title_align_self_baseline", &LOC), - Lc::n("FlexItem::new().with_align_self(flex::ItemAlign::Baseline)"), + Lc::n("FlexItem::new().with_align_self(align::ItemSelf::Baseline)"), )) .with_child( demo_row( Flex::new() - .with_align(flex::Align::Start) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_align(align::Items::Start) + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child( sized_box(Lc::t("flex_box_tall", &LOC), "2.5rem 1rem") @@ -240,7 +240,7 @@ fn align_self_block() -> Block { ) .with_child( demo_box(flex_item("1")) - .with_prop(FlexItem::new().with_align_self(flex::ItemAlign::Baseline)), + .with_prop(FlexItem::new().with_align_self(align::ItemSelf::Baseline)), ) .with_child(sized_box(Lc::t("flex_box_tall", &LOC), "2.5rem 1rem")), ) @@ -249,41 +249,41 @@ fn align_self_block() -> Block { fn align_content_block() -> Block { let mut block = Block::new().with_title(Lc::t("flex_block_title_align_content", &LOC)); - let align_content_variants: [(&str, flex::AlignContent, &str); 7] = [ + let align_content_variants: [(&str, align::Content, &str); 7] = [ ( "flex_title_align_content_start", - flex::AlignContent::Start, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::Start)", + align::Content::Start, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::Start)", ), ( "flex_title_align_content_end", - flex::AlignContent::End, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::End)", + align::Content::End, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::End)", ), ( "flex_title_align_content_center", - flex::AlignContent::Center, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::Center)", + align::Content::Center, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::Center)", ), ( "flex_title_align_content_between", - flex::AlignContent::SpaceBetween, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::SpaceBetween)", + align::Content::SpaceBetween, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::SpaceBetween)", ), ( "flex_title_align_content_around", - flex::AlignContent::SpaceAround, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::SpaceAround)", + align::Content::SpaceAround, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::SpaceAround)", ), ( "flex_title_align_content_evenly", - flex::AlignContent::SpaceEvenly, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::SpaceEvenly)", + align::Content::SpaceEvenly, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::SpaceEvenly)", ), ( "flex_title_align_content_stretch", - flex::AlignContent::Stretch, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::Stretch)", + align::Content::Stretch, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::Stretch)", ), ]; for (title_key, align_content, code) in align_content_variants { @@ -291,7 +291,7 @@ fn align_content_block() -> Block { Flex::new() .with_wrap(flex::Behavior::Wrap) .with_align_content(align_content) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_prop(PropsOp::add_style("max-width", "21rem")) .with_prop(PropsOp::add_style("min-height", "11rem")); @@ -316,7 +316,7 @@ fn grow_shrink_block() -> Block { Lc::n("FlexItem::new().with_grow(flex::ItemGrow::Is1)"), )) .with_child( - demo_row(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) .with_child(demo_box(Lc::t("flex_box_fixed", &LOC))) .with_child( demo_box(Lc::t("flex_box_grows", &LOC)) @@ -329,7 +329,7 @@ fn grow_shrink_block() -> Block { Lc::n("FlexItem::new().with_shrink(flex::ItemShrink::Is0)"), )) .with_child( - demo_row(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) .with_prop(PropsOp::add_style("max-width", "31rem")) .with_child( demo_box(flex_item("1")).with_prop(PropsOp::add_style("width", "10.5rem")), @@ -354,7 +354,7 @@ fn other_block() -> Block { Lc::n("FlexItem::push_end()"), )) .with_child( - demo_row(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) .with_child(demo_box(Lc::t("flex_box_start_1", &LOC))) .with_child(demo_box(Lc::t("flex_box_start_2", &LOC))) .with_child(demo_box(Lc::t("flex_box_end", &LOC)).with_prop(FlexItem::push_end())), @@ -363,8 +363,8 @@ fn other_block() -> Block { let mut wrap_row = demo_row( Flex::new() .with_wrap(flex::Behavior::Wrap) - .with_align_content(flex::AlignContent::SpaceBetween) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_align_content(align::Content::SpaceBetween) + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_prop(PropsOp::add_style("max-width", "20rem")) .with_prop(PropsOp::add_style("min-height", "11rem")); @@ -380,7 +380,7 @@ fn other_block() -> Block { Lc::n(concat!( "Flex::new()", ".with_wrap(Behavior::Wrap)", - ".with_align_content(AlignContent::SpaceBetween)", + ".with_align_content(align::Content::SpaceBetween)", )), )) .with_child(wrap_row) @@ -389,7 +389,7 @@ fn other_block() -> Block { Lc::n("FlexItem::new().with_order(ItemOrder::First) / .with_order(ItemOrder::Last)"), )) .with_child( - demo_row(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) .with_child( demo_box(Lc::n("A")) .with_prop(FlexItem::new().with_order(flex::ItemOrder::Last)), @@ -404,7 +404,7 @@ fn other_block() -> Block { ) .with_child(caption( Lc::t("flex_title_gap_none", &LOC), - Lc::n("Flex::new() (Gap::None por defecto)"), + Lc::n("Flex::new() (align::Gap::None por defecto)"), )) .with_child( demo_row(Flex::new()) @@ -414,10 +414,10 @@ fn other_block() -> Block { ) .with_child(caption( Lc::t("flex_title_gap_some", &LOC), - Lc::n("Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(1.5)))"), + Lc::n("Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(1.5)))"), )) .with_child( - demo_row(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(1.5)))) + demo_row(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(1.5)))) .with_child(demo_box(flex_item("1"))) .with_child(demo_box(flex_item("2"))) .with_child(demo_box(flex_item("3"))), @@ -465,8 +465,8 @@ fn other_block() -> Block { .with_child( demo_row( Flex::new() - .with_align(flex::Align::Center) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_align(align::Items::Center) + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child(demo_box(Lc::t("flex_box_file", &LOC))) .with_child(demo_box(Lc::t("flex_box_edit", &LOC))) diff --git a/examples/intro-responsive.rs b/examples/intro-responsive.rs index fd53d4fe..6bcf1b0e 100644 --- a/examples/intro-responsive.rs +++ b/examples/intro-responsive.rs @@ -52,7 +52,7 @@ fn activation_block() -> Block { Lc::n("Flex::at(Breakpoint::Md)"), )) .with_child( - demo_row(Flex::at(Breakpoint::Md).with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::at(Breakpoint::Md).with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) .with_child(demo_box(Lc::t("responsive_box_nav_home", &LOC))) .with_child(demo_box(Lc::t("responsive_box_nav_products", &LOC))) .with_child(demo_box(Lc::t("responsive_box_nav_about", &LOC))) @@ -76,7 +76,7 @@ fn direction_block() -> Block { Flex::new() .with_direction(flex::Direction::Column) .with_direction_at(Breakpoint::Md, flex::Direction::RowReverse) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child(sized_box( Lc::t("responsive_box_image", &LOC), @@ -124,7 +124,7 @@ fn justify_align_block() -> Block { "Flex::new()", ".with_justify(ContentJustify::Center)", ".with_justify_at(Breakpoint::Md, ContentJustify::SpaceBetween)", - ".with_align(Align::Center)", + ".with_align(align::Items::Center)", )), )) .with_child( @@ -132,8 +132,8 @@ fn justify_align_block() -> Block { Flex::new() .with_justify(flex::ContentJustify::Center) .with_justify_at(Breakpoint::Md, flex::ContentJustify::SpaceBetween) - .with_align(flex::Align::Center) - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + .with_align(align::Items::Center) + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child(demo_box(Lc::t("responsive_box_logo", &LOC))) .with_child(demo_box(Lc::t("responsive_box_menu", &LOC))), @@ -151,7 +151,7 @@ fn order_block() -> Block { )), )) .with_child( - demo_row(Flex::at(Breakpoint::Lg).with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::at(Breakpoint::Lg).with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) .with_child(demo_box(Lc::t("responsive_box_content", &LOC))) .with_child(demo_box(Lc::t("responsive_box_sidebar", &LOC)).with_prop( FlexItem::new().with_order_at(Breakpoint::Lg, flex::ItemOrder::First), @@ -166,7 +166,7 @@ fn gap_grow_block() -> Block { Lc::t("responsive_title_gap_grow", &LOC), Lc::n(concat!( "Flex::new()", - ".with_gap(Gap::Both(RelRem(0.5)))", + ".with_gap(align::Gap::Both(RelRem(0.5)))", ".with_gap_at(Breakpoint::Md, Gap::Both(RelRem(1.5)))", " + FlexItem::new().with_grow_at(Breakpoint::Md, ItemGrow::Is1)", )), @@ -174,8 +174,8 @@ fn gap_grow_block() -> Block { .with_child( demo_row( Flex::new() - .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))) - .with_gap_at(Breakpoint::Md, flex::Gap::Both(UnitValue::RelRem(1.5))), + .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))) + .with_gap_at(Breakpoint::Md, align::Gap::Both(UnitValue::RelRem(1.5))), ) .with_child(demo_box(Lc::t("responsive_box_file", &LOC))) .with_child(demo_box(Lc::t("responsive_box_edit", &LOC))) diff --git a/examples/intro-spacing.rs b/examples/intro-spacing.rs index 75b9e24b..cb5b9f4f 100644 --- a/examples/intro-spacing.rs +++ b/examples/intro-spacing.rs @@ -66,7 +66,7 @@ fn padding_block() -> Block { block = block .with_child(caption(Lc::t(title_key, &LOC), Lc::n(code))) .with_child( - demo_row(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) .with_child(demo_box(box_sample()).with_prop(Padding::new().with_all(size))), ); } diff --git a/src/html.rs b/src/html.rs index 80881899..8cc433cb 100644 --- a/src/html.rs +++ b/src/html.rs @@ -37,8 +37,12 @@ pub use unit::UnitValue; // **< HTML LAYOUT >******************************************************************************** +// Mecanismo interno compartido por `flex` y `grid` para resolver clases CSS nativas *responsive*. mod responsive; +// Vocabulario de alineación compartido por `flex` y `grid`. +pub mod align; + pub mod flex; #[doc(inline)] pub use flex::{Flex, FlexItem}; diff --git a/src/html/align.rs b/src/html/align.rs new file mode 100644 index 00000000..e1da0945 --- /dev/null +++ b/src/html/align.rs @@ -0,0 +1,214 @@ +//! Definiciones de alineación compartidas por [`Flex`] y [`Grid`]. +//! +//! Reúne las definiciones CSS que Flexbox y CSS Grid resuelven de forma idéntica, con el mismo +//! nombre de propiedad y mismo catálogo de valores. La propia especificación CSS no los considera +//! una característica exclusiva de ninguno de los dos modos, sino del [CSS Box Alignment Module] +//! que ambos comparten: +//! +//! - [`align::Items`] (`align-items`), el contenedor alinea los elementos en el eje transversal +//! (Flex) o en el eje de filas (Grid). +//! - [`align::Content`] (`align-content`), el contenedor alinea las líneas (Flex) o las pistas +//! (Grid), si sobra espacio en ese eje. +//! - [`align::Gap`] (`gap`/`row-gap`/`column-gap`), define el espaciado entre elementos (Flex) o +//! entre pistas (Grid). +//! - [`align::ItemSelf`] (`align-self`), aplicado en el propio ítem para sobrescribir la alineación +//! del contenedor. +//! +//! # Lo que no vive aquí +//! +//! `justify-items`/`justify-self` no tienen equivalente en Flexbox. Sólo hay un eje principal y uno +//! transversal, intercambiables con [`Direction`], no dos ejes independientes como en Grid. +//! `justify-content` comparte nombre de propiedad en ambos modos, pero su catálogo de valores es +//! distinto porque Grid admite `stretch` y Flex no. Los tres tienen su propio tipo en su propio +//! módulo (ver la documentación de [`grid`](crate::html::grid) para el detalle completo de qué se +//! comparte y qué no). +//! +//! [CSS Box Alignment Module]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_box_alignment +//! [`Flex`]: crate::html::Flex +//! [`Grid`]: crate::html::Grid +//! [`align::Items`]: crate::html::align::Items +//! [`align::Content`]: crate::html::align::Content +//! [`align::ItemSelf`]: crate::html::align::ItemSelf +//! [`align::Gap`]: crate::html::align::Gap +//! [`Direction`]: crate::html::flex::Direction + +use crate::html::unit::UnitValue; +use crate::{AutoDefault, CowStr}; + +// **< Items >************************************************************************************** + +/// Alinea los elementos en un contenedor [`Flex`] (eje transversal) o [`Grid`] (eje de filas). +/// +/// [`Flex`]: crate::html::Flex +/// [`Grid`]: crate::html::Grid +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum Items { + /// Por defecto (`align-items: normal` no explícito), mismo efecto que [`Items::Stretch`], salvo + /// que el elemento tenga su propio tamaño. + #[default] + Default, + /// Alinea los elementos al inicio del eje (`align-items: flex-start`). + Start, + /// Alinea los elementos al final del eje (`align-items: flex-end`). + End, + /// Centra los elementos en el eje (`align-items: center`). + Center, + /// Alinea los elementos por su línea base de texto (`align-items: baseline`). + Baseline, + /// Estira los elementos para ocupar todo el eje (`align-items: stretch`). + Stretch, +} + +impl Items { + // Devuelve el valor CSS de `align-items`, o "" para el valor por defecto. + pub(crate) fn value(self) -> CowStr { + match self { + Self::Default => "".into(), + Self::Start => "flex-start".into(), + Self::End => "flex-end".into(), + Self::Center => "center".into(), + Self::Baseline => "baseline".into(), + Self::Stretch => "stretch".into(), + } + } +} + +// **< Content >************************************************************************************ + +/// Alinea varias líneas (en un contenedor [`Flex`]) o varias pistas (en un contenedor [`Grid`]). +/// +/// En Flex sólo tiene efecto si el contenedor usa [`Behavior::Wrap`] o [`Behavior::WrapReverse`] y +/// genera más de una línea de elementos. +/// +/// En Grid, en cambio, se aplica en el eje de filas, cuando sobra espacio en ese eje y siempre que +/// la suma de las pistas de fila sea menor que la altura del contenedor, sin depender de ningún +/// ajuste de línea. +/// +/// [`Flex`]: crate::html::Flex +/// [`Grid`]: crate::html::Grid +/// [`Behavior::Wrap`]: crate::html::flex::Behavior::Wrap +/// [`Behavior::WrapReverse`]: crate::html::flex::Behavior::WrapReverse +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum Content { + /// Por defecto (`align-content: normal` no explícito), como en [`Content::Stretch`], las líneas + /// o pistas se estiran para ocupar el espacio sobrante, sin efecto visible si no hay ningún + /// espacio sobrante que repartir (p. ej. una altura `auto` ajustada al contenido). + #[default] + Default, + /// Alinea al inicio del eje (`align-content: flex-start`). + Start, + /// Alinea al final del eje (`align-content: flex-end`). + End, + /// Centra en el eje (`align-content: center`). + Center, + /// Reparte el espacio sobrante entre las líneas o pistas (`align-content: space-between`). + SpaceBetween, + /// Reparte el espacio sobrante alrededor de cada línea o pista (`align-content: space-around`). + SpaceAround, + /// Reparte el espacio sobrante en partes iguales, incluidos los extremos + /// (`align-content: space-evenly`). + SpaceEvenly, + /// Estira las líneas o pistas para ocupar todo el eje (`align-content: stretch`). + Stretch, +} + +impl Content { + // Devuelve el valor CSS de `align-content`, o "" para el valor por defecto. + pub(crate) fn value(self) -> CowStr { + match self { + Self::Default => "".into(), + Self::Start => "flex-start".into(), + Self::End => "flex-end".into(), + Self::Center => "center".into(), + Self::SpaceBetween => "space-between".into(), + Self::SpaceAround => "space-around".into(), + Self::SpaceEvenly => "space-evenly".into(), + Self::Stretch => "stretch".into(), + } + } +} + +// **< Gap >**************************************************************************************** + +/// Espaciado entre los elementos ([`Flex`]) o entre las pistas ([`Grid`]) de un contenedor. +/// +/// Se resuelve como estilo `gap`/`row-gap`/`column-gap` según aplique. +/// +/// En Flex, si se va a combinar con un [`ItemSize`] porcentual sobre los hijos, la sección "Cómo +/// combinarlo con `Gap`" de [`ItemSize`] explica cómo evitar que el hueco desborde el contenedor. +/// Esa combinación no aplica a Grid, donde el reparto de espacio entre pistas no tiene el mismo +/// problema. +/// +/// [`Flex`]: crate::html::Flex +/// [`Grid`]: crate::html::Grid +/// [`ItemSize`]: crate::html::flex::ItemSize +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum Gap { + /// Por defecto, no hay espaciado (`gap: normal` no explícito). + #[default] + None, + /// Mismo espaciado entre filas y columnas. + Both(UnitValue), + /// Espaciado distinto entre filas y columnas. + Distinct { row: UnitValue, column: UnitValue }, +} + +impl Gap { + // Declaraciones de estilo (propiedad, valor) para este espaciado; cada hueco a `None` si no hay + // ninguna medible (`UnitValue::None`/`UnitValue::Auto` no producen ningún estilo). + pub(crate) fn styles(self) -> [Option<(&'static str, CowStr)>; 2] { + match self { + Self::None => [None, None], + Self::Both(value) => [Self::style("gap", value), None], + Self::Distinct { row, column } => [ + Self::style("row-gap", row), + Self::style("column-gap", column), + ], + } + } + + // Declaración (propiedad, valor) para un valor medible, o `None` si no lo es. + fn style(property: &'static str, value: UnitValue) -> Option<(&'static str, CowStr)> { + value.is_measurable().then(|| (property, value.into())) + } +} + +// **< ItemSelf >*********************************************************************************** + +/// Alineación individual de [`FlexItem`] (eje transversal) o [`GridItem`] (eje de filas). +/// +/// Sobrescribe la alineación del contenedor con el valor de `align-self`. En Grid, `flex-start`/ +/// `flex-end` equivalen a `start`/`end`. +/// +/// [`FlexItem`]: crate::html::flex::FlexItem +/// [`GridItem`]: crate::html::grid::GridItem +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum ItemSelf { + /// Por defecto, hereda la alineación del contenedor (`align-self: auto` no explícito). + #[default] + Default, + /// Alinea el ítem al inicio del eje (`align-self: flex-start`). + Start, + /// Alinea el ítem al final del eje (`align-self: flex-end`). + End, + /// Centra el ítem en el eje (`align-self: center`). + Center, + /// Alinea el ítem por su línea base de texto (`align-self: baseline`). + Baseline, + /// Estira el ítem para ocupar todo el eje (`align-self: stretch`). + Stretch, +} + +impl ItemSelf { + // Devuelve el valor CSS de `align-self`, o "" para el valor por defecto. + pub(crate) fn value(self) -> CowStr { + match self { + Self::Default => "".into(), + Self::Start => "flex-start".into(), + Self::End => "flex-end".into(), + Self::Center => "center".into(), + Self::Baseline => "baseline".into(), + Self::Stretch => "stretch".into(), + } + } +} diff --git a/tests/component_navbar.rs b/tests/component_navbar.rs index 5a680828..be0d6e6d 100644 --- a/tests/component_navbar.rs +++ b/tests/component_navbar.rs @@ -55,7 +55,7 @@ async fn flex_adds_its_styles_to_the_content_area() { async fn flex_gap_adds_a_style_to_the_content_area() { let mut cx = Context::default(); let mut navbar = Navbar::simple() - .with_flex(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) + .with_flex(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) .with_item(navbar::Item::nav(one_link_nav())); let html = navbar.render(&mut cx).await.into_string(); let assets = cx.render_assets().into_string(); diff --git a/tests/html_props_flex_item.rs b/tests/html_props_flex_item.rs index a05a51d6..e0556fff 100644 --- a/tests/html_props_flex_item.rs +++ b/tests/html_props_flex_item.rs @@ -36,7 +36,7 @@ async fn shrink_adds_flex_shrink_style() { async fn align_self_adds_matching_style() { let mut cx = Context::default(); let props = - Props::default().with_prop(FlexItem::new().with_align_self(flex::ItemAlign::Center)); + Props::default().with_prop(FlexItem::new().with_align_self(align::ItemSelf::Center)); let html = html! { span (props.unpack(&mut cx)) {} }.into_string(); let assets = cx.render_assets().into_string(); @@ -109,7 +109,7 @@ async fn combines_several_facets_in_one_call() { FlexItem::new() .with_grow(flex::ItemGrow::Is1) .with_shrink(flex::ItemShrink::Is0) - .with_align_self(flex::ItemAlign::Start) + .with_align_self(align::ItemSelf::Start) .with_order(flex::ItemOrder::Is2) .with_size(flex::ItemSize::Custom(UnitValue::Zero)) .with_offset(flex::ItemOffset::Percent10),