✨ (pagetop): Añade puntos de corte a Flex/FlexItem
- `Breakpoint` gana variante `Xxxl` y métodos `name()`/`min_width()`/ `resolved()`, para consultarse contra el tema activo. - `Theme::breakpoint_entry()` sustituye a `breakpoint_min_width()`: devuelve un `BreakpointEntry` con nombre y ancho mínimo. - Nuevo `Responsive<T>`, valor en cascada mobile-first por punto de corte. - Ejemplo `examples/intro-responsive.rs` con los patrones de uso.
This commit is contained in:
parent
8577ca8a59
commit
0b8f3f3000
22 changed files with 1257 additions and 359 deletions
|
|
@ -142,13 +142,14 @@ impl ResponsiveStyles {
|
|||
/// Renderiza las declaraciones acumuladas como texto CSS.
|
||||
///
|
||||
/// Emite primero las declaraciones sin punto de corte (`None`), siempre sin envoltorio. Luego
|
||||
/// recorre los puntos de corte reales en orden *mobile-first* (`Xs` a `Xxl`, omitiendo los que
|
||||
/// recorre los puntos de corte reales en orden *mobile-first* (`Xs` a `Xxxl`, omitiendo los que
|
||||
/// no tengan ninguna declaración) y, para cada uno, agrupa las reglas de todas sus entradas
|
||||
/// (`.clases { propiedad: valor; ... }`). Si el ancho mínimo resuelto por el tema activo
|
||||
/// (`.clases{propiedad:valor;...}`). Si el ancho mínimo resuelto por el tema activo
|
||||
/// ([`Breakpoint::min_width()`]) es una cadena vacía, las reglas se emiten tal cual, sin punto
|
||||
/// de corte real; en cualquier otro caso se envuelven en `@media (min-width: ...)`.
|
||||
/// de corte real; en cualquier otro caso se envuelven en `@media(min-width:...)`.
|
||||
///
|
||||
/// El resultado no contiene saltos de línea.
|
||||
/// El resultado no contiene espacios ni saltos de línea salvo los que pueda llevar el propio
|
||||
/// valor de una declaración (p. ej. `font-family: "Segoe UI", sans-serif`).
|
||||
pub fn render(&self, cx: &Context) -> Markup {
|
||||
let mut css = String::new();
|
||||
|
||||
|
|
@ -164,11 +165,11 @@ impl ResponsiveStyles {
|
|||
css.push_str(&rules);
|
||||
} else {
|
||||
css.push_str(&util::join!(
|
||||
"@media (min-width: ",
|
||||
"@media(min-width:",
|
||||
min_width,
|
||||
") { ",
|
||||
"){",
|
||||
rules,
|
||||
" }"
|
||||
"}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -176,8 +177,8 @@ impl ResponsiveStyles {
|
|||
html! { (PreEscaped(css)) }
|
||||
}
|
||||
|
||||
// Construye, concatenadas y sin separador, las reglas CSS (`.clases { propiedad: valor; ... }`)
|
||||
// de todas las entradas del punto de corte indicado.
|
||||
// Construye, concatenadas y sin separador, las reglas CSS (`.clases{propiedad:valor;...}`) de
|
||||
// todas las entradas del punto de corte indicado.
|
||||
fn render_rules(&self, breakpoint: Option<Breakpoint>) -> String {
|
||||
let mut rules = String::new();
|
||||
for (_, classes, styles) in self
|
||||
|
|
@ -188,10 +189,10 @@ impl ResponsiveStyles {
|
|||
let selector = classes.replace(' ', ".");
|
||||
let declarations = styles
|
||||
.iter()
|
||||
.map(|(property, value)| util::join!(property.as_ref(), ": ", value.as_ref()))
|
||||
.map(|(property, value)| util::join!(property.as_ref(), ":", value.as_ref()))
|
||||
.collect::<Vec<_>>()
|
||||
.join("; ");
|
||||
rules.push_str(&util::join!(".", selector, " { ", declarations, " }"));
|
||||
.join(";");
|
||||
rules.push_str(&util::join!(".", selector, "{", declarations, "}"));
|
||||
}
|
||||
rules
|
||||
}
|
||||
|
|
|
|||
110
src/html/flex.rs
110
src/html/flex.rs
|
|
@ -5,24 +5,30 @@
|
|||
//! ofrecen su propio `with_flex()`, como [`Container`] o [`Navbar`].
|
||||
//!
|
||||
//! [`FlexItem`] configura, en cambio, un único elemento en relación con el contenedor flex de su
|
||||
//! padre (crecimiento, reducción, alineación individual, orden, ancho y desplazamiento). Al poder
|
||||
//! acabar aplicándose sobre cualquier componente (no sólo los que ofrecen `with_flex()`), no tiene
|
||||
//! un builder propio: se aplica con [`PropsOp::flex_item()`] sobre el `with_prop()` que ya expone
|
||||
//! cualquier componente.
|
||||
//! padre (crecimiento, reducción, alineación individual, orden, ancho y desplazamiento). No tiene
|
||||
//! un builder propio ya que puede acabar aplicándose sobre cualquier componente (no sólo los que
|
||||
//! ofrecen `with_flex()`). Por eso se aplica con [`PropsOp::flex_item()`] sobre el `with_prop()`
|
||||
//! que normalmente ya expone cualquier componente.
|
||||
//!
|
||||
//! # Un entorno autosuficiente
|
||||
//! # Un entorno nativo autosuficiente
|
||||
//!
|
||||
//! Toda la configuración de `Flex`/`FlexItem` se resuelve con estilos en línea (`style="..."`),
|
||||
//! nunca como clases CSS (consulta el propio [`Flex`] para ver el porqué). Los estilos en línea
|
||||
//! tienen la especificidad más alta que existe en CSS, salvo `!important`, así que ningún framework
|
||||
//! CSS de terceros, ni el CSS de la propia aplicación, puede sobrescribirlo por accidente. Funciona
|
||||
//! igual conviva con quien conviva en la misma página, sin necesidad de coordinar nombres de clase
|
||||
//! ni orden alguno en la carga de hojas de estilo.
|
||||
//! Toda la configuración de `Flex`/`FlexItem` se resuelve generando clases CSS dinámicamente y de
|
||||
//! manera independiente a cualquier tema o framework CSS. El nombre interno de cada clase se deriva
|
||||
//! de la propiedad y el valor que representa, así que dos elementos con la misma configuración
|
||||
//! comparten la misma regla en vez de duplicarla. Las declaraciones correspondientes se registran
|
||||
//! vía [`AssetsOp::AddResponsiveStyle`] y se renderizan como reglas en el `<head>` del documento.
|
||||
//! Funciona igual conviva con quien conviva en la misma página, sin necesidad de coordinar nombres
|
||||
//! de clase ni orden alguno en la carga de hojas de estilo.
|
||||
//!
|
||||
//! [Flexbox]: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Flexible_box_layout
|
||||
//! [`AssetsOp::AddResponsiveStyle`]: crate::core::component::AssetsOp::AddResponsiveStyle
|
||||
//! [`PropsOp::flex_item()`]: crate::html::props::PropsOp::flex_item
|
||||
//! [`Container`]: crate::base::component::Container
|
||||
//! [`Navbar`]: crate::base::component::Navbar
|
||||
//! [`PropsOp::flex_item()`]: crate::html::props::PropsOp::flex_item
|
||||
|
||||
use crate::CowStr;
|
||||
use crate::core::component::{AssetsOp, Context, Contextual};
|
||||
use crate::core::theme::BreakpointEntry;
|
||||
|
||||
mod props_container;
|
||||
pub use props_container::{Align, AlignContent, Behavior, ContentJustify, Direction, Gap};
|
||||
|
|
@ -35,3 +41,83 @@ pub use container::Flex;
|
|||
|
||||
mod item;
|
||||
pub use item::FlexItem;
|
||||
|
||||
// **< Flex / FlexItem PRIVATE >********************************************************************
|
||||
|
||||
// Sustituye, en un valor CSS ya resuelto, los únicos caracteres (`.`, `%`) que no podrían usarse
|
||||
// como fragmento de un nombre de clase. Así, `"1.5rem"` sería `"1_5rem"` y `"33.3333%"` quedaría
|
||||
// como `"33_3333pct"`.
|
||||
fn value_to_token(value: &str) -> String {
|
||||
value.replace('.', "_").replace('%', "pct")
|
||||
}
|
||||
|
||||
// Añade un estilo (`property: value`) al punto de corte indicado, y la clase a `classes`, separada
|
||||
// con un espacio de las que ya hubiera. Recibe un `BreakpointEntry` ya resuelto (ver
|
||||
// `Breakpoint::resolved()`) y extrae aquí el `Breakpoint` que `AddResponsiveStyle` necesita, que
|
||||
// puede ser nulo si aplica siempre.
|
||||
//
|
||||
// La clase se copia al acumulador y se mueve al `AssetsOp`, sin clonarla. Recibirla como `CowStr`
|
||||
// permite además que las clases fijas, las que no dependen de ningún punto de corte, lleguen como
|
||||
// `&'static str` sin asignar memoria.
|
||||
fn styles(
|
||||
cx: &mut Context,
|
||||
classes: &mut String,
|
||||
entry: Option<BreakpointEntry>,
|
||||
class: CowStr,
|
||||
property: &'static str,
|
||||
value: CowStr,
|
||||
) {
|
||||
if !classes.is_empty() {
|
||||
classes.push(' ');
|
||||
}
|
||||
classes.push_str(&class);
|
||||
|
||||
cx.alter_assets(AssetsOp::AddResponsiveStyle(
|
||||
entry.map(|e| e.breakpoint),
|
||||
class,
|
||||
property.into(),
|
||||
value,
|
||||
));
|
||||
}
|
||||
|
||||
// Nombre de clase según el punto de corte: `prefix` ya incluye el guion bajo final antes del valor
|
||||
// (p. ej. `"_flex-direction_"`), y `entry` añade su sufijo si aplica (`"_flex-direction_row_md_"`),
|
||||
// ya resuelto para el tema activo (ver `Breakpoint::resolved()`).
|
||||
macro_rules! responsive_class {
|
||||
($prefix:expr, $token:expr, $entry:expr) => {
|
||||
match $entry {
|
||||
None => util::join!($prefix, $token, "_"),
|
||||
Some(entry) => util::join!($prefix, $token, "_", entry.name, "_"),
|
||||
}
|
||||
};
|
||||
}
|
||||
use responsive_class;
|
||||
|
||||
// Recorre las entradas para una propiedad `Responsive<T>` cuyo valor CSS es un único `T::value()`,
|
||||
// generando y registrando (vía `styles()`) una clase por punto de corte con valor.
|
||||
//
|
||||
// La forma con el marcador final `val` es para propiedades cuyo valor puede contener `.`/`%` (como
|
||||
// `ItemSize` o `ItemOffset` en `FlexItem`) y necesitan pasar por `value_to_token()`.
|
||||
macro_rules! apply {
|
||||
($cx:expr, $classes:expr, $field:expr, $prefix:literal, $property:literal) => {
|
||||
for (bp, value) in $field.by_breakpoint() {
|
||||
let value = value.value();
|
||||
if !value.is_empty() {
|
||||
let entry = bp.resolved($cx);
|
||||
let class = responsive_class!($prefix, value, entry);
|
||||
styles($cx, $classes, entry, class.into(), $property, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
($cx:expr, $classes:expr, $field:expr, $prefix:literal, $property:literal, val) => {
|
||||
for (bp, value) in $field.by_breakpoint() {
|
||||
let value = value.value();
|
||||
if !value.is_empty() {
|
||||
let entry = bp.resolved($cx);
|
||||
let class = responsive_class!($prefix, value_to_token(&value), entry);
|
||||
styles($cx, $classes, entry, class.into(), $property, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
use apply;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,39 @@
|
|||
use crate::html::flex::props_container::{
|
||||
Align, AlignContent, Behavior, ContentJustify, Direction, Gap,
|
||||
};
|
||||
use crate::html::props::{Props, PropsOp};
|
||||
use crate::{AutoDefault, Getters, builder_impl};
|
||||
use crate::core::component::Context;
|
||||
use crate::core::theme::{Breakpoint, Responsive};
|
||||
use crate::html::flex::{Align, AlignContent, Behavior, ContentJustify, Direction, Gap};
|
||||
use crate::{AutoDefault, Getters, builder_impl, util};
|
||||
|
||||
// **< DisplayFlex >********************************************************************************
|
||||
|
||||
// Modo de activación del posicionamiento Flexbox de un contenedor `Flex`. Detalle interno de
|
||||
// implementación: la API pública sólo expone los constructores `Flex::new()`, `Flex::at()`,
|
||||
// `Flex::inline()` e `Flex::inline_at()`, nunca esta variante directamente.
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||
enum DisplayFlex {
|
||||
#[default]
|
||||
Always,
|
||||
AlwaysInline,
|
||||
At(Breakpoint),
|
||||
InlineAt(Breakpoint),
|
||||
}
|
||||
|
||||
// **< Flex >***************************************************************************************
|
||||
|
||||
/// Configuración para el posicionamiento Flexbox en un contenedor.
|
||||
///
|
||||
/// Se resuelve con estilos en línea (`display`, `flex-direction`, `flex-wrap`, `justify-content`,
|
||||
/// `align-items`, `align-content`, `gap`), nunca como clases CSS. Son propiedades estándar que no
|
||||
/// requieren interpretación por parte de los temas, siempre funcionan igual, sin una sola
|
||||
/// línea de CSS ni de código específico.
|
||||
/// Se resuelve como clases CSS generadas dinámicamente (`display`, `flex-direction`, `flex-wrap`,
|
||||
/// `justify-content`, `align-items`, `align-content`, `gap`), registradas vía
|
||||
/// [`AssetsOp::AddResponsiveStyle`] en [`ResponsiveStyles`] y renderizadas como reglas en el
|
||||
/// `<head>` del documento. Son propiedades nativas que no requieren interpretación por parte de los
|
||||
/// temas, siempre funcionan igual, sin una sola línea de CSS ni de código específico.
|
||||
///
|
||||
/// Esto tiene además una consecuencia práctica; los estilos en línea tienen la especificidad más
|
||||
/// alta que existe en CSS, salvo `!important`. Ningún *framework* CSS de terceros, ni el CSS de la
|
||||
/// propia aplicación, puede sobrescribir por accidente lo que `Flex` aplica. Es un mecanismo
|
||||
/// autosuficiente que funciona igual conviva con quien conviva en la misma página, sin coordinar
|
||||
/// nombres de clase ni orden de carga de hojas de estilo con nadie.
|
||||
/// El nombre de cada clase se deriva de la propiedad y el valor que representa (por ejemplo
|
||||
/// `_flex-direction_row_`), así que dos contenedores con la misma configuración comparten la misma
|
||||
/// regla generada en vez de duplicarla, y el nombre generado no coincide por accidente con clases
|
||||
/// de terceros.
|
||||
///
|
||||
/// [`AssetsOp::AddResponsiveStyle`]: crate::core::component::AssetsOp::AddResponsiveStyle
|
||||
/// [`ResponsiveStyles`]: crate::html::ResponsiveStyles
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
|
|
@ -26,7 +42,7 @@ use crate::{AutoDefault, Getters, builder_impl};
|
|||
///
|
||||
/// let actions = Container::new()
|
||||
/// .with_flex(
|
||||
/// Flex::row()
|
||||
/// Flex::new()
|
||||
/// .with_justify(flex::ContentJustify::End)
|
||||
/// .with_align(flex::Align::Center)
|
||||
/// .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))),
|
||||
|
|
@ -36,38 +52,66 @@ use crate::{AutoDefault, Getters, builder_impl};
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq, Getters)]
|
||||
pub struct Flex {
|
||||
/// Devuelve la dirección del eje principal.
|
||||
// Determina si esta configuración debe aplicarse (y con qué variante de `display`) o si
|
||||
// `Flex` no está en absoluto configurado. `None` es el estado real de ausencia: lo que tiene
|
||||
// un contenedor que nunca ha llamado a `with_flex()`. Sin getter público; `new()`, `at()`,
|
||||
// `inline()` e `inline_at()` son la única forma de activarlo.
|
||||
#[getters(skip)]
|
||||
display: Option<DisplayFlex>,
|
||||
/// Devuelve la dirección del eje principal por punto de corte.
|
||||
#[getters(copy)]
|
||||
direction: Direction,
|
||||
/// Devuelve el comportamiento cuando los elementos no caben en una sola línea.
|
||||
direction: Responsive<Direction>,
|
||||
/// Devuelve el comportamiento cuando los elementos no caben en una sola línea, por punto de
|
||||
/// corte.
|
||||
#[getters(copy)]
|
||||
wrap: Behavior,
|
||||
/// Devuelve la alineación de los elementos en el eje principal.
|
||||
wrap: Responsive<Behavior>,
|
||||
/// Devuelve la alineación de los elementos en el eje principal, por punto de corte.
|
||||
#[getters(copy)]
|
||||
justify: ContentJustify,
|
||||
/// Devuelve la alineación de los elementos en el eje transversal.
|
||||
justify: Responsive<ContentJustify>,
|
||||
/// Devuelve la alineación de los elementos en el eje transversal, por punto de corte.
|
||||
#[getters(copy)]
|
||||
align: Align,
|
||||
/// Devuelve la alineación de las líneas cuando hay más de una.
|
||||
align: Responsive<Align>,
|
||||
/// Devuelve la alineación de las líneas cuando hay más de una, por punto de corte.
|
||||
#[getters(copy)]
|
||||
align_content: AlignContent,
|
||||
/// Devuelve el espaciado entre elementos.
|
||||
align_content: Responsive<AlignContent>,
|
||||
/// Devuelve el espaciado entre elementos, por punto de corte.
|
||||
#[getters(copy)]
|
||||
gap: Gap,
|
||||
gap: Responsive<Gap>,
|
||||
}
|
||||
|
||||
#[builder_impl]
|
||||
impl Flex {
|
||||
/// Crea una configuración Flex para disponer los elementos en fila (comportamiento por
|
||||
/// defecto).
|
||||
pub fn row() -> Self {
|
||||
Self::default()
|
||||
/// Define una configuración Flex con `display: flex`, sin punto de corte: se aplica siempre.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
display: Some(DisplayFlex::Always),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea una configuración Flex para disponer los elementos en columna.
|
||||
pub fn column() -> Self {
|
||||
/// Define una configuración Flex con `display: flex` que se aplica a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn at(bp: Breakpoint) -> Self {
|
||||
Self {
|
||||
direction: Direction::Column,
|
||||
display: Some(DisplayFlex::At(bp)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Define una configuración Flex con `display: inline-flex`, sin punto de corte: se aplica
|
||||
/// siempre.
|
||||
pub fn inline() -> Self {
|
||||
Self {
|
||||
display: Some(DisplayFlex::AlwaysInline),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Define una configuración Flex con `display: inline-flex` que se aplica a partir del punto de
|
||||
/// corte indicado.
|
||||
pub fn inline_at(bp: Breakpoint) -> Self {
|
||||
Self {
|
||||
display: Some(DisplayFlex::InlineAt(bp)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -75,43 +119,106 @@ impl Flex {
|
|||
// **< Flex BUILDER >***************************************************************************
|
||||
|
||||
/// Establece la dirección del eje principal.
|
||||
pub fn with_direction(mut self, direction: Direction) -> Self {
|
||||
self.direction = direction;
|
||||
pub fn with_direction(mut self, dir: Direction) -> Self {
|
||||
self.direction = self.direction.set(dir);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la dirección del eje principal a partir del punto de corte indicado.
|
||||
pub fn with_direction_at(mut self, bp: Breakpoint, dir: Direction) -> Self {
|
||||
self.direction = self.direction.set_at(bp, dir);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el comportamiento cuando los elementos no caben en una sola línea.
|
||||
pub fn with_wrap(mut self, wrap: Behavior) -> Self {
|
||||
self.wrap = wrap;
|
||||
self.wrap = self.wrap.set(wrap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el comportamiento cuando los elementos no caben en una sola línea, a partir del
|
||||
/// punto de corte indicado.
|
||||
pub fn with_wrap_at(mut self, bp: Breakpoint, wrap: Behavior) -> Self {
|
||||
self.wrap = self.wrap.set_at(bp, wrap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de los elementos en el eje principal.
|
||||
pub fn with_justify(mut self, justify: ContentJustify) -> Self {
|
||||
self.justify = justify;
|
||||
self.justify = self.justify.set(justify);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de los elementos en el eje principal, a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn with_justify_at(mut self, bp: Breakpoint, justify: ContentJustify) -> Self {
|
||||
self.justify = self.justify.set_at(bp, justify);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de los elementos en el eje transversal.
|
||||
pub fn with_align(mut self, align: Align) -> Self {
|
||||
self.align = align;
|
||||
self.align = self.align.set(align);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de los elementos en el eje transversal, a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn with_align_at(mut self, bp: Breakpoint, align: Align) -> Self {
|
||||
self.align = self.align.set_at(bp, align);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de las líneas cuando hay más de una (ver [`AlignContent`]).
|
||||
pub fn with_align_content(mut self, align_content: AlignContent) -> Self {
|
||||
self.align_content = align_content;
|
||||
self.align_content = self.align_content.set(align_content);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de las líneas cuando hay más de una (ver [`AlignContent`]), a partir
|
||||
/// del punto de corte indicado.
|
||||
pub fn with_align_content_at(mut self, bp: Breakpoint, align_content: AlignContent) -> Self {
|
||||
self.align_content = self.align_content.set_at(bp, align_content);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el espaciado entre elementos.
|
||||
pub fn with_gap(mut self, gap: Gap) -> Self {
|
||||
self.gap = gap;
|
||||
self.gap = self.gap.set(gap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el espaciado entre elementos, a partir del punto de corte indicado.
|
||||
pub fn with_gap_at(mut self, bp: Breakpoint, gap: Gap) -> Self {
|
||||
self.gap = self.gap.set_at(bp, gap);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Flex {
|
||||
/// Combina esta configuración con otra `Flex`, campo a campo, o la resetea a los valores por
|
||||
/// defecto si se pasa `None`.
|
||||
///
|
||||
/// Cada campo de `flex` que tenga un valor sustituye al correspondiente de `self`; los que
|
||||
/// estén a `None` dejan intacto el valor ya presente en `self`. Así, sucesivas llamadas pueden
|
||||
/// ir completando o sobrescribiendo campos concretos sin necesidad de repetir los ya
|
||||
/// establecidos. Es el método recomendado para que un contenedor propio adopte `Flex` de forma
|
||||
/// incremental (ver [`Container::with_flex()`](crate::base::component::Container::with_flex)
|
||||
/// como referencia de uso).
|
||||
pub fn merge(mut self, flex: impl Into<Option<Flex>>) -> Self {
|
||||
let Some(flex) = flex.into() else {
|
||||
return Flex::default();
|
||||
};
|
||||
self.display = flex.display.or(self.display);
|
||||
self.direction = self.direction.merge(flex.direction);
|
||||
self.wrap = self.wrap.merge(flex.wrap);
|
||||
self.justify = self.justify.merge(flex.justify);
|
||||
self.align = self.align.merge(flex.align);
|
||||
self.align_content = self.align_content.merge(flex.align_content);
|
||||
self.gap = self.gap.merge(flex.gap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Aplica esta configuración a un [`Props`] como declaraciones de estilo en línea.
|
||||
///
|
||||
/// Es el método recomendado para que un componente adopte `Flex`: concentra en un único sitio
|
||||
|
|
@ -119,19 +226,53 @@ impl Flex {
|
|||
/// use. Precedente: [`Container`](crate::base::component::Container) lo aplica sobre su
|
||||
/// propio `Props`; [`Navbar`](crate::base::component::Navbar), sobre el `Props` de su área de
|
||||
/// contenido.
|
||||
pub fn apply_to(self, props: &mut Props) {
|
||||
props.alter_prop(PropsOp::add_style("display", "flex"));
|
||||
for (property, value) in [
|
||||
("flex-direction", self.direction.value()),
|
||||
("flex-wrap", self.wrap.value()),
|
||||
("justify-content", self.justify.value()),
|
||||
("align-items", self.align.value()),
|
||||
("align-content", self.align_content.value()),
|
||||
] {
|
||||
props.alter_prop(PropsOp::add_style(property, value));
|
||||
}
|
||||
for (property, value) in self.gap.styles() {
|
||||
props.alter_prop(PropsOp::add_style(property, value));
|
||||
///
|
||||
/// Las clases generadas se añaden a `classes`, separadas con un espacio de las que ya hubiera,
|
||||
/// para poder compartir un único acumulador con [`FlexItem::apply()`](super::FlexItem::apply)
|
||||
/// sin cadenas intermedias.
|
||||
#[rustfmt::skip]
|
||||
pub(crate) fn apply(self, cx: &mut Context, classes: &mut String) {
|
||||
// Sin `display` no hay contenedor Flex: el resto de facetas (`flex-direction`, `gap`...)
|
||||
// no tienen ningún efecto en CSS sin `display: flex`/`inline-flex`, así que ni se generan.
|
||||
let Some(display) = self.display else {
|
||||
return;
|
||||
};
|
||||
|
||||
use super::{apply, responsive_class, styles, value_to_token};
|
||||
|
||||
let (prefix, value) = match display {
|
||||
DisplayFlex::Always
|
||||
| DisplayFlex::At(_) => ("_flex_", "flex"),
|
||||
DisplayFlex::AlwaysInline
|
||||
| DisplayFlex::InlineAt(_) => ("_inline-flex_", "inline-flex"),
|
||||
};
|
||||
let entry = match display {
|
||||
DisplayFlex::At(bp) | DisplayFlex::InlineAt(bp) => bp.resolved(cx),
|
||||
_ => None,
|
||||
};
|
||||
let class = match entry {
|
||||
None => prefix.into(),
|
||||
Some(entry) => util::join!(prefix, entry.name, "_").into(),
|
||||
};
|
||||
styles(cx, classes, entry, class, "display", value.into());
|
||||
|
||||
apply!(cx, classes, self.direction, "_flex-direction_", "flex-direction");
|
||||
apply!(cx, classes, self.wrap, "_flex-wrap_", "flex-wrap");
|
||||
apply!(cx, classes, self.justify, "_flex-justify_", "justify-content");
|
||||
apply!(cx, classes, self.align, "_flex-align-items_", "align-items");
|
||||
apply!(cx, classes, self.align_content, "_flex-align-content_", "align-content");
|
||||
for (bp, gap) in self.gap.by_breakpoint() {
|
||||
let entry = bp.resolved(cx);
|
||||
for (property, value) in gap.styles().into_iter().flatten() {
|
||||
// El prefijo de `gap` no es literal (depende de la propiedad), así que se compone
|
||||
// aquí en un único `join!` en vez de pasar por `responsive_class!`.
|
||||
let token = value_to_token(&value);
|
||||
let class = match entry {
|
||||
None => util::join!("_flex-", property, "_", token, "_"),
|
||||
Some(e) => util::join!("_flex-", property, "_", token, "_", e.name, "_"),
|
||||
};
|
||||
styles(cx, classes, entry, class.into(), property, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
use crate::html::flex::props_item::{
|
||||
ItemAlign, ItemGrow, ItemOffset, ItemOrder, ItemShrink, ItemSize,
|
||||
};
|
||||
use crate::html::props::{Props, PropsOp};
|
||||
use crate::{AutoDefault, Getters, builder_impl};
|
||||
|
||||
// **< FlexItem >***********************************************************************************
|
||||
use crate::core::component::Context;
|
||||
use crate::core::theme::{Breakpoint, Responsive};
|
||||
use crate::html::PropsOp;
|
||||
use crate::html::flex::{ItemAlign, ItemGrow, ItemOffset, ItemOrder, ItemShrink, ItemSize};
|
||||
use crate::{AutoDefault, Getters, builder_impl, util};
|
||||
|
||||
/// Configuración de un elemento como ítem de un contenedor Flexbox.
|
||||
///
|
||||
/// A diferencia de [`Flex`](crate::html::flex::Flex), que configura el comportamiento Flexbox
|
||||
/// global de un contenedor y sus hijos como grupo, `FlexItem` configura un único elemento en
|
||||
/// relación con el contenedor flex padre: crecimiento ([`ItemGrow`]), reducción ([`ItemShrink`]),
|
||||
/// alineación individual ([`ItemAlign`]), orden visual ([`ItemOrder`]), ancho ([`ItemSize`]) y
|
||||
/// alineación individual ([`ItemAlign`]), orden visual ([`ItemOrder`]), tamaño ([`ItemSize`]) y
|
||||
/// desplazamiento ([`ItemOffset`]).
|
||||
///
|
||||
/// No tiene un builder dedicado en ningún componente. De hecho, no tendría sentido porque cualquier
|
||||
|
|
@ -20,7 +18,8 @@ use crate::{AutoDefault, Getters, builder_impl};
|
|||
/// exponer cualquier componente.
|
||||
///
|
||||
/// Con [`ItemSize`] y [`ItemOffset`] se pueden modelar rejillas de columnas fijas sobre Flexbox,
|
||||
/// combinando un ancho en fracción del contenedor con un desplazamiento lateral cuando se necesite.
|
||||
/// combinando un tamaño en fracción del contenedor con un desplazamiento lateral cuando se
|
||||
/// necesite.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
|
|
@ -28,7 +27,7 @@ use crate::{AutoDefault, Getters, builder_impl};
|
|||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// // Crece para ocupar el espacio sobrante, partiendo de ancho cero.
|
||||
/// let title = Button::plain(Lc::n("Panel")).with_prop(PropsOp::flex_item(
|
||||
/// let panel = Button::plain(Lc::n("Panel")).with_prop(PropsOp::flex_item(
|
||||
/// FlexItem::new()
|
||||
/// .with_grow(flex::ItemGrow::Is1)
|
||||
/// .with_size(flex::ItemSize::Custom(UnitValue::Zero)),
|
||||
|
|
@ -43,24 +42,24 @@ use crate::{AutoDefault, Getters, builder_impl};
|
|||
/// ```
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq, Getters)]
|
||||
pub struct FlexItem {
|
||||
/// Devuelve el factor de crecimiento.
|
||||
/// Devuelve el factor de crecimiento, por punto de corte.
|
||||
#[getters(copy)]
|
||||
grow: ItemGrow,
|
||||
/// Devuelve el factor de reducción.
|
||||
grow: Responsive<ItemGrow>,
|
||||
/// Devuelve el factor de reducción, por punto de corte.
|
||||
#[getters(copy)]
|
||||
shrink: ItemShrink,
|
||||
/// Devuelve la alineación individual en el eje transversal.
|
||||
shrink: Responsive<ItemShrink>,
|
||||
/// Devuelve la alineación individual en el eje transversal, por punto de corte.
|
||||
#[getters(copy)]
|
||||
align_self: ItemAlign,
|
||||
/// Devuelve la posición en el orden visual.
|
||||
align_self: Responsive<ItemAlign>,
|
||||
/// Devuelve la posición en el orden visual, por punto de corte.
|
||||
#[getters(copy)]
|
||||
order: ItemOrder,
|
||||
/// Devuelve el ancho como fracción del contenedor.
|
||||
order: Responsive<ItemOrder>,
|
||||
/// Devuelve el tamaño como fracción del contenedor, por punto de corte.
|
||||
#[getters(copy)]
|
||||
size: ItemSize,
|
||||
/// Devuelve el desplazamiento respecto al inicio del contenedor.
|
||||
size: Responsive<ItemSize>,
|
||||
/// Devuelve el desplazamiento respecto al inicio del contenedor, por punto de corte.
|
||||
#[getters(copy)]
|
||||
offset: ItemOffset,
|
||||
offset: Responsive<ItemOffset>,
|
||||
}
|
||||
|
||||
#[builder_impl]
|
||||
|
|
@ -70,80 +69,19 @@ impl FlexItem {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
// **< FlexItem BUILDER >***********************************************************************
|
||||
|
||||
/// Establece el factor de crecimiento.
|
||||
pub fn with_grow(mut self, grow: ItemGrow) -> Self {
|
||||
self.grow = grow;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el factor de reducción.
|
||||
pub fn with_shrink(mut self, shrink: ItemShrink) -> Self {
|
||||
self.shrink = shrink;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación individual en el eje transversal.
|
||||
pub fn with_align_self(mut self, align_self: ItemAlign) -> Self {
|
||||
self.align_self = align_self;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la posición en el orden visual.
|
||||
pub fn with_order(mut self, order: ItemOrder) -> Self {
|
||||
self.order = order;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el ancho como una fracción del contenedor (`flex-basis`). No fuerza
|
||||
/// [`ItemShrink::Is0`](super::ItemShrink::Is0) por sí solo (consulta la documentación de
|
||||
/// [`ItemSize`] antes de combinarlo con [`with_shrink()`](Self::with_shrink) porque con un
|
||||
/// tamaño en porcentaje, forzar `ItemShrink::Is0` sólo es seguro si el contenedor no tiene
|
||||
/// [`Gap`](super::Gap)).
|
||||
pub fn with_size(mut self, size: ItemSize) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el desplazamiento respecto al inicio del contenedor (`margin-inline-start`). No
|
||||
/// tiene relación con [`push_end()`](Self::push_end) aunque aplican la misma propiedad CSS para
|
||||
/// casos de uso distintos.
|
||||
pub fn with_offset(mut self, offset: ItemOffset) -> Self {
|
||||
self.offset = offset;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl FlexItem {
|
||||
// Aplica esta configuración a un Props como declaraciones de estilo en línea.
|
||||
pub(crate) fn apply_to(self, props: &mut Props) {
|
||||
for (property, value) in [
|
||||
("flex-grow", self.grow.value()),
|
||||
("flex-shrink", self.shrink.value()),
|
||||
("align-self", self.align_self.value()),
|
||||
("order", self.order.value()),
|
||||
("flex-basis", self.size.value()),
|
||||
("margin-inline-start", self.offset.value()),
|
||||
] {
|
||||
props.alter_prop(PropsOp::add_style(property, value));
|
||||
}
|
||||
}
|
||||
|
||||
/// Separa un elemento (y los que le sigan en el mismo eje principal) del resto, empujándolo
|
||||
/// hacia el extremo final de un contenedor flex.
|
||||
/// Crea una configuración de ítem que empuja el elemento, y los que le sigan, hacia el extremo
|
||||
/// final de un contenedor flex en fila.
|
||||
///
|
||||
/// Se resuelve siempre como margen inicial automático (`margin-inline-start: auto`) en línea,
|
||||
/// igual que el resto de facetas de `FlexItem`. Es el mecanismo estándar de Flexbox para, por
|
||||
/// ejemplo, separar dos menús dentro de una misma [`Navbar`](crate::base::component::Navbar)
|
||||
/// -- uno pegado al inicio, el siguiente empujado al final -- sin que el contenedor necesite
|
||||
/// conocer ninguna distinción entre sus elementos.
|
||||
/// Aplica `margin-inline-start: auto`, un margen automático que absorbe todo el espacio libre
|
||||
/// que quede antes del elemento en el eje de escritura. Con la dirección por defecto
|
||||
/// ([`Direction::Row`](super::Direction::Row)) ese eje es el principal, de ahí el efecto de
|
||||
/// empuje. En un contenedor en columna, en cambio, ese eje es el transversal: el margen ya no
|
||||
/// empuja nada, sólo desplaza ese elemento hacia el final de la línea (a la derecha si se
|
||||
/// escribe de izquierda a derecha).
|
||||
///
|
||||
/// No forma parte de los campos de `FlexItem` (no se combina con `grow`/`shrink`/`align_self`/
|
||||
/// `order`/`size`/`offset` en una misma llamada): es una función asociada independiente porque
|
||||
/// resuelve un caso de uso completo por sí sola, con una sola línea, y vive aquí -- en vez de
|
||||
/// como función suelta del módulo `flex` -- para dejar claro que es una operación de **ítem**,
|
||||
/// no de contenedor.
|
||||
/// Es el mecanismo estándar de Flexbox para, por ejemplo, separar dos menús dentro de un mismo
|
||||
/// [`Navbar`](crate::base::component::Navbar) (uno pegado al inicio, el siguiente empujado al
|
||||
/// final) sin que el contenedor necesite conocer ninguna distinción entre sus elementos.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
|
|
@ -151,12 +89,139 @@ impl FlexItem {
|
|||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let user_menu = Nav::new()
|
||||
/// .with_prop(FlexItem::push_end())
|
||||
/// .with_prop(FlexItem::push_end().into())
|
||||
/// .with_item(nav::Item::link(Lc::n("Profile"), "/profile"))
|
||||
/// .with_item(nav::Item::link(Lc::n("Sign out"), "/sign-out"));
|
||||
/// ```
|
||||
pub fn push_end() -> PropsOp {
|
||||
PropsOp::add_style("margin-inline-start", "auto")
|
||||
pub fn push_end() -> Self {
|
||||
Self::new().with_offset(ItemOffset::Auto)
|
||||
}
|
||||
|
||||
// **< FlexItem BUILDER >***********************************************************************
|
||||
|
||||
/// Establece el factor de crecimiento.
|
||||
pub fn with_grow(mut self, grow: ItemGrow) -> Self {
|
||||
self.grow = self.grow.set(grow);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el factor de crecimiento, a partir del punto de corte indicado.
|
||||
pub fn with_grow_at(mut self, bp: Breakpoint, grow: ItemGrow) -> Self {
|
||||
self.grow = self.grow.set_at(bp, grow);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el factor de reducción.
|
||||
pub fn with_shrink(mut self, shrink: ItemShrink) -> Self {
|
||||
self.shrink = self.shrink.set(shrink);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el factor de reducción, a partir del punto de corte indicado.
|
||||
pub fn with_shrink_at(mut self, bp: Breakpoint, shrink: ItemShrink) -> Self {
|
||||
self.shrink = self.shrink.set_at(bp, shrink);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación individual en el eje transversal.
|
||||
pub fn with_align_self(mut self, align_self: ItemAlign) -> Self {
|
||||
self.align_self = self.align_self.set(align_self);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación individual en el eje transversal, a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn with_align_self_at(mut self, bp: Breakpoint, align_self: ItemAlign) -> Self {
|
||||
self.align_self = self.align_self.set_at(bp, align_self);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la posición en el orden visual.
|
||||
pub fn with_order(mut self, order: ItemOrder) -> Self {
|
||||
self.order = self.order.set(order);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la posición en el orden visual, a partir del punto de corte indicado.
|
||||
pub fn with_order_at(mut self, bp: Breakpoint, order: ItemOrder) -> Self {
|
||||
self.order = self.order.set_at(bp, order);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el tamaño como una fracción del contenedor (`flex-basis`). No fuerza
|
||||
/// [`ItemShrink::Is0`](super::ItemShrink::Is0) por sí solo (consulta la documentación de
|
||||
/// [`ItemSize`] antes de combinarlo con [`with_shrink()`](Self::with_shrink) porque con un
|
||||
/// tamaño en porcentaje, forzar `ItemShrink::Is0` sólo es seguro si el contenedor no tiene
|
||||
/// [`Gap`](super::Gap)).
|
||||
pub fn with_size(mut self, size: ItemSize) -> Self {
|
||||
self.size = self.size.set(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el tamaño como una fracción del contenedor (`flex-basis`), a partir del punto de
|
||||
/// corte indicado.
|
||||
pub fn with_size_at(mut self, bp: Breakpoint, size: ItemSize) -> Self {
|
||||
self.size = self.size.set_at(bp, size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el desplazamiento respecto al inicio del contenedor (`margin-inline-start`).
|
||||
/// [`push_end()`](Self::push_end) fija este mismo campo a [`ItemOffset::Auto`]; combinar los
|
||||
/// dos deja el que se aplique en último lugar.
|
||||
pub fn with_offset(mut self, offset: ItemOffset) -> Self {
|
||||
self.offset = self.offset.set(offset);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el desplazamiento respecto al inicio del contenedor (`margin-inline-start`), a
|
||||
/// partir del punto de corte indicado.
|
||||
pub fn with_offset_at(mut self, bp: Breakpoint, offset: ItemOffset) -> Self {
|
||||
self.offset = self.offset.set_at(bp, offset);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl FlexItem {
|
||||
/// Combina esta configuración con otra `FlexItem`, campo a campo.
|
||||
///
|
||||
/// La fusión llega al nivel de cada punto de corte; donde `item` tenga un valor establecido,
|
||||
/// sustituye al de `self` y donde no lo tenga, se conserva el que ya hubiera. Por eso un `item`
|
||||
/// que sólo establezca `with_size_at(Breakpoint::Lg, ...)` no borra el `with_size()` base que
|
||||
/// `self` ya tuviera, sólo sustituye la entrada de ese punto de corte.
|
||||
///
|
||||
/// Es el método que usa [`Props::with_prop()`](crate::html::props::Props::with_prop) para que
|
||||
/// sucesivas [`PropsOp::FlexItem`](crate::html::props::PropsOp::FlexItem) sobre el mismo
|
||||
/// componente vayan completando campos concretos sin repetir los ya establecidos, en vez de
|
||||
/// partir de cero en cada llamada.
|
||||
pub fn merge(mut self, item: FlexItem) -> Self {
|
||||
self.grow = self.grow.merge(item.grow);
|
||||
self.shrink = self.shrink.merge(item.shrink);
|
||||
self.align_self = self.align_self.merge(item.align_self);
|
||||
self.order = self.order.merge(item.order);
|
||||
self.size = self.size.merge(item.size);
|
||||
self.offset = self.offset.merge(item.offset);
|
||||
self
|
||||
}
|
||||
|
||||
/// Aplica esta configuración como clases de utilidad responsive en el [`Context`], igual que
|
||||
/// [`Flex::apply()`](super::Flex::apply): cada faceta con valor añade una declaración de
|
||||
/// estilo (por punto de corte, si se ha establecido alguno) y su propia clase. Un campo sin
|
||||
/// ningún valor establecido, o con un valor cuya variante es la "por defecto" del propio enum
|
||||
/// (p. ej. `ItemGrow::Default`), no añade nada.
|
||||
///
|
||||
/// Las clases generadas se añaden a `classes`, separadas con un espacio de las que ya hubiera,
|
||||
/// para poder compartir un único acumulador con [`Flex::apply()`](super::Flex::apply) sin
|
||||
/// cadenas intermedias.
|
||||
#[rustfmt::skip]
|
||||
pub(crate) fn apply(self, cx: &mut Context, classes: &mut String) {
|
||||
use super::{apply, responsive_class, styles, value_to_token};
|
||||
|
||||
apply!(cx, classes, self.grow, "_flex-item-grow_", "flex-grow");
|
||||
apply!(cx, classes, self.shrink, "_flex-item-shrink_", "flex-shrink");
|
||||
apply!(cx, classes, self.align_self, "_flex-item-align_", "align-self");
|
||||
apply!(cx, classes, self.order, "_flex-item-order_", "order");
|
||||
apply!(cx, classes, self.size, "_flex-item-basis_", "flex-basis", val);
|
||||
apply!(cx, classes, self.offset, "_flex-item-offset_", "margin-inline-start", val);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -207,28 +207,21 @@ pub enum Gap {
|
|||
}
|
||||
|
||||
impl Gap {
|
||||
// Declaraciones de estilo (propiedad, valor) para este espaciado; vacío si no hay ninguna
|
||||
// medible (`UnitValue::None`/`UnitValue::Auto` no producen ningún estilo).
|
||||
pub(super) fn styles(self) -> Vec<(&'static str, CowStr)> {
|
||||
// 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(super) fn styles(self) -> [Option<(&'static str, CowStr)>; 2] {
|
||||
match self {
|
||||
Self::None => Vec::new(),
|
||||
Self::Both(value) => {
|
||||
if value.is_measurable() {
|
||||
vec![("gap", value.into())]
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
Self::Distinct { row, column } => {
|
||||
let mut styles = Vec::new();
|
||||
if row.is_measurable() {
|
||||
styles.push(("row-gap", row.into()));
|
||||
}
|
||||
if column.is_measurable() {
|
||||
styles.push(("column-gap", column.into()));
|
||||
}
|
||||
styles
|
||||
}
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,10 @@ pub enum ItemOffset {
|
|||
/// Por defecto, sin desplazamiento (`margin-inline-start: 0` no explícito).
|
||||
#[default]
|
||||
None,
|
||||
/// Empuja el ítem, y los que le sigan, hacia el extremo final de un contenedor en fila
|
||||
/// (`margin-inline-start: auto`). Ver [`FlexItem::push_end()`](super::FlexItem::push_end), que
|
||||
/// detalla el comportamiento cuando el contenedor está en columna.
|
||||
Auto,
|
||||
/// Se desplaza el 10% del ancho del contenedor (`margin-inline-start: 10%`).
|
||||
Percent10,
|
||||
/// Se desplaza el 20% del ancho del contenedor (`margin-inline-start: 20%`).
|
||||
|
|
@ -106,6 +110,7 @@ impl ItemOffset {
|
|||
pub(super) fn value(self) -> CowStr {
|
||||
match self {
|
||||
Self::None => "".into(),
|
||||
Self::Auto => "auto".into(),
|
||||
Self::Percent10 => "10%".into(),
|
||||
Self::Percent20 => "20%".into(),
|
||||
Self::Percent25 => "25%".into(),
|
||||
|
|
@ -217,10 +222,14 @@ impl ItemShrink {
|
|||
|
||||
// **< ItemSize >***********************************************************************************
|
||||
|
||||
/// Ancho en [`FlexItem`](super::FlexItem) para un ítem como fracción del contenedor.
|
||||
/// Tamaño en [`FlexItem`](super::FlexItem) para un ítem como fracción del contenedor.
|
||||
///
|
||||
/// Dimensiona el eje principal (`flex-basis`). Con la dirección por defecto
|
||||
/// ([`Direction::Row`](super::Direction::Row)) fija el ancho, y en un contenedor en columna fija el
|
||||
/// alto. El resto de esta documentación describe el caso en fila, que es el habitual.
|
||||
///
|
||||
/// Permite maquetar rejillas de columnas fijas. Un ítem con [`ItemSize::Percent33`] ocupa un tercio
|
||||
/// del ancho del contenedor con independencia de su contenido.
|
||||
/// del contenedor con independencia de su contenido.
|
||||
///
|
||||
/// # Cómo combinarlo con `Gap`
|
||||
///
|
||||
|
|
@ -253,31 +262,31 @@ pub enum ItemSize {
|
|||
/// Por defecto, el tamaño se calcula según el contenido (`flex-basis: auto` no explícito).
|
||||
#[default]
|
||||
Default,
|
||||
/// Ocupa el 10% del ancho del contenedor (`flex-basis: 10%`).
|
||||
/// Ocupa el 10% del contenedor (`flex-basis: 10%`).
|
||||
Percent10,
|
||||
/// Ocupa el 20% del ancho del contenedor (`flex-basis: 20%`).
|
||||
/// Ocupa el 20% del contenedor (`flex-basis: 20%`).
|
||||
Percent20,
|
||||
/// Ocupa el 25% del ancho del contenedor (`flex-basis: 25%`).
|
||||
/// Ocupa el 25% del contenedor (`flex-basis: 25%`).
|
||||
Percent25,
|
||||
/// Ocupa un tercio del ancho del contenedor (`flex-basis: 33.3333%`).
|
||||
/// Ocupa un tercio del contenedor (`flex-basis: 33.3333%`).
|
||||
Percent33,
|
||||
/// Ocupa el 40% del ancho del contenedor (`flex-basis: 40%`).
|
||||
/// Ocupa el 40% del contenedor (`flex-basis: 40%`).
|
||||
Percent40,
|
||||
/// Ocupa la mitad del ancho del contenedor (`flex-basis: 50%`).
|
||||
/// Ocupa la mitad del contenedor (`flex-basis: 50%`).
|
||||
Percent50,
|
||||
/// Ocupa el 60% del ancho del contenedor (`flex-basis: 60%`).
|
||||
/// Ocupa el 60% del contenedor (`flex-basis: 60%`).
|
||||
Percent60,
|
||||
/// Ocupa dos tercios del ancho del contenedor (`flex-basis: 66.6667%`).
|
||||
/// Ocupa dos tercios del contenedor (`flex-basis: 66.6667%`).
|
||||
Percent66,
|
||||
/// Ocupa el 75% del ancho del contenedor (`flex-basis: 75%`).
|
||||
/// Ocupa el 75% del contenedor (`flex-basis: 75%`).
|
||||
Percent75,
|
||||
/// Ocupa el 80% del ancho del contenedor (`flex-basis: 80%`).
|
||||
/// Ocupa el 80% del contenedor (`flex-basis: 80%`).
|
||||
Percent80,
|
||||
/// Ocupa el 90% del ancho del contenedor (`flex-basis: 90%`).
|
||||
/// Ocupa el 90% del contenedor (`flex-basis: 90%`).
|
||||
Percent90,
|
||||
/// Ocupa el 100% del ancho del contenedor (`flex-basis: 100%`).
|
||||
/// Ocupa el 100% del contenedor (`flex-basis: 100%`).
|
||||
Percent100,
|
||||
/// Cualquier otro valor, incluidas unidades absolutas (p. ej. un ancho fijo en píxeles).
|
||||
/// Cualquier otro valor, incluidas unidades absolutas (p. ej. un tamaño fijo en píxeles).
|
||||
Custom(UnitValue),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::core::TypeInfo;
|
||||
use crate::core::component::Context;
|
||||
use crate::html::flex::{Flex, FlexItem};
|
||||
use crate::html::maud::{Escaper, RenderAttrs};
|
||||
use crate::html::props::{PropsError, PropsExtra, PropsOp};
|
||||
use crate::{AutoDefault, CowStr, builder_impl, trace, util};
|
||||
|
|
@ -188,6 +189,7 @@ pub struct Props {
|
|||
styles: Vec<(CowStr, CowStr)>,
|
||||
attrs: Vec<(CowStr, CowStr)>,
|
||||
extras: HashMap<&'static str, PropsExtra>,
|
||||
flex_item: FlexItem,
|
||||
}
|
||||
|
||||
#[builder_impl]
|
||||
|
|
@ -335,7 +337,7 @@ impl Props {
|
|||
self.extras.remove(key);
|
||||
}
|
||||
PropsOp::FlexItem(placement) => {
|
||||
placement.apply_to(self);
|
||||
self.flex_item = self.flex_item.merge(placement);
|
||||
}
|
||||
}
|
||||
self
|
||||
|
|
@ -578,9 +580,11 @@ impl Props {
|
|||
/// [`FlexItem`]: crate::html::flex::FlexItem
|
||||
/// [`unpack_with_flex()`]: Self::unpack_with_flex
|
||||
pub fn unpack<'a>(&'a self, cx: &mut Context) -> impl RenderAttrs + 'a {
|
||||
let mut classes = String::new();
|
||||
self.flex_item.apply(cx, &mut classes);
|
||||
PropsUnpack {
|
||||
props: self,
|
||||
classes: self.flex_item.apply(cx),
|
||||
classes,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -597,9 +601,12 @@ impl Props {
|
|||
/// [`FlexItem`]: crate::html::flex::FlexItem
|
||||
/// [`PropsOp::FlexItem`]: crate::html::props::PropsOp::FlexItem
|
||||
pub fn unpack_with_flex<'a>(&'a self, cx: &mut Context, flex: Flex) -> impl RenderAttrs + 'a {
|
||||
let mut classes = String::new();
|
||||
flex.apply(cx, &mut classes);
|
||||
self.flex_item.apply(cx, &mut classes);
|
||||
PropsUnpack {
|
||||
props: self,
|
||||
classes: util::join_pair!(flex.apply(cx), " ", self.flex_item.apply(cx)),
|
||||
classes,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue