✨ (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
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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue