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 como clases CSS generadas dinámicamente (`display`, `flex-direction`, `flex-wrap`,
/// `justify-content`, `align-items`, `align-content`, `gap`), registradas vía
/// [`AssetsOp::add_responsive_style()`] en [`ResponsiveStyles`] y renderizadas como reglas en el
/// `
` 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.
///
/// 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::add_responsive_style()`]: crate::core::component::AssetsOp::add_responsive_style
/// [`ResponsiveStyles`]: crate::html::ResponsiveStyles
///
/// # Ejemplo
///
/// ```rust,no_run
/// use pagetop::prelude::*;
///
/// let actions = Container::new()
/// .with_flex(
/// Flex::new()
/// .with_justify(flex::ContentJustify::End)
/// .with_align(flex::Align::Center)
/// .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))),
/// )
/// .with_child(Button::submit(Lc::n("Save")))
/// .with_child(Button::plain(Lc::n("Cancel")));
/// ```
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq, Getters)]
pub struct Flex {
// 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,
/// Devuelve la dirección del eje principal por punto de corte.
#[getters(copy)]
direction: Responsive,
/// Devuelve el comportamiento cuando los elementos no caben en una sola línea, por punto de
/// corte.
#[getters(copy)]
wrap: Responsive,
/// Devuelve la alineación de los elementos en el eje principal, por punto de corte.
#[getters(copy)]
justify: Responsive,
/// Devuelve la alineación de los elementos en el eje transversal, por punto de corte.
#[getters(copy)]
align: Responsive,
/// Devuelve la alineación de las líneas cuando hay más de una, por punto de corte.
#[getters(copy)]
align_content: Responsive,
/// Devuelve el espaciado entre elementos, por punto de corte.
#[getters(copy)]
gap: Responsive,
}
#[builder_impl]
impl Flex {
/// 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()
}
}
/// 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 {
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()
}
}
// **< Flex BUILDER >***************************************************************************
/// Establece la dirección del eje principal.
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 = 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 = 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 = 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 = 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 = 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