Skip to main content

hyperchad_transformer/
lib.rs

1//! Core UI transformation system with container models, layout calculations, and HTML generation.
2//!
3//! This crate provides a comprehensive UI transformation system for building modern user interfaces.
4//! It includes a flexible container model, layout calculation engine, HTML generation, and tree
5//! traversal utilities.
6//!
7//! # Features
8//!
9//! * **Container System** - Comprehensive UI container model with styling and layout properties
10//! * **Layout Engine** - Advanced layout calculation with flexbox and grid support (via `layout` feature)
11//! * **HTML Generation** - Complete HTML rendering with CSS generation (via `html` feature)
12//! * **Calculation System** - CSS `calc()` expressions with viewport units (vw, vh, dvw, dvh)
13//! * **Element Types** - Full HTML element support including semantic elements, forms, and media
14//! * **Responsive Design** - Conditional styling and responsive breakpoints via override system
15//! * **Tree Traversal** - Efficient container tree navigation and manipulation
16//!
17//! # Example
18//!
19//! ```rust
20//! use hyperchad_transformer::{Container, Element, Number};
21//! use hyperchad_transformer::models::LayoutDirection;
22//! use hyperchad_color::Color;
23//!
24//! // Create a basic container with styling
25//! let container = Container {
26//!     element: Element::Div,
27//!     width: Some(Number::from(300)),
28//!     height: Some(Number::from(200)),
29//!     background: Some(Color::from_hex("#f0f0f0")),
30//!     direction: LayoutDirection::Column,
31//!     padding_left: Some(Number::from(20)),
32//!     padding_right: Some(Number::from(20)),
33//!     ..Default::default()
34//! };
35//!
36//! // Generate HTML representation
37//! let html = container.to_string();
38//! ```
39//!
40//! # Main Entry Points
41//!
42//! * [`Container`] - The core UI container struct with styling and layout properties
43//! * [`Element`] - Enum representing different HTML element types
44//! * [`Number`] - Numeric values with unit support (px, %, vw, vh, calc expressions)
45//! * [`Calculation`] - Arithmetic expressions for CSS `calc()` support
46//!
47//! # Re-exports
48//!
49//! * [`actions`] - Action system for interactive behaviors (re-exported from `hyperchad_actions`)
50//! * [`models`] - Layout and styling model types (re-exported from `hyperchad_transformer_models`)
51
52#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
53#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
54#![allow(clippy::multiple_crate_versions)]
55
56use std::{any::Any, collections::BTreeMap, io::Write};
57
58use switchy_env::var;
59
60use hyperchad_actions::Action;
61use hyperchad_color::Color;
62use hyperchad_transformer_models::{
63    AlignItems, Cursor, FontWeight, ImageFit, ImageLoading, JustifyContent, LayoutDirection,
64    LayoutOverflow, LinkTarget, OverflowWrap, Position, Route, TextAlign, TextDecorationLine,
65    TextDecorationStyle, TextOverflow, UserSelect, Visibility, WhiteSpace,
66};
67use parse::parse_number;
68use serde::{Deserialize, Serialize, de::Error};
69use serde_json::Value;
70
71pub use hyperchad_actions as actions;
72pub use hyperchad_transformer_models as models;
73use strum::{EnumDiscriminants, EnumIter};
74
75#[cfg(test)]
76/// Arbitrary value generation for property-based testing with proptest.
77pub mod arb;
78#[cfg(any(test, feature = "html"))]
79/// HTML parsing and generation utilities (requires `html` feature).
80pub mod html;
81#[cfg(feature = "layout")]
82/// Layout calculation engine for UI containers (requires `layout` feature).
83pub mod layout;
84/// Parsing utilities for numeric values and CSS calculation expressions.
85pub mod parse;
86
87/// Represents a calculation expression that can be evaluated with context.
88///
89/// Supports arithmetic operations, grouping, and min/max functions. Calculations can
90/// contain dynamic values that depend on container size or viewport dimensions.
91#[derive(Clone, Debug, PartialEq, EnumDiscriminants, Serialize, Deserialize)]
92#[strum_discriminants(derive(EnumIter))]
93#[strum_discriminants(name(CalculationType))]
94#[strum_discriminants(vis(pub(crate)))]
95pub enum Calculation {
96    /// A numeric value.
97    Number(Box<Number>),
98    /// Addition of two calculations.
99    Add(Box<Self>, Box<Self>),
100    /// Subtraction of two calculations.
101    Subtract(Box<Self>, Box<Self>),
102    /// Multiplication of two calculations.
103    Multiply(Box<Self>, Box<Self>),
104    /// Division of two calculations.
105    Divide(Box<Self>, Box<Self>),
106    /// Grouped calculation for precedence control.
107    Grouping(Box<Self>),
108    /// Minimum of two calculations.
109    Min(Box<Self>, Box<Self>),
110    /// Maximum of two calculations.
111    Max(Box<Self>, Box<Self>),
112}
113
114impl Calculation {
115    fn calc(&self, container: f32, view_width: f32, view_height: f32) -> f32 {
116        match self {
117            Self::Number(number) => number.calc(container, view_width, view_height),
118            Self::Add(left, right) => {
119                left.calc(container, view_width, view_height)
120                    + right.calc(container, view_width, view_height)
121            }
122            Self::Subtract(left, right) => {
123                left.calc(container, view_width, view_height)
124                    - right.calc(container, view_width, view_height)
125            }
126            Self::Multiply(left, right) => {
127                left.calc(container, view_width, view_height)
128                    * right.calc(container, view_width, view_height)
129            }
130            Self::Divide(left, right) => {
131                left.calc(container, view_width, view_height)
132                    / right.calc(container, view_width, view_height)
133            }
134            Self::Grouping(value) => value.calc(container, view_width, view_height),
135            Self::Min(left, right) => {
136                let a = left.calc(container, view_width, view_height);
137                let b = right.calc(container, view_width, view_height);
138                if a > b { b } else { a }
139            }
140            Self::Max(left, right) => {
141                let a = left.calc(container, view_width, view_height);
142                let b = right.calc(container, view_width, view_height);
143                if a > b { a } else { b }
144            }
145        }
146    }
147
148    /// Returns a reference to this calculation if it contains dynamic values.
149    ///
150    /// Dynamic values are those that depend on container size (percentages).
151    #[must_use]
152    pub fn as_dynamic(&self) -> Option<&Self> {
153        match self {
154            Self::Number(x) => {
155                if x.is_dynamic() {
156                    Some(self)
157                } else {
158                    None
159                }
160            }
161            Self::Add(a, b)
162            | Self::Subtract(a, b)
163            | Self::Multiply(a, b)
164            | Self::Divide(a, b)
165            | Self::Min(a, b)
166            | Self::Max(a, b) => {
167                if a.is_dynamic() || b.is_dynamic() {
168                    Some(self)
169                } else {
170                    None
171                }
172            }
173            Self::Grouping(x) => {
174                if x.is_dynamic() {
175                    Some(self)
176                } else {
177                    None
178                }
179            }
180        }
181    }
182
183    /// Checks if this calculation contains dynamic values.
184    #[must_use]
185    pub fn is_dynamic(&self) -> bool {
186        self.as_dynamic().is_some()
187    }
188
189    /// Returns a reference to this calculation if it contains only fixed values.
190    ///
191    /// Fixed values are absolute values that don't depend on context.
192    #[must_use]
193    pub fn as_fixed(&self) -> Option<&Self> {
194        match self {
195            Self::Number(x) => {
196                if x.is_fixed() {
197                    Some(self)
198                } else {
199                    None
200                }
201            }
202            Self::Add(a, b)
203            | Self::Subtract(a, b)
204            | Self::Multiply(a, b)
205            | Self::Divide(a, b)
206            | Self::Min(a, b)
207            | Self::Max(a, b) => {
208                if a.is_fixed() && b.is_fixed() {
209                    Some(self)
210                } else {
211                    None
212                }
213            }
214            Self::Grouping(x) => {
215                if x.is_fixed() {
216                    Some(self)
217                } else {
218                    None
219                }
220            }
221        }
222    }
223
224    /// Checks if this calculation contains only fixed values.
225    #[must_use]
226    pub fn is_fixed(&self) -> bool {
227        self.as_fixed().is_some()
228    }
229}
230
231impl std::fmt::Display for Calculation {
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        match self {
234            Self::Number(number) => f.write_str(&number.to_string()),
235            Self::Add(left, right) => f.write_fmt(format_args!("{left} + {right}")),
236            Self::Subtract(left, right) => f.write_fmt(format_args!("{left} - {right}")),
237            Self::Multiply(left, right) => f.write_fmt(format_args!("{left} * {right}")),
238            Self::Divide(left, right) => f.write_fmt(format_args!("{left} / {right}")),
239            Self::Grouping(value) => f.write_fmt(format_args!("({value})")),
240            Self::Min(left, right) => f.write_fmt(format_args!("min({left}, {right})")),
241            Self::Max(left, right) => f.write_fmt(format_args!("max({left}, {right})")),
242        }
243    }
244}
245
246/// Represents a numeric value with optional unit or calculation.
247///
248/// Supports absolute values, percentages, viewport units, and calculated expressions.
249/// Can be evaluated to a concrete value given container and viewport dimensions.
250#[derive(Clone, Debug, EnumDiscriminants)]
251#[strum_discriminants(derive(EnumIter))]
252#[strum_discriminants(name(NumberType))]
253#[strum_discriminants(vis(pub(crate)))]
254pub enum Number {
255    /// Floating-point absolute value.
256    Real(f32),
257    /// Integer absolute value.
258    Integer(i64),
259    /// Floating-point percentage of container.
260    RealPercent(f32),
261    /// Integer percentage of container.
262    IntegerPercent(i64),
263    /// Floating-point dynamic viewport width percentage.
264    RealDvw(f32),
265    /// Integer dynamic viewport width percentage.
266    IntegerDvw(i64),
267    /// Floating-point dynamic viewport height percentage.
268    RealDvh(f32),
269    /// Integer dynamic viewport height percentage.
270    IntegerDvh(i64),
271    /// Floating-point viewport width percentage.
272    RealVw(f32),
273    /// Integer viewport width percentage.
274    IntegerVw(i64),
275    /// Floating-point viewport height percentage.
276    RealVh(f32),
277    /// Integer viewport height percentage.
278    IntegerVh(i64),
279    /// Calculated expression.
280    Calc(Calculation),
281}
282
283impl Serialize for Number {
284    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
285    where
286        S: serde::Serializer,
287    {
288        match self {
289            Self::Real(x) => x.serialize(serializer),
290            Self::Integer(x) => x.serialize(serializer),
291            Self::RealPercent(x) => format!("{x}%").serialize(serializer),
292            Self::IntegerPercent(x) => format!("{x}%").serialize(serializer),
293            Self::RealDvw(x) => format!("{x}dvw").serialize(serializer),
294            Self::IntegerDvw(x) => format!("{x}dvw").serialize(serializer),
295            Self::RealDvh(x) => format!("{x}dvh").serialize(serializer),
296            Self::IntegerDvh(x) => format!("{x}dvh").serialize(serializer),
297            Self::RealVw(x) => format!("{x}vw").serialize(serializer),
298            Self::IntegerVw(x) => format!("{x}vw").serialize(serializer),
299            Self::RealVh(x) => format!("{x}vh").serialize(serializer),
300            Self::IntegerVh(x) => format!("{x}vh").serialize(serializer),
301            Self::Calc(calculation) => format!("calc({calculation})").serialize(serializer),
302        }
303    }
304}
305
306impl<'de> Deserialize<'de> for Number {
307    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
308    where
309        D: serde::Deserializer<'de>,
310    {
311        #[derive(Deserialize)]
312        #[serde(rename = "Number")]
313        enum NumberInner {
314            Real(f32),
315            Integer(i64),
316            RealPercent(f32),
317            IntegerPercent(i64),
318            RealDvw(f32),
319            IntegerDvw(i64),
320            RealDvh(f32),
321            IntegerDvh(i64),
322            RealVw(f32),
323            IntegerVw(i64),
324            RealVh(f32),
325            IntegerVh(i64),
326            Calc(Calculation),
327        }
328
329        impl From<NumberInner> for Number {
330            fn from(value: NumberInner) -> Self {
331                match value {
332                    NumberInner::Real(x) => Self::Real(x),
333                    NumberInner::Integer(x) => Self::Integer(x),
334                    NumberInner::RealPercent(x) => Self::RealPercent(x),
335                    NumberInner::IntegerPercent(x) => Self::IntegerPercent(x),
336                    NumberInner::RealDvw(x) => Self::RealDvw(x),
337                    NumberInner::IntegerDvw(x) => Self::IntegerDvw(x),
338                    NumberInner::RealDvh(x) => Self::RealDvh(x),
339                    NumberInner::IntegerDvh(x) => Self::IntegerDvh(x),
340                    NumberInner::RealVw(x) => Self::RealVw(x),
341                    NumberInner::IntegerVw(x) => Self::IntegerVw(x),
342                    NumberInner::RealVh(x) => Self::RealVh(x),
343                    NumberInner::IntegerVh(x) => Self::IntegerVh(x),
344                    NumberInner::Calc(calculation) => Self::Calc(calculation),
345                }
346            }
347        }
348
349        log::trace!("attempting to deserialize Number");
350        let value: Value = Value::deserialize(deserializer)?;
351        log::trace!("deserialized Number to {value:?}");
352
353        Ok(if value.is_i64() {
354            #[allow(clippy::cast_possible_wrap)]
355            Self::Integer(value.as_i64().unwrap())
356        } else if value.is_u64() {
357            #[allow(clippy::cast_possible_wrap)]
358            Self::Integer(value.as_u64().unwrap() as i64)
359        } else if value.is_f64() {
360            #[allow(clippy::cast_possible_truncation)]
361            Self::Real(value.as_f64().unwrap() as f32)
362        } else if value.is_string() {
363            parse_number(value.as_str().unwrap()).map_err(D::Error::custom)?
364        } else {
365            serde_json::from_value::<NumberInner>(value)
366                .map_err(D::Error::custom)?
367                .into()
368        })
369    }
370}
371
372impl Number {
373    /// Evaluates this number to a concrete pixel value.
374    ///
375    /// Percentages are calculated relative to `container`, viewport units relative to
376    /// `view_width` and `view_height`.
377    #[must_use]
378    pub fn calc(&self, container: f32, view_width: f32, view_height: f32) -> f32 {
379        match self {
380            Self::Real(x) => *x,
381            #[allow(clippy::cast_precision_loss)]
382            Self::Integer(x) => *x as f32,
383            Self::RealPercent(x) => container * (*x / 100.0),
384            #[allow(clippy::cast_precision_loss)]
385            Self::IntegerPercent(x) => container * (*x as f32 / 100.0),
386            Self::RealVw(x) | Self::RealDvw(x) => view_width * (*x / 100.0),
387            #[allow(clippy::cast_precision_loss)]
388            Self::IntegerVw(x) | Self::IntegerDvw(x) => view_width * (*x as f32 / 100.0),
389            Self::RealVh(x) | Self::RealDvh(x) => view_height * (*x / 100.0),
390            #[allow(clippy::cast_precision_loss)]
391            Self::IntegerVh(x) | Self::IntegerDvh(x) => view_height * (*x as f32 / 100.0),
392            Self::Calc(x) => x.calc(container, view_width, view_height),
393        }
394    }
395
396    /// Returns a reference to this number if it is dynamic.
397    ///
398    /// Dynamic numbers depend on container size (percentages).
399    #[must_use]
400    pub fn as_dynamic(&self) -> Option<&Self> {
401        match self {
402            Self::RealPercent(_) | Self::IntegerPercent(_) => Some(self),
403            Self::Real(_)
404            | Self::Integer(_)
405            | Self::RealDvw(_)
406            | Self::IntegerDvw(_)
407            | Self::RealDvh(_)
408            | Self::IntegerDvh(_)
409            | Self::RealVw(_)
410            | Self::IntegerVw(_)
411            | Self::RealVh(_)
412            | Self::IntegerVh(_) => None,
413            Self::Calc(x) => {
414                if x.is_dynamic() {
415                    Some(self)
416                } else {
417                    None
418                }
419            }
420        }
421    }
422
423    /// Checks if this number is dynamic.
424    #[must_use]
425    pub fn is_dynamic(&self) -> bool {
426        self.as_dynamic().is_some()
427    }
428
429    /// Returns a reference to this number if it is fixed.
430    ///
431    /// Fixed numbers don't depend on container size.
432    #[must_use]
433    pub fn as_fixed(&self) -> Option<&Self> {
434        match self {
435            Self::RealPercent(_) | Self::IntegerPercent(_) => None,
436            Self::Real(_)
437            | Self::Integer(_)
438            | Self::RealDvw(_)
439            | Self::IntegerDvw(_)
440            | Self::RealDvh(_)
441            | Self::IntegerDvh(_)
442            | Self::RealVw(_)
443            | Self::IntegerVw(_)
444            | Self::RealVh(_)
445            | Self::IntegerVh(_) => Some(self),
446            Self::Calc(x) => {
447                if x.is_fixed() {
448                    Some(self)
449                } else {
450                    None
451                }
452            }
453        }
454    }
455
456    /// Checks if this number is fixed.
457    #[must_use]
458    pub fn is_fixed(&self) -> bool {
459        self.as_fixed().is_some()
460    }
461}
462
463#[cfg(test)]
464mod test_number_deserialize {
465    use pretty_assertions::assert_eq;
466    use proptest::prelude::*;
467
468    use crate::Number;
469
470    proptest! {
471        #[test]
472        fn can_serialize_then_deserialize(number: Number) {
473            log::trace!("number={number:?}");
474            let serialized = serde_json::to_string(&number).unwrap();
475            log::trace!("serialized={serialized}");
476            let deserialized = serde_json::from_str(&serialized).unwrap();
477            log::trace!("deserialized={deserialized:?}");
478
479            assert_eq!(number, deserialized);
480        }
481    }
482}
483
484#[cfg(test)]
485mod test_number_calc {
486    use crate::{Calculation, Number};
487
488    #[test_log::test]
489    fn number_calc_evaluates_real_value_unchanged() {
490        let num = Number::Real(42.5);
491        let result = num.calc(100.0, 1920.0, 1080.0);
492        assert!((result - 42.5).abs() < f32::EPSILON);
493    }
494
495    #[test_log::test]
496    fn number_calc_evaluates_integer_value_unchanged() {
497        let num = Number::Integer(100);
498        let result = num.calc(100.0, 1920.0, 1080.0);
499        assert!((result - 100.0).abs() < f32::EPSILON);
500    }
501
502    #[test_log::test]
503    fn number_calc_evaluates_real_percent_relative_to_container() {
504        let num = Number::RealPercent(50.0);
505        let result = num.calc(200.0, 1920.0, 1080.0);
506        assert!((result - 100.0).abs() < f32::EPSILON);
507    }
508
509    #[test_log::test]
510    fn number_calc_evaluates_integer_percent_relative_to_container() {
511        let num = Number::IntegerPercent(25);
512        let result = num.calc(400.0, 1920.0, 1080.0);
513        assert!((result - 100.0).abs() < f32::EPSILON);
514    }
515
516    #[test_log::test]
517    fn number_calc_evaluates_real_vw_relative_to_viewport_width() {
518        let num = Number::RealVw(50.0);
519        let result = num.calc(100.0, 1920.0, 1080.0);
520        assert!((result - 960.0).abs() < f32::EPSILON);
521    }
522
523    #[test_log::test]
524    fn number_calc_evaluates_integer_vw_relative_to_viewport_width() {
525        let num = Number::IntegerVw(10);
526        let result = num.calc(100.0, 1920.0, 1080.0);
527        assert!((result - 192.0).abs() < f32::EPSILON);
528    }
529
530    #[test_log::test]
531    fn number_calc_evaluates_real_vh_relative_to_viewport_height() {
532        let num = Number::RealVh(50.0);
533        let result = num.calc(100.0, 1920.0, 1080.0);
534        assert!((result - 540.0).abs() < f32::EPSILON);
535    }
536
537    #[test_log::test]
538    fn number_calc_evaluates_integer_vh_relative_to_viewport_height() {
539        let num = Number::IntegerVh(100);
540        let result = num.calc(100.0, 1920.0, 1080.0);
541        assert!((result - 1080.0).abs() < f32::EPSILON);
542    }
543
544    #[test_log::test]
545    fn number_calc_evaluates_real_dvw_same_as_vw() {
546        let num = Number::RealDvw(50.0);
547        let result = num.calc(100.0, 1920.0, 1080.0);
548        assert!((result - 960.0).abs() < f32::EPSILON);
549    }
550
551    #[test_log::test]
552    fn number_calc_evaluates_integer_dvw_same_as_vw() {
553        let num = Number::IntegerDvw(10);
554        let result = num.calc(100.0, 1920.0, 1080.0);
555        assert!((result - 192.0).abs() < f32::EPSILON);
556    }
557
558    #[test_log::test]
559    fn number_calc_evaluates_real_dvh_same_as_vh() {
560        let num = Number::RealDvh(50.0);
561        let result = num.calc(100.0, 1920.0, 1080.0);
562        assert!((result - 540.0).abs() < f32::EPSILON);
563    }
564
565    #[test_log::test]
566    fn number_calc_evaluates_integer_dvh_same_as_vh() {
567        let num = Number::IntegerDvh(100);
568        let result = num.calc(100.0, 1920.0, 1080.0);
569        assert!((result - 1080.0).abs() < f32::EPSILON);
570    }
571
572    #[test_log::test]
573    fn number_calc_evaluates_calculation_expression() {
574        let num = Number::Calc(Calculation::Add(
575            Box::new(Calculation::Number(Box::new(Number::Integer(10)))),
576            Box::new(Calculation::Number(Box::new(Number::Integer(20)))),
577        ));
578        let result = num.calc(100.0, 1920.0, 1080.0);
579        assert!((result - 30.0).abs() < f32::EPSILON);
580    }
581
582    #[test_log::test]
583    fn number_is_dynamic_returns_true_for_percent_types() {
584        assert!(Number::RealPercent(50.0).is_dynamic());
585        assert!(Number::IntegerPercent(50).is_dynamic());
586    }
587
588    #[test_log::test]
589    fn number_is_dynamic_returns_false_for_fixed_types() {
590        assert!(!Number::Real(50.0).is_dynamic());
591        assert!(!Number::Integer(50).is_dynamic());
592        assert!(!Number::RealVw(50.0).is_dynamic());
593        assert!(!Number::IntegerVw(50).is_dynamic());
594        assert!(!Number::RealVh(50.0).is_dynamic());
595        assert!(!Number::IntegerVh(50).is_dynamic());
596        assert!(!Number::RealDvw(50.0).is_dynamic());
597        assert!(!Number::IntegerDvw(50).is_dynamic());
598        assert!(!Number::RealDvh(50.0).is_dynamic());
599        assert!(!Number::IntegerDvh(50).is_dynamic());
600    }
601
602    #[test_log::test]
603    fn number_is_fixed_returns_true_for_non_percent_types() {
604        assert!(Number::Real(50.0).is_fixed());
605        assert!(Number::Integer(50).is_fixed());
606        assert!(Number::RealVw(50.0).is_fixed());
607        assert!(Number::IntegerVw(50).is_fixed());
608        assert!(Number::RealVh(50.0).is_fixed());
609        assert!(Number::IntegerVh(50).is_fixed());
610    }
611
612    #[test_log::test]
613    fn number_is_fixed_returns_false_for_percent_types() {
614        assert!(!Number::RealPercent(50.0).is_fixed());
615        assert!(!Number::IntegerPercent(50).is_fixed());
616    }
617
618    #[test_log::test]
619    fn number_calc_with_dynamic_percentage_in_calculation() {
620        let num = Number::Calc(Calculation::Add(
621            Box::new(Calculation::Number(Box::new(Number::IntegerPercent(50)))),
622            Box::new(Calculation::Number(Box::new(Number::Integer(10)))),
623        ));
624        let result = num.calc(100.0, 1920.0, 1080.0);
625        // 50% of 100 = 50, plus 10 = 60
626        assert!((result - 60.0).abs() < f32::EPSILON);
627        assert!(num.is_dynamic());
628    }
629}
630
631#[cfg(test)]
632mod test_calculation_calc {
633    use crate::{Calculation, Number};
634
635    #[test_log::test]
636    fn calculation_add_computes_sum() {
637        let calc = Calculation::Add(
638            Box::new(Calculation::Number(Box::new(Number::Integer(10)))),
639            Box::new(Calculation::Number(Box::new(Number::Integer(5)))),
640        );
641        let result = calc.calc(100.0, 1920.0, 1080.0);
642        assert!((result - 15.0).abs() < f32::EPSILON);
643    }
644
645    #[test_log::test]
646    fn calculation_subtract_computes_difference() {
647        let calc = Calculation::Subtract(
648            Box::new(Calculation::Number(Box::new(Number::Integer(10)))),
649            Box::new(Calculation::Number(Box::new(Number::Integer(3)))),
650        );
651        let result = calc.calc(100.0, 1920.0, 1080.0);
652        assert!((result - 7.0).abs() < f32::EPSILON);
653    }
654
655    #[test_log::test]
656    fn calculation_multiply_computes_product() {
657        let calc = Calculation::Multiply(
658            Box::new(Calculation::Number(Box::new(Number::Integer(7)))),
659            Box::new(Calculation::Number(Box::new(Number::Integer(6)))),
660        );
661        let result = calc.calc(100.0, 1920.0, 1080.0);
662        assert!((result - 42.0).abs() < f32::EPSILON);
663    }
664
665    #[test_log::test]
666    fn calculation_divide_computes_quotient() {
667        let calc = Calculation::Divide(
668            Box::new(Calculation::Number(Box::new(Number::Integer(20)))),
669            Box::new(Calculation::Number(Box::new(Number::Integer(4)))),
670        );
671        let result = calc.calc(100.0, 1920.0, 1080.0);
672        assert!((result - 5.0).abs() < f32::EPSILON);
673    }
674
675    #[test_log::test]
676    fn calculation_grouping_evaluates_inner_expression() {
677        let calc = Calculation::Grouping(Box::new(Calculation::Add(
678            Box::new(Calculation::Number(Box::new(Number::Integer(3)))),
679            Box::new(Calculation::Number(Box::new(Number::Integer(4)))),
680        )));
681        let result = calc.calc(100.0, 1920.0, 1080.0);
682        assert!((result - 7.0).abs() < f32::EPSILON);
683    }
684
685    #[test_log::test]
686    fn calculation_min_returns_smaller_value() {
687        let calc = Calculation::Min(
688            Box::new(Calculation::Number(Box::new(Number::Integer(10)))),
689            Box::new(Calculation::Number(Box::new(Number::Integer(5)))),
690        );
691        let result = calc.calc(100.0, 1920.0, 1080.0);
692        assert!((result - 5.0).abs() < f32::EPSILON);
693    }
694
695    #[test_log::test]
696    fn calculation_min_handles_equal_values() {
697        let calc = Calculation::Min(
698            Box::new(Calculation::Number(Box::new(Number::Integer(7)))),
699            Box::new(Calculation::Number(Box::new(Number::Integer(7)))),
700        );
701        let result = calc.calc(100.0, 1920.0, 1080.0);
702        assert!((result - 7.0).abs() < f32::EPSILON);
703    }
704
705    #[test_log::test]
706    fn calculation_max_returns_larger_value() {
707        let calc = Calculation::Max(
708            Box::new(Calculation::Number(Box::new(Number::Integer(10)))),
709            Box::new(Calculation::Number(Box::new(Number::Integer(5)))),
710        );
711        let result = calc.calc(100.0, 1920.0, 1080.0);
712        assert!((result - 10.0).abs() < f32::EPSILON);
713    }
714
715    #[test_log::test]
716    fn calculation_max_handles_equal_values() {
717        let calc = Calculation::Max(
718            Box::new(Calculation::Number(Box::new(Number::Integer(7)))),
719            Box::new(Calculation::Number(Box::new(Number::Integer(7)))),
720        );
721        let result = calc.calc(100.0, 1920.0, 1080.0);
722        assert!((result - 7.0).abs() < f32::EPSILON);
723    }
724
725    #[test_log::test]
726    fn calculation_nested_operations() {
727        // (10 + 5) * 2 = 30
728        let calc = Calculation::Multiply(
729            Box::new(Calculation::Grouping(Box::new(Calculation::Add(
730                Box::new(Calculation::Number(Box::new(Number::Integer(10)))),
731                Box::new(Calculation::Number(Box::new(Number::Integer(5)))),
732            )))),
733            Box::new(Calculation::Number(Box::new(Number::Integer(2)))),
734        );
735        let result = calc.calc(100.0, 1920.0, 1080.0);
736        assert!((result - 30.0).abs() < f32::EPSILON);
737    }
738
739    #[test_log::test]
740    fn calculation_is_dynamic_with_percent_number() {
741        let calc = Calculation::Number(Box::new(Number::IntegerPercent(50)));
742        assert!(calc.is_dynamic());
743    }
744
745    #[test_log::test]
746    fn calculation_is_dynamic_with_percent_in_nested_add() {
747        let calc = Calculation::Add(
748            Box::new(Calculation::Number(Box::new(Number::Integer(10)))),
749            Box::new(Calculation::Number(Box::new(Number::IntegerPercent(50)))),
750        );
751        assert!(calc.is_dynamic());
752    }
753
754    #[test_log::test]
755    fn calculation_is_fixed_with_all_fixed_numbers() {
756        let calc = Calculation::Add(
757            Box::new(Calculation::Number(Box::new(Number::Integer(10)))),
758            Box::new(Calculation::Number(Box::new(Number::Integer(20)))),
759        );
760        assert!(calc.is_fixed());
761    }
762
763    #[test_log::test]
764    fn calculation_is_fixed_returns_false_with_dynamic_operand() {
765        let calc = Calculation::Add(
766            Box::new(Calculation::Number(Box::new(Number::Integer(10)))),
767            Box::new(Calculation::Number(Box::new(Number::IntegerPercent(50)))),
768        );
769        assert!(!calc.is_fixed());
770    }
771
772    #[test_log::test]
773    fn calculation_min_is_dynamic_with_one_dynamic_operand() {
774        let calc = Calculation::Min(
775            Box::new(Calculation::Number(Box::new(Number::IntegerPercent(50)))),
776            Box::new(Calculation::Number(Box::new(Number::Integer(100)))),
777        );
778        assert!(calc.is_dynamic());
779    }
780
781    #[test_log::test]
782    fn calculation_max_is_dynamic_with_one_dynamic_operand() {
783        let calc = Calculation::Max(
784            Box::new(Calculation::Number(Box::new(Number::Integer(100)))),
785            Box::new(Calculation::Number(Box::new(Number::IntegerPercent(50)))),
786        );
787        assert!(calc.is_dynamic());
788    }
789
790    #[test_log::test]
791    fn calculation_grouping_is_dynamic_with_dynamic_inner() {
792        let calc = Calculation::Grouping(Box::new(Calculation::Number(Box::new(
793            Number::IntegerPercent(50),
794        ))));
795        assert!(calc.is_dynamic());
796    }
797
798    #[test_log::test]
799    fn calculation_grouping_is_fixed_with_fixed_inner() {
800        let calc =
801            Calculation::Grouping(Box::new(Calculation::Number(Box::new(Number::Integer(50)))));
802        assert!(calc.is_fixed());
803    }
804}
805
806#[cfg(test)]
807mod test_container_methods {
808    use crate::{Container, Element, Flex, Number};
809    use hyperchad_transformer_models::{AlignItems, JustifyContent, LayoutDirection, Position};
810
811    #[test_log::test]
812    fn container_is_fixed_returns_true_for_fixed_position() {
813        let container = Container {
814            position: Some(Position::Fixed),
815            ..Default::default()
816        };
817        assert!(container.is_fixed());
818    }
819
820    #[test_log::test]
821    fn container_is_fixed_returns_true_for_sticky_position() {
822        let container = Container {
823            position: Some(Position::Sticky),
824            ..Default::default()
825        };
826        assert!(container.is_fixed());
827    }
828
829    #[test_log::test]
830    fn container_is_fixed_returns_false_for_relative_position() {
831        let container = Container {
832            position: Some(Position::Relative),
833            ..Default::default()
834        };
835        assert!(!container.is_fixed());
836    }
837
838    #[test_log::test]
839    fn container_is_fixed_returns_false_for_absolute_position() {
840        let container = Container {
841            position: Some(Position::Absolute),
842            ..Default::default()
843        };
844        assert!(!container.is_fixed());
845    }
846
847    #[test_log::test]
848    fn container_is_fixed_returns_false_for_static_position() {
849        let container = Container {
850            position: Some(Position::Static),
851            ..Default::default()
852        };
853        assert!(!container.is_fixed());
854    }
855
856    #[test_log::test]
857    fn container_is_fixed_returns_false_for_no_position() {
858        let container = Container::default();
859        assert!(!container.is_fixed());
860    }
861
862    #[test_log::test]
863    fn container_is_raw_returns_true_for_raw_element() {
864        let container = Container {
865            element: Element::Raw {
866                value: "test".to_string(),
867            },
868            ..Default::default()
869        };
870        assert!(container.is_raw());
871    }
872
873    #[test_log::test]
874    fn container_is_raw_returns_false_for_div_element() {
875        let container = Container {
876            element: Element::Div,
877            ..Default::default()
878        };
879        assert!(!container.is_raw());
880    }
881
882    #[test_log::test]
883    fn container_is_visible_returns_true_when_not_hidden() {
884        let container = Container::default();
885        assert!(container.is_visible());
886    }
887
888    #[test_log::test]
889    fn container_is_visible_returns_true_when_hidden_is_false() {
890        let container = Container {
891            hidden: Some(false),
892            ..Default::default()
893        };
894        assert!(container.is_visible());
895    }
896
897    #[test_log::test]
898    fn container_is_visible_returns_false_when_hidden_is_true() {
899        let container = Container {
900            hidden: Some(true),
901            ..Default::default()
902        };
903        assert!(!container.is_visible());
904    }
905
906    #[test_log::test]
907    fn container_is_hidden_returns_true_only_when_hidden_is_true() {
908        assert!(
909            Container {
910                hidden: Some(true),
911                ..Default::default()
912            }
913            .is_hidden()
914        );
915        assert!(
916            !Container {
917                hidden: Some(false),
918                ..Default::default()
919            }
920            .is_hidden()
921        );
922        assert!(!Container::default().is_hidden());
923    }
924
925    #[test_log::test]
926    fn container_is_span_returns_true_for_raw_element() {
927        let container = Container {
928            element: Element::Raw {
929                value: "test".to_string(),
930            },
931            ..Default::default()
932        };
933        assert!(container.is_span());
934    }
935
936    #[test_log::test]
937    fn container_is_span_returns_true_for_span_element() {
938        let container = Container {
939            element: Element::Span,
940            ..Default::default()
941        };
942        assert!(container.is_span());
943    }
944
945    #[test_log::test]
946    fn container_is_span_returns_true_for_anchor_element() {
947        let container = Container {
948            element: Element::Anchor {
949                target: None,
950                href: None,
951            },
952            ..Default::default()
953        };
954        assert!(container.is_span());
955    }
956
957    #[test_log::test]
958    fn container_is_span_returns_false_for_div_element() {
959        let container = Container {
960            element: Element::Div,
961            ..Default::default()
962        };
963        assert!(!container.is_span());
964    }
965
966    #[test_log::test]
967    fn container_is_flex_container_returns_true_for_row_direction() {
968        let container = Container {
969            direction: LayoutDirection::Row,
970            ..Default::default()
971        };
972        assert!(container.is_flex_container());
973    }
974
975    #[test_log::test]
976    fn container_is_flex_container_returns_false_for_column_direction_with_no_flex_props() {
977        let container = Container {
978            direction: LayoutDirection::Column,
979            ..Default::default()
980        };
981        assert!(!container.is_flex_container());
982    }
983
984    #[test_log::test]
985    fn find_element_by_id_finds_self() {
986        let container = Container {
987            id: 42,
988            ..Default::default()
989        };
990        let found = container.find_element_by_id(42);
991        assert!(found.is_some());
992        assert_eq!(found.unwrap().id, 42);
993    }
994
995    #[test_log::test]
996    fn find_element_by_id_finds_nested_child() {
997        let child = Container {
998            id: 99,
999            ..Default::default()
1000        };
1001        let container = Container {
1002            id: 1,
1003            children: vec![Container {
1004                id: 2,
1005                children: vec![child],
1006                ..Default::default()
1007            }],
1008            ..Default::default()
1009        };
1010        let found = container.find_element_by_id(99);
1011        assert!(found.is_some());
1012        assert_eq!(found.unwrap().id, 99);
1013    }
1014
1015    #[test_log::test]
1016    fn find_element_by_id_returns_none_for_missing_id() {
1017        let container = Container {
1018            id: 1,
1019            ..Default::default()
1020        };
1021        assert!(container.find_element_by_id(999).is_none());
1022    }
1023
1024    #[test_log::test]
1025    fn find_element_by_str_id_finds_element() {
1026        let container = Container {
1027            str_id: Some("target".to_string()),
1028            ..Default::default()
1029        };
1030        let found = container.find_element_by_str_id("target");
1031        assert!(found.is_some());
1032    }
1033
1034    #[test_log::test]
1035    fn find_element_by_str_id_finds_nested_element() {
1036        let container = Container {
1037            str_id: Some("parent".to_string()),
1038            children: vec![Container {
1039                str_id: Some("child".to_string()),
1040                ..Default::default()
1041            }],
1042            ..Default::default()
1043        };
1044        let found = container.find_element_by_str_id("child");
1045        assert!(found.is_some());
1046    }
1047
1048    #[test_log::test]
1049    fn find_element_by_class_finds_element() {
1050        let container = Container {
1051            classes: vec!["my-class".to_string()],
1052            ..Default::default()
1053        };
1054        let found = container.find_element_by_class("my-class");
1055        assert!(found.is_some());
1056    }
1057
1058    #[test_log::test]
1059    fn find_element_by_class_returns_none_for_missing_class() {
1060        let container = Container {
1061            classes: vec!["other-class".to_string()],
1062            ..Default::default()
1063        };
1064        assert!(container.find_element_by_class("my-class").is_none());
1065    }
1066
1067    #[test_log::test]
1068    fn find_parent_by_id_returns_parent_of_child() {
1069        let container = Container {
1070            id: 1,
1071            children: vec![Container {
1072                id: 2,
1073                ..Default::default()
1074            }],
1075            ..Default::default()
1076        };
1077        let parent = container.find_parent_by_id(2);
1078        assert!(parent.is_some());
1079        assert_eq!(parent.unwrap().id, 1);
1080    }
1081
1082    #[test_log::test]
1083    fn find_parent_by_id_returns_none_for_root() {
1084        let container = Container {
1085            id: 1,
1086            ..Default::default()
1087        };
1088        assert!(container.find_parent_by_id(1).is_none());
1089    }
1090
1091    #[test_log::test]
1092    fn find_parent_by_str_id_mut_returns_parent() {
1093        let mut container = Container {
1094            id: 1,
1095            children: vec![Container {
1096                id: 2,
1097                str_id: Some("child-element".to_string()),
1098                ..Default::default()
1099            }],
1100            ..Default::default()
1101        };
1102        let parent = container.find_parent_by_str_id_mut("child-element");
1103        assert!(parent.is_some());
1104        assert_eq!(parent.unwrap().id, 1);
1105    }
1106
1107    #[test_log::test]
1108    fn find_parent_by_str_id_mut_returns_none_for_root() {
1109        let mut container = Container {
1110            id: 1,
1111            str_id: Some("root".to_string()),
1112            ..Default::default()
1113        };
1114        assert!(container.find_parent_by_str_id_mut("root").is_none());
1115    }
1116
1117    #[test_log::test]
1118    fn find_parent_by_str_id_mut_finds_nested_parent() {
1119        let mut container = Container {
1120            id: 1,
1121            children: vec![Container {
1122                id: 2,
1123                children: vec![Container {
1124                    id: 3,
1125                    str_id: Some("deeply-nested".to_string()),
1126                    ..Default::default()
1127                }],
1128                ..Default::default()
1129            }],
1130            ..Default::default()
1131        };
1132        let parent = container.find_parent_by_str_id_mut("deeply-nested");
1133        assert!(parent.is_some());
1134        assert_eq!(parent.unwrap().id, 2);
1135    }
1136
1137    #[test_log::test]
1138    fn is_span_returns_false_when_child_is_not_span() {
1139        let container = Container {
1140            element: Element::Span,
1141            children: vec![Container {
1142                element: Element::Div,
1143                ..Default::default()
1144            }],
1145            ..Default::default()
1146        };
1147        assert!(!container.is_span());
1148    }
1149
1150    #[test_log::test]
1151    fn is_span_returns_true_when_all_children_are_spans() {
1152        let container = Container {
1153            element: Element::Span,
1154            children: vec![
1155                Container {
1156                    element: Element::Span,
1157                    ..Default::default()
1158                },
1159                Container {
1160                    element: Element::Raw {
1161                        value: "text".to_string(),
1162                    },
1163                    ..Default::default()
1164                },
1165            ],
1166            ..Default::default()
1167        };
1168        assert!(container.is_span());
1169    }
1170
1171    #[test_log::test]
1172    fn is_flex_container_returns_true_with_justify_content() {
1173        let container = Container {
1174            justify_content: Some(JustifyContent::Center),
1175            ..Default::default()
1176        };
1177        assert!(container.is_flex_container());
1178    }
1179
1180    #[test_log::test]
1181    fn is_flex_container_returns_true_with_align_items() {
1182        let container = Container {
1183            align_items: Some(AlignItems::Center),
1184            ..Default::default()
1185        };
1186        assert!(container.is_flex_container());
1187    }
1188
1189    #[test_log::test]
1190    fn is_flex_container_returns_true_with_column_gap() {
1191        let container = Container {
1192            column_gap: Some(Number::Integer(10)),
1193            ..Default::default()
1194        };
1195        assert!(container.is_flex_container());
1196    }
1197
1198    #[test_log::test]
1199    fn is_flex_container_returns_true_when_child_has_flex() {
1200        let container = Container {
1201            children: vec![Container {
1202                flex: Some(Flex::default()),
1203                ..Default::default()
1204            }],
1205            ..Default::default()
1206        };
1207        assert!(container.is_flex_container());
1208    }
1209
1210    #[test_log::test]
1211    fn replace_id_children_with_elements_replaces_children() {
1212        let mut container = Container {
1213            id: 1,
1214            children: vec![Container {
1215                id: 2,
1216                children: vec![Container {
1217                    id: 10,
1218                    ..Default::default()
1219                }],
1220                ..Default::default()
1221            }],
1222            ..Default::default()
1223        };
1224
1225        let replacements = vec![
1226            Container {
1227                id: 100,
1228                ..Default::default()
1229            },
1230            Container {
1231                id: 101,
1232                ..Default::default()
1233            },
1234        ];
1235
1236        let original = container.replace_id_children_with_elements(replacements, 2);
1237
1238        assert!(original.is_some());
1239        let original_children = original.unwrap();
1240        assert_eq!(original_children.len(), 1);
1241        assert_eq!(original_children[0].id, 10);
1242
1243        let target = container.find_element_by_id(2).unwrap();
1244        assert_eq!(target.children.len(), 2);
1245        assert_eq!(target.children[0].id, 100);
1246        assert_eq!(target.children[1].id, 101);
1247    }
1248
1249    #[test_log::test]
1250    fn replace_id_children_with_elements_returns_none_for_missing_id() {
1251        let mut container = Container {
1252            id: 1,
1253            ..Default::default()
1254        };
1255
1256        let result = container.replace_id_children_with_elements(vec![], 999);
1257        assert!(result.is_none());
1258    }
1259
1260    #[test_log::test]
1261    fn replace_str_id_children_with_elements_replaces_children() {
1262        let mut container = Container {
1263            id: 1,
1264            children: vec![Container {
1265                id: 2,
1266                str_id: Some("target".to_string()),
1267                children: vec![Container {
1268                    id: 10,
1269                    ..Default::default()
1270                }],
1271                ..Default::default()
1272            }],
1273            ..Default::default()
1274        };
1275
1276        let replacements = vec![Container {
1277            id: 200,
1278            ..Default::default()
1279        }];
1280
1281        let original = container.replace_str_id_children_with_elements(replacements, "target");
1282
1283        assert!(original.is_some());
1284        let original_children = original.unwrap();
1285        assert_eq!(original_children.len(), 1);
1286        assert_eq!(original_children[0].id, 10);
1287
1288        let target = container.find_element_by_str_id("target").unwrap();
1289        assert_eq!(target.children.len(), 1);
1290        assert_eq!(target.children[0].id, 200);
1291    }
1292
1293    #[test_log::test]
1294    fn replace_str_id_children_with_elements_returns_none_for_missing_str_id() {
1295        let mut container = Container {
1296            id: 1,
1297            ..Default::default()
1298        };
1299
1300        let result = container.replace_str_id_children_with_elements(vec![], "nonexistent");
1301        assert!(result.is_none());
1302    }
1303
1304    #[test_log::test]
1305    fn replace_id_with_elements_replaces_element_and_returns_original() {
1306        let mut container = Container {
1307            id: 1,
1308            children: vec![
1309                Container {
1310                    id: 2,
1311                    ..Default::default()
1312                },
1313                Container {
1314                    id: 3,
1315                    ..Default::default()
1316                },
1317            ],
1318            ..Default::default()
1319        };
1320
1321        let replacements = vec![
1322            Container {
1323                id: 100,
1324                ..Default::default()
1325            },
1326            Container {
1327                id: 101,
1328                ..Default::default()
1329            },
1330        ];
1331
1332        let original = container.replace_id_with_elements(replacements, 2);
1333
1334        assert!(original.is_some());
1335        assert_eq!(original.unwrap().id, 2);
1336        assert_eq!(container.children.len(), 3);
1337        assert_eq!(container.children[0].id, 100);
1338        assert_eq!(container.children[1].id, 101);
1339        assert_eq!(container.children[2].id, 3);
1340    }
1341
1342    #[test_log::test]
1343    fn replace_id_with_elements_returns_none_for_missing_id() {
1344        let mut container = Container {
1345            id: 1,
1346            ..Default::default()
1347        };
1348
1349        let result = container.replace_id_with_elements(vec![], 999);
1350        assert!(result.is_none());
1351    }
1352
1353    #[test_log::test]
1354    fn replace_str_id_with_elements_replaces_element_and_returns_original() {
1355        let mut container = Container {
1356            id: 1,
1357            children: vec![
1358                Container {
1359                    id: 2,
1360                    str_id: Some("to-replace".to_string()),
1361                    ..Default::default()
1362                },
1363                Container {
1364                    id: 3,
1365                    ..Default::default()
1366                },
1367            ],
1368            ..Default::default()
1369        };
1370
1371        let replacements = vec![Container {
1372            id: 200,
1373            str_id: Some("replacement".to_string()),
1374            ..Default::default()
1375        }];
1376
1377        let original = container.replace_str_id_with_elements(replacements, "to-replace");
1378
1379        assert!(original.is_some());
1380        assert_eq!(original.unwrap().id, 2);
1381        assert_eq!(container.children.len(), 2);
1382        assert_eq!(container.children[0].id, 200);
1383        assert_eq!(
1384            container.children[0].str_id,
1385            Some("replacement".to_string())
1386        );
1387        assert_eq!(container.children[1].id, 3);
1388    }
1389
1390    #[test_log::test]
1391    fn replace_str_id_with_elements_returns_none_for_missing_str_id() {
1392        let mut container = Container {
1393            id: 1,
1394            ..Default::default()
1395        };
1396
1397        let result = container.replace_str_id_with_elements(vec![], "nonexistent");
1398        assert!(result.is_none());
1399    }
1400
1401    #[test_log::test]
1402    fn iter_overrides_returns_empty_for_no_overrides() {
1403        let container = Container::default();
1404        assert_eq!(container.iter_overrides(false).count(), 0);
1405    }
1406
1407    #[test_log::test]
1408    fn visible_elements_filters_hidden() {
1409        let container = Container {
1410            children: vec![
1411                Container {
1412                    hidden: Some(true),
1413                    ..Default::default()
1414                },
1415                Container {
1416                    hidden: Some(false),
1417                    ..Default::default()
1418                },
1419                Container::default(),
1420            ],
1421            ..Default::default()
1422        };
1423        assert_eq!(container.visible_elements().count(), 2);
1424    }
1425
1426    #[test_log::test]
1427    fn relative_positioned_elements_filters_absolute() {
1428        let container = Container {
1429            children: vec![
1430                Container {
1431                    position: Some(Position::Absolute),
1432                    ..Default::default()
1433                },
1434                Container {
1435                    position: Some(Position::Relative),
1436                    ..Default::default()
1437                },
1438                Container::default(),
1439            ],
1440            ..Default::default()
1441        };
1442        assert_eq!(container.relative_positioned_elements().count(), 2);
1443    }
1444
1445    #[test_log::test]
1446    fn absolute_positioned_elements_only_returns_absolute() {
1447        let container = Container {
1448            children: vec![
1449                Container {
1450                    position: Some(Position::Absolute),
1451                    ..Default::default()
1452                },
1453                Container {
1454                    position: Some(Position::Relative),
1455                    ..Default::default()
1456                },
1457                Container::default(),
1458            ],
1459            ..Default::default()
1460        };
1461        assert_eq!(container.absolute_positioned_elements().count(), 1);
1462    }
1463
1464    #[test_log::test]
1465    fn fixed_positioned_elements_only_returns_fixed_and_sticky() {
1466        let container = Container {
1467            children: vec![
1468                Container {
1469                    position: Some(Position::Fixed),
1470                    ..Default::default()
1471                },
1472                Container {
1473                    position: Some(Position::Sticky),
1474                    ..Default::default()
1475                },
1476                Container {
1477                    position: Some(Position::Absolute),
1478                    ..Default::default()
1479                },
1480                Container::default(),
1481            ],
1482            ..Default::default()
1483        };
1484        assert_eq!(container.fixed_positioned_elements().count(), 2);
1485    }
1486}
1487
1488#[cfg(test)]
1489mod test_element_methods {
1490    use crate::{Element, HeaderSize};
1491
1492    #[test_log::test]
1493    fn allows_children_returns_true_for_container_elements() {
1494        assert!(Element::Div.allows_children());
1495        assert!(Element::Aside.allows_children());
1496        assert!(Element::Main.allows_children());
1497        assert!(Element::Header.allows_children());
1498        assert!(Element::Footer.allows_children());
1499        assert!(Element::Section.allows_children());
1500        assert!(
1501            Element::Form {
1502                action: None,
1503                method: None
1504            }
1505            .allows_children()
1506        );
1507        assert!(Element::Span.allows_children());
1508        assert!(Element::UnorderedList.allows_children());
1509        assert!(Element::OrderedList.allows_children());
1510        assert!(Element::ListItem.allows_children());
1511        assert!(Element::Table.allows_children());
1512        assert!(Element::THead.allows_children());
1513        assert!(Element::TBody.allows_children());
1514        assert!(Element::TR.allows_children());
1515        assert!(Element::Details { open: None }.allows_children());
1516        assert!(Element::Summary.allows_children());
1517    }
1518
1519    #[test_log::test]
1520    fn allows_children_returns_true_for_elements_with_fields() {
1521        assert!(Element::Button { r#type: None }.allows_children());
1522        assert!(
1523            Element::Anchor {
1524                target: None,
1525                href: None
1526            }
1527            .allows_children()
1528        );
1529        assert!(
1530            Element::Heading {
1531                size: HeaderSize::H1
1532            }
1533            .allows_children()
1534        );
1535        assert!(
1536            Element::TH {
1537                rows: None,
1538                columns: None
1539            }
1540            .allows_children()
1541        );
1542        assert!(
1543            Element::TD {
1544                rows: None,
1545                columns: None
1546            }
1547            .allows_children()
1548        );
1549    }
1550
1551    #[test_log::test]
1552    fn allows_children_returns_false_for_leaf_elements() {
1553        assert!(
1554            !Element::Raw {
1555                value: "test".to_string()
1556            }
1557            .allows_children()
1558        );
1559        assert!(
1560            !Element::Image {
1561                source: None,
1562                alt: None,
1563                fit: None,
1564                source_set: None,
1565                sizes: None,
1566                loading: None
1567            }
1568            .allows_children()
1569        );
1570        assert!(
1571            !Element::Textarea {
1572                value: String::new(),
1573                placeholder: None,
1574                name: None,
1575                rows: None,
1576                cols: None
1577            }
1578            .allows_children()
1579        );
1580        assert!(
1581            !Element::Input {
1582                input: crate::Input::Text {
1583                    value: None,
1584                    placeholder: None
1585                },
1586                name: None,
1587                autofocus: None
1588            }
1589            .allows_children()
1590        );
1591    }
1592
1593    #[test_log::test]
1594    fn tag_display_str_returns_expected_strings() {
1595        assert_eq!(Element::Div.tag_display_str(), "Div");
1596        assert_eq!(
1597            Element::Raw {
1598                value: String::new()
1599            }
1600            .tag_display_str(),
1601            "Raw"
1602        );
1603        assert_eq!(Element::Span.tag_display_str(), "Span");
1604        assert_eq!(
1605            Element::Heading {
1606                size: HeaderSize::H2
1607            }
1608            .tag_display_str(),
1609            "Heading"
1610        );
1611        assert_eq!(Element::Table.tag_display_str(), "Table");
1612        assert_eq!(Element::Button { r#type: None }.tag_display_str(), "Button");
1613    }
1614}
1615
1616#[cfg(test)]
1617mod test_bfs_traversal {
1618    use crate::Container;
1619
1620    #[test_log::test]
1621    fn bfs_visit_visits_all_nodes() {
1622        let container = Container {
1623            id: 1,
1624            children: vec![
1625                Container {
1626                    id: 2,
1627                    children: vec![Container {
1628                        id: 4,
1629                        ..Default::default()
1630                    }],
1631                    ..Default::default()
1632                },
1633                Container {
1634                    id: 3,
1635                    ..Default::default()
1636                },
1637            ],
1638            ..Default::default()
1639        };
1640
1641        let mut visited_ids = Vec::new();
1642        let _ = container.bfs_visit(|c| visited_ids.push(c.id));
1643
1644        // Should visit all nodes
1645        assert!(visited_ids.contains(&1));
1646        assert!(visited_ids.contains(&2));
1647        assert!(visited_ids.contains(&3));
1648        assert!(visited_ids.contains(&4));
1649    }
1650
1651    #[test_log::test]
1652    fn bfs_traverse_visits_nodes_with_children() {
1653        let container = Container {
1654            id: 1,
1655            children: vec![
1656                Container {
1657                    id: 2,
1658                    children: vec![Container {
1659                        id: 4,
1660                        ..Default::default()
1661                    }],
1662                    ..Default::default()
1663                },
1664                Container {
1665                    id: 3,
1666                    children: vec![Container {
1667                        id: 5,
1668                        ..Default::default()
1669                    }],
1670                    ..Default::default()
1671                },
1672            ],
1673            ..Default::default()
1674        };
1675
1676        let bfs = container.bfs();
1677        let mut visited_ids = Vec::new();
1678        bfs.traverse(&container, |c| visited_ids.push(c.id));
1679
1680        // Traverse only visits nodes that have children (the paths to them)
1681        // So it visits the parents of the leaf nodes
1682        assert!(visited_ids.contains(&1) || visited_ids.contains(&2) || visited_ids.contains(&3));
1683    }
1684
1685    #[test_log::test]
1686    fn bfs_paths_from_container_creates_structure() {
1687        let container = Container {
1688            id: 1,
1689            children: vec![Container {
1690                id: 2,
1691                children: vec![Container {
1692                    id: 3,
1693                    ..Default::default()
1694                }],
1695                ..Default::default()
1696            }],
1697            ..Default::default()
1698        };
1699
1700        let bfs = crate::BfsPaths::from(&container);
1701        let mut count = 0;
1702        bfs.traverse(&container, |_| count += 1);
1703
1704        // Should have visited some nodes
1705        assert!(count >= 1);
1706    }
1707
1708    #[test_log::test]
1709    fn container_from_vec_creates_div_with_children() {
1710        let children = vec![
1711            Container {
1712                id: 1,
1713                ..Default::default()
1714            },
1715            Container {
1716                id: 2,
1717                ..Default::default()
1718            },
1719        ];
1720        let container: Container = children.into();
1721
1722        assert!(matches!(container.element, crate::Element::Div));
1723        assert_eq!(container.children.len(), 2);
1724    }
1725}
1726
1727static EPSILON: f32 = 0.00001;
1728
1729impl PartialEq for Number {
1730    fn eq(&self, other: &Self) -> bool {
1731        match (self, other) {
1732            #[allow(clippy::cast_precision_loss)]
1733            (Self::Real(float), Self::Integer(int))
1734            | (Self::RealPercent(float), Self::IntegerPercent(int))
1735            | (Self::RealVw(float), Self::IntegerVw(int))
1736            | (Self::RealVh(float), Self::IntegerVh(int))
1737            | (Self::RealDvw(float), Self::IntegerDvw(int))
1738            | (Self::RealDvh(float), Self::IntegerDvh(int))
1739            | (Self::Integer(int), Self::Real(float))
1740            | (Self::IntegerPercent(int), Self::RealPercent(float))
1741            | (Self::IntegerVw(int), Self::RealVw(float))
1742            | (Self::IntegerVh(int), Self::RealVh(float))
1743            | (Self::IntegerDvw(int), Self::RealDvw(float))
1744            | (Self::IntegerDvh(int), Self::RealDvh(float)) => {
1745                (*int as f32 - *float).abs() < EPSILON
1746            }
1747            (Self::Real(l), Self::Real(r))
1748            | (Self::RealPercent(l), Self::RealPercent(r))
1749            | (Self::RealVw(l), Self::RealVw(r))
1750            | (Self::RealVh(l), Self::RealVh(r))
1751            | (Self::RealDvw(l), Self::RealDvw(r))
1752            | (Self::RealDvh(l), Self::RealDvh(r)) => {
1753                l.is_infinite() && r.is_infinite()
1754                    || l.is_nan() && r.is_nan()
1755                    || (l - r).abs() < EPSILON
1756            }
1757            (Self::Integer(l), Self::Integer(r))
1758            | (Self::IntegerPercent(l), Self::IntegerPercent(r))
1759            | (Self::IntegerVw(l), Self::IntegerVw(r))
1760            | (Self::IntegerVh(l), Self::IntegerVh(r))
1761            | (Self::IntegerDvw(l), Self::IntegerDvw(r))
1762            | (Self::IntegerDvh(l), Self::IntegerDvh(r)) => l == r,
1763            (Self::Calc(l), Self::Calc(r)) => l == r,
1764            _ => false,
1765        }
1766    }
1767}
1768
1769impl std::fmt::Display for Number {
1770    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1771        match self {
1772            Self::Real(x) => {
1773                if x.abs() < EPSILON {
1774                    return f.write_fmt(format_args!("0"));
1775                }
1776                f.write_fmt(format_args!("{x}"))
1777            }
1778            Self::Integer(x) => {
1779                if *x == 0 {
1780                    return f.write_fmt(format_args!("0"));
1781                }
1782                f.write_fmt(format_args!("{x}"))
1783            }
1784            Self::RealPercent(x) => {
1785                if x.abs() < EPSILON {
1786                    return f.write_fmt(format_args!("0%"));
1787                }
1788                f.write_fmt(format_args!("{x}%"))
1789            }
1790            Self::IntegerPercent(x) => {
1791                if *x == 0 {
1792                    return f.write_fmt(format_args!("0%"));
1793                }
1794                f.write_fmt(format_args!("{x}%"))
1795            }
1796            Self::RealVw(x) => {
1797                if x.abs() < EPSILON {
1798                    return f.write_fmt(format_args!("0vw"));
1799                }
1800                f.write_fmt(format_args!("{x}vw"))
1801            }
1802            Self::IntegerVw(x) => {
1803                if *x == 0 {
1804                    return f.write_fmt(format_args!("0vw"));
1805                }
1806                f.write_fmt(format_args!("{x}vw"))
1807            }
1808            Self::RealVh(x) => {
1809                if x.abs() < EPSILON {
1810                    return f.write_fmt(format_args!("0vh"));
1811                }
1812                f.write_fmt(format_args!("{x}vh"))
1813            }
1814            Self::IntegerVh(x) => {
1815                if *x == 0 {
1816                    return f.write_fmt(format_args!("0vh"));
1817                }
1818                f.write_fmt(format_args!("{x}vh"))
1819            }
1820            Self::RealDvw(x) => {
1821                if x.abs() < EPSILON {
1822                    return f.write_fmt(format_args!("0dvw"));
1823                }
1824                f.write_fmt(format_args!("{x}dvw"))
1825            }
1826            Self::IntegerDvw(x) => {
1827                if *x == 0 {
1828                    return f.write_fmt(format_args!("0dvw"));
1829                }
1830                f.write_fmt(format_args!("{x}dvw"))
1831            }
1832            Self::RealDvh(x) => {
1833                if x.abs() < EPSILON {
1834                    return f.write_fmt(format_args!("0dvh"));
1835                }
1836                f.write_fmt(format_args!("{x}dvh"))
1837            }
1838            Self::IntegerDvh(x) => {
1839                if *x == 0 {
1840                    return f.write_fmt(format_args!("0dvh"));
1841                }
1842                f.write_fmt(format_args!("{x}dvh"))
1843            }
1844            Self::Calc(x) => f.write_fmt(format_args!("calc({x})")),
1845        }
1846    }
1847}
1848
1849impl From<f32> for Number {
1850    fn from(x: f32) -> Self {
1851        Self::Real(x)
1852    }
1853}
1854
1855impl From<f64> for Number {
1856    fn from(x: f64) -> Self {
1857        #[allow(clippy::cast_possible_truncation)]
1858        Self::Real(x as f32)
1859    }
1860}
1861
1862impl From<i8> for Number {
1863    fn from(x: i8) -> Self {
1864        Self::Integer(x.into())
1865    }
1866}
1867
1868impl From<i16> for Number {
1869    fn from(x: i16) -> Self {
1870        Self::Integer(x.into())
1871    }
1872}
1873
1874impl From<i32> for Number {
1875    fn from(x: i32) -> Self {
1876        Self::Integer(x.into())
1877    }
1878}
1879
1880impl From<i64> for Number {
1881    fn from(x: i64) -> Self {
1882        Self::Integer(x)
1883    }
1884}
1885
1886impl From<u8> for Number {
1887    fn from(x: u8) -> Self {
1888        Self::Integer(x.into())
1889    }
1890}
1891
1892impl From<u16> for Number {
1893    fn from(x: u16) -> Self {
1894        Self::Integer(x.into())
1895    }
1896}
1897
1898impl From<u32> for Number {
1899    fn from(x: u32) -> Self {
1900        Self::Integer(x.into())
1901    }
1902}
1903
1904#[allow(clippy::fallible_impl_from)]
1905impl From<u64> for Number {
1906    fn from(x: u64) -> Self {
1907        Self::Integer(x.try_into().unwrap())
1908    }
1909}
1910
1911impl Default for Number {
1912    fn default() -> Self {
1913        Self::Integer(0)
1914    }
1915}
1916
1917impl From<String> for Number {
1918    fn from(x: String) -> Self {
1919        x.as_str().into()
1920    }
1921}
1922
1923impl From<&String> for Number {
1924    fn from(x: &String) -> Self {
1925        x.as_str().into()
1926    }
1927}
1928
1929#[allow(clippy::fallible_impl_from)]
1930impl From<&str> for Number {
1931    fn from(x: &str) -> Self {
1932        parse_number(x).unwrap()
1933    }
1934}
1935
1936#[cfg(feature = "logic")]
1937impl From<hyperchad_actions::logic::IfExpression<i32, hyperchad_actions::logic::Responsive>>
1938    for Number
1939{
1940    fn from(
1941        if_expr: hyperchad_actions::logic::IfExpression<i32, hyperchad_actions::logic::Responsive>,
1942    ) -> Self {
1943        if let Some(default) = if_expr.default {
1944            Self::Integer(i64::from(default))
1945        } else if let Some(value) = if_expr.value {
1946            Self::Integer(i64::from(value))
1947        } else {
1948            Self::Integer(0)
1949        }
1950    }
1951}
1952
1953#[cfg(feature = "logic")]
1954impl From<hyperchad_actions::logic::IfExpression<i64, hyperchad_actions::logic::Responsive>>
1955    for Number
1956{
1957    fn from(
1958        if_expr: hyperchad_actions::logic::IfExpression<i64, hyperchad_actions::logic::Responsive>,
1959    ) -> Self {
1960        if let Some(default) = if_expr.default {
1961            Self::Integer(default)
1962        } else if let Some(value) = if_expr.value {
1963            Self::Integer(value)
1964        } else {
1965            Self::Integer(0)
1966        }
1967    }
1968}
1969
1970#[cfg(feature = "logic")]
1971impl From<hyperchad_actions::logic::IfExpression<f32, hyperchad_actions::logic::Responsive>>
1972    for Number
1973{
1974    fn from(
1975        if_expr: hyperchad_actions::logic::IfExpression<f32, hyperchad_actions::logic::Responsive>,
1976    ) -> Self {
1977        if let Some(default) = if_expr.default {
1978            Self::Real(default)
1979        } else if let Some(value) = if_expr.value {
1980            Self::Real(value)
1981        } else {
1982            Self::Real(0.0)
1983        }
1984    }
1985}
1986
1987/// Wrapper for boolean values with conditional logic support.
1988#[cfg(feature = "logic")]
1989pub struct BoolWrapper(pub bool);
1990
1991#[cfg(feature = "logic")]
1992impl From<bool> for BoolWrapper {
1993    fn from(value: bool) -> Self {
1994        Self(value)
1995    }
1996}
1997
1998#[cfg(feature = "logic")]
1999impl From<BoolWrapper> for bool {
2000    fn from(wrapper: BoolWrapper) -> Self {
2001        wrapper.0
2002    }
2003}
2004
2005#[cfg(feature = "logic")]
2006impl From<hyperchad_actions::logic::IfExpression<bool, hyperchad_actions::logic::Responsive>>
2007    for BoolWrapper
2008{
2009    fn from(
2010        if_expr: hyperchad_actions::logic::IfExpression<bool, hyperchad_actions::logic::Responsive>,
2011    ) -> Self {
2012        if let Some(default) = if_expr.default {
2013            Self(default)
2014        } else if let Some(value) = if_expr.value {
2015            Self(value)
2016        } else {
2017            Self(false)
2018        }
2019    }
2020}
2021
2022#[cfg(feature = "logic")]
2023impl<T> From<hyperchad_actions::logic::IfExpression<T, hyperchad_actions::logic::Responsive>>
2024    for Flex
2025where
2026    T: Into<Self>,
2027{
2028    fn from(
2029        if_expr: hyperchad_actions::logic::IfExpression<T, hyperchad_actions::logic::Responsive>,
2030    ) -> Self {
2031        if let Some(default) = if_expr.default {
2032            default.into()
2033        } else if let Some(value) = if_expr.value {
2034            value.into()
2035        } else {
2036            Self::default()
2037        }
2038    }
2039}
2040
2041/// Text decoration configuration including underline, overline, and strikethrough.
2042#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
2043pub struct TextDecoration {
2044    /// Color of the decoration line.
2045    pub color: Option<Color>,
2046    /// Types of decoration lines to apply.
2047    pub line: Vec<TextDecorationLine>,
2048    /// Style of the decoration line.
2049    pub style: Option<TextDecorationStyle>,
2050    /// Thickness of the decoration line.
2051    pub thickness: Option<Number>,
2052}
2053
2054/// Flexbox sizing configuration with grow, shrink, and basis values.
2055#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
2056pub struct Flex {
2057    /// Flex grow factor.
2058    pub grow: Number,
2059    /// Flex shrink factor.
2060    pub shrink: Number,
2061    /// Flex basis size.
2062    pub basis: Number,
2063}
2064
2065impl Default for Flex {
2066    fn default() -> Self {
2067        Self {
2068            grow: Number::Integer(1),
2069            shrink: Number::Integer(1),
2070            basis: Number::IntegerPercent(0),
2071        }
2072    }
2073}
2074
2075impl From<i64> for Flex {
2076    fn from(i: i64) -> Self {
2077        Self {
2078            grow: Number::Integer(i),
2079            ..Default::default()
2080        }
2081    }
2082}
2083
2084/// Conditions that trigger responsive layout changes.
2085#[derive(Clone, Debug)]
2086pub enum ResponsiveTrigger {
2087    /// Triggered when container width is at most the specified value.
2088    MaxWidth(Number),
2089    /// Triggered when container height is at most the specified value.
2090    MaxHeight(Number),
2091}
2092
2093/// Configuration override applied when a condition is met.
2094#[derive(Clone, Debug, PartialEq)]
2095pub struct ConfigOverride {
2096    /// Condition that must be satisfied for overrides to apply.
2097    pub condition: OverrideCondition,
2098    /// Style properties to override when condition is met.
2099    pub overrides: Vec<OverrideItem>,
2100    /// Default override if condition is not met.
2101    pub default: Option<OverrideItem>,
2102}
2103
2104/// Condition type for configuration overrides.
2105#[derive(Clone, Debug, PartialEq, Eq)]
2106pub enum OverrideCondition {
2107    /// Condition based on responsive target name.
2108    ResponsiveTarget {
2109        /// Name of the responsive target.
2110        name: String,
2111    },
2112}
2113
2114impl From<String> for OverrideCondition {
2115    fn from(value: String) -> Self {
2116        Self::ResponsiveTarget { name: value }
2117    }
2118}
2119
2120impl From<&str> for OverrideCondition {
2121    fn from(value: &str) -> Self {
2122        value.to_string().into()
2123    }
2124}
2125
2126/// Style property that can be overridden based on conditions.
2127///
2128/// Represents individual style properties that can be dynamically changed
2129/// when responsive conditions are met.
2130#[derive(Clone, Debug, PartialEq, EnumDiscriminants)]
2131#[strum_discriminants(derive(EnumIter))]
2132#[strum_discriminants(name(OverrideItemType))]
2133#[strum_discriminants(vis(pub(crate)))]
2134pub enum OverrideItem {
2135    /// Element ID override.
2136    StrId(String),
2137    /// CSS class list override.
2138    Classes(Vec<String>),
2139    /// Layout direction override (row, column, etc.).
2140    Direction(LayoutDirection),
2141    /// Horizontal overflow behavior override.
2142    OverflowX(LayoutOverflow),
2143    /// Vertical overflow behavior override.
2144    OverflowY(LayoutOverflow),
2145    /// Grid cell size override for grid layouts.
2146    GridCellSize(Number),
2147    /// Main axis alignment override (flex-start, center, space-between, etc.).
2148    JustifyContent(JustifyContent),
2149    /// Cross axis alignment override (flex-start, center, stretch, etc.).
2150    AlignItems(AlignItems),
2151    /// Text alignment override (left, center, right, justify).
2152    TextAlign(TextAlign),
2153    /// White space handling override (normal, nowrap, pre, etc.).
2154    WhiteSpace(WhiteSpace),
2155    /// Text decoration styling override (underline, line-through, etc.).
2156    TextDecoration(TextDecoration),
2157    /// Font family list override.
2158    FontFamily(Vec<String>),
2159    /// Font weight override (normal, bold, numeric values).
2160    FontWeight(FontWeight),
2161    /// Width override.
2162    Width(Number),
2163    /// Minimum width override.
2164    MinWidth(Number),
2165    /// Maximum width override.
2166    MaxWidth(Number),
2167    /// Height override.
2168    Height(Number),
2169    /// Minimum height override.
2170    MinHeight(Number),
2171    /// Maximum height override.
2172    MaxHeight(Number),
2173    /// Flex sizing override (grow, shrink, basis).
2174    Flex(Flex),
2175    /// Column gap override for flex/grid layouts.
2176    ColumnGap(Number),
2177    /// Row gap override for flex/grid layouts.
2178    RowGap(Number),
2179    /// Opacity override (0.0 to 1.0).
2180    Opacity(Number),
2181    /// Left position override for positioned elements.
2182    Left(Number),
2183    /// Right position override for positioned elements.
2184    Right(Number),
2185    /// Top position override for positioned elements.
2186    Top(Number),
2187    /// Bottom position override for positioned elements.
2188    Bottom(Number),
2189    /// Horizontal translation transform override.
2190    TranslateX(Number),
2191    /// Vertical translation transform override.
2192    TranslateY(Number),
2193    /// Cursor style override (pointer, default, text, etc.).
2194    Cursor(Cursor),
2195    /// User selection behavior override (auto, none, text, all).
2196    UserSelect(UserSelect),
2197    /// Text wrapping behavior override (normal, break-word, anywhere).
2198    OverflowWrap(OverflowWrap),
2199    /// Text overflow handling override (clip, ellipsis).
2200    TextOverflow(TextOverflow),
2201    /// Position type override (static, relative, absolute, fixed).
2202    Position(Position),
2203    /// Background color override.
2204    Background(Color),
2205    /// Top border override (color and width).
2206    BorderTop((Color, Number)),
2207    /// Right border override (color and width).
2208    BorderRight((Color, Number)),
2209    /// Bottom border override (color and width).
2210    BorderBottom((Color, Number)),
2211    /// Left border override (color and width).
2212    BorderLeft((Color, Number)),
2213    /// Top-left border radius override.
2214    BorderTopLeftRadius(Number),
2215    /// Top-right border radius override.
2216    BorderTopRightRadius(Number),
2217    /// Bottom-left border radius override.
2218    BorderBottomLeftRadius(Number),
2219    /// Bottom-right border radius override.
2220    BorderBottomRightRadius(Number),
2221    /// Left margin override.
2222    MarginLeft(Number),
2223    /// Right margin override.
2224    MarginRight(Number),
2225    /// Top margin override.
2226    MarginTop(Number),
2227    /// Bottom margin override.
2228    MarginBottom(Number),
2229    /// Left padding override.
2230    PaddingLeft(Number),
2231    /// Right padding override.
2232    PaddingRight(Number),
2233    /// Top padding override.
2234    PaddingTop(Number),
2235    /// Bottom padding override.
2236    PaddingBottom(Number),
2237    /// Font size override.
2238    FontSize(Number),
2239    /// Text color override.
2240    Color(Color),
2241    /// Visibility toggle override (true = hidden, false = visible).
2242    Hidden(bool),
2243    /// CSS visibility property override (visible, hidden, collapse).
2244    Visibility(Visibility),
2245}
2246
2247impl OverrideItem {
2248    /// Serializes this override item to a JSON string.
2249    ///
2250    /// # Errors
2251    ///
2252    /// * If the serialization fails
2253    pub fn serialize(&self) -> Result<String, serde_json::Error> {
2254        match self {
2255            Self::StrId(x) => serde_json::to_string(x),
2256            Self::Direction(x) => serde_json::to_string(x),
2257            Self::OverflowX(x) | Self::OverflowY(x) => serde_json::to_string(x),
2258            Self::JustifyContent(x) => serde_json::to_string(x),
2259            Self::AlignItems(x) => serde_json::to_string(x),
2260            Self::TextAlign(x) => serde_json::to_string(x),
2261            Self::WhiteSpace(x) => serde_json::to_string(x),
2262            Self::TextDecoration(x) => serde_json::to_string(x),
2263            Self::Classes(x) | Self::FontFamily(x) => serde_json::to_string(x),
2264            Self::FontWeight(x) => serde_json::to_string(x),
2265            Self::Flex(x) => serde_json::to_string(x),
2266            Self::Width(x)
2267            | Self::MinWidth(x)
2268            | Self::MaxWidth(x)
2269            | Self::Height(x)
2270            | Self::MinHeight(x)
2271            | Self::MaxHeight(x)
2272            | Self::ColumnGap(x)
2273            | Self::RowGap(x)
2274            | Self::Opacity(x)
2275            | Self::Left(x)
2276            | Self::Right(x)
2277            | Self::Top(x)
2278            | Self::Bottom(x)
2279            | Self::TranslateX(x)
2280            | Self::TranslateY(x)
2281            | Self::BorderTopLeftRadius(x)
2282            | Self::BorderTopRightRadius(x)
2283            | Self::BorderBottomLeftRadius(x)
2284            | Self::BorderBottomRightRadius(x)
2285            | Self::MarginLeft(x)
2286            | Self::MarginRight(x)
2287            | Self::MarginTop(x)
2288            | Self::MarginBottom(x)
2289            | Self::PaddingLeft(x)
2290            | Self::PaddingRight(x)
2291            | Self::PaddingTop(x)
2292            | Self::PaddingBottom(x)
2293            | Self::FontSize(x)
2294            | Self::GridCellSize(x) => serde_json::to_string(x),
2295            Self::Cursor(x) => serde_json::to_string(x),
2296            Self::UserSelect(x) => serde_json::to_string(x),
2297            Self::OverflowWrap(x) => serde_json::to_string(x),
2298            Self::TextOverflow(x) => serde_json::to_string(x),
2299            Self::Position(x) => serde_json::to_string(x),
2300            Self::BorderTop(x)
2301            | Self::BorderRight(x)
2302            | Self::BorderBottom(x)
2303            | Self::BorderLeft(x) => serde_json::to_string(x),
2304            Self::Background(x) | Self::Color(x) => serde_json::to_string(x),
2305            Self::Hidden(x) => serde_json::to_string(x),
2306            Self::Visibility(x) => serde_json::to_string(x),
2307        }
2308    }
2309
2310    /// Converts this override item to a JSON value.
2311    ///
2312    /// # Errors
2313    ///
2314    /// * If the serialization fails
2315    pub fn as_value(&self) -> Result<Value, serde_json::Error> {
2316        match self {
2317            Self::StrId(x) => serde_json::to_value(x),
2318            Self::Direction(x) => serde_json::to_value(x),
2319            Self::OverflowX(x) | Self::OverflowY(x) => serde_json::to_value(x),
2320            Self::JustifyContent(x) => serde_json::to_value(x),
2321            Self::AlignItems(x) => serde_json::to_value(x),
2322            Self::TextAlign(x) => serde_json::to_value(x),
2323            Self::WhiteSpace(x) => serde_json::to_value(x),
2324            Self::TextDecoration(x) => serde_json::to_value(x),
2325            Self::Classes(x) | Self::FontFamily(x) => serde_json::to_value(x),
2326            Self::FontWeight(x) => serde_json::to_value(x),
2327            Self::Flex(x) => serde_json::to_value(x),
2328            Self::Width(x)
2329            | Self::MinWidth(x)
2330            | Self::MaxWidth(x)
2331            | Self::Height(x)
2332            | Self::MinHeight(x)
2333            | Self::MaxHeight(x)
2334            | Self::ColumnGap(x)
2335            | Self::RowGap(x)
2336            | Self::Opacity(x)
2337            | Self::Left(x)
2338            | Self::Right(x)
2339            | Self::Top(x)
2340            | Self::Bottom(x)
2341            | Self::TranslateX(x)
2342            | Self::TranslateY(x)
2343            | Self::BorderTopLeftRadius(x)
2344            | Self::BorderTopRightRadius(x)
2345            | Self::BorderBottomLeftRadius(x)
2346            | Self::BorderBottomRightRadius(x)
2347            | Self::MarginLeft(x)
2348            | Self::MarginRight(x)
2349            | Self::MarginTop(x)
2350            | Self::MarginBottom(x)
2351            | Self::PaddingLeft(x)
2352            | Self::PaddingRight(x)
2353            | Self::PaddingTop(x)
2354            | Self::PaddingBottom(x)
2355            | Self::FontSize(x)
2356            | Self::GridCellSize(x) => serde_json::to_value(x),
2357            Self::Cursor(x) => serde_json::to_value(x),
2358            Self::UserSelect(x) => serde_json::to_value(x),
2359            Self::OverflowWrap(x) => serde_json::to_value(x),
2360            Self::TextOverflow(x) => serde_json::to_value(x),
2361            Self::Position(x) => serde_json::to_value(x),
2362            Self::BorderTop(x)
2363            | Self::BorderRight(x)
2364            | Self::BorderBottom(x)
2365            | Self::BorderLeft(x) => serde_json::to_value(x),
2366            Self::Background(x) | Self::Color(x) => serde_json::to_value(x),
2367            Self::Hidden(x) => serde_json::to_value(x),
2368            Self::Visibility(x) => serde_json::to_value(x),
2369        }
2370    }
2371
2372    /// Returns a type-erased reference to the inner value as a trait object.
2373    #[must_use]
2374    pub fn as_any<'a>(&'a self) -> Box<dyn Any + 'a> {
2375        match self {
2376            Self::StrId(x) => Box::new(x),
2377            Self::Direction(x) => Box::new(x),
2378            Self::OverflowX(x) | Self::OverflowY(x) => Box::new(x),
2379            Self::JustifyContent(x) => Box::new(x),
2380            Self::AlignItems(x) => Box::new(x),
2381            Self::TextAlign(x) => Box::new(x),
2382            Self::WhiteSpace(x) => Box::new(x),
2383            Self::TextDecoration(x) => Box::new(x),
2384            Self::Classes(x) | Self::FontFamily(x) => Box::new(x),
2385            Self::FontWeight(x) => Box::new(x),
2386            Self::Flex(x) => Box::new(x),
2387            Self::Width(x)
2388            | Self::MinWidth(x)
2389            | Self::MaxWidth(x)
2390            | Self::Height(x)
2391            | Self::MinHeight(x)
2392            | Self::MaxHeight(x)
2393            | Self::ColumnGap(x)
2394            | Self::RowGap(x)
2395            | Self::Opacity(x)
2396            | Self::Left(x)
2397            | Self::Right(x)
2398            | Self::Top(x)
2399            | Self::Bottom(x)
2400            | Self::TranslateX(x)
2401            | Self::TranslateY(x)
2402            | Self::BorderTopLeftRadius(x)
2403            | Self::BorderTopRightRadius(x)
2404            | Self::BorderBottomLeftRadius(x)
2405            | Self::BorderBottomRightRadius(x)
2406            | Self::MarginLeft(x)
2407            | Self::MarginRight(x)
2408            | Self::MarginTop(x)
2409            | Self::MarginBottom(x)
2410            | Self::PaddingLeft(x)
2411            | Self::PaddingRight(x)
2412            | Self::PaddingTop(x)
2413            | Self::PaddingBottom(x)
2414            | Self::FontSize(x)
2415            | Self::GridCellSize(x) => Box::new(x),
2416            Self::Cursor(x) => Box::new(x),
2417            Self::UserSelect(x) => Box::new(x),
2418            Self::OverflowWrap(x) => Box::new(x),
2419            Self::TextOverflow(x) => Box::new(x),
2420            Self::Position(x) => Box::new(x),
2421            Self::BorderTop(x)
2422            | Self::BorderRight(x)
2423            | Self::BorderBottom(x)
2424            | Self::BorderLeft(x) => Box::new(x),
2425            Self::Background(x) | Self::Color(x) => Box::new(x),
2426            Self::Hidden(x) => Box::new(x),
2427            Self::Visibility(x) => Box::new(x),
2428        }
2429    }
2430
2431    /// # Errors
2432    ///
2433    /// * If the serialization fails
2434    #[cfg(feature = "logic")]
2435    #[allow(clippy::too_many_lines)]
2436    fn as_json_if_expression_string(
2437        &self,
2438        responsive: hyperchad_actions::logic::Responsive,
2439        default: Option<&Self>,
2440    ) -> Result<String, serde_json::Error> {
2441        match self {
2442            Self::StrId(x) => {
2443                let mut expr = responsive.then::<&String>(x);
2444
2445                if let Some(Self::StrId(default)) = default {
2446                    expr = expr.or_else(default);
2447                }
2448
2449                serde_json::to_string(&expr)
2450            }
2451            Self::Direction(x) => {
2452                let mut expr = responsive.then::<&LayoutDirection>(x);
2453
2454                if let Some(Self::Direction(default)) = default {
2455                    expr = expr.or_else(default);
2456                }
2457
2458                serde_json::to_string(&expr)
2459            }
2460            Self::OverflowX(x) | Self::OverflowY(x) => {
2461                let mut expr = responsive.then::<&LayoutOverflow>(x);
2462
2463                if let Some(Self::OverflowX(default) | Self::OverflowY(default)) = default {
2464                    expr = expr.or_else(default);
2465                }
2466
2467                serde_json::to_string(&expr)
2468            }
2469            Self::JustifyContent(x) => {
2470                let mut expr = responsive.then::<&JustifyContent>(x);
2471
2472                if let Some(Self::JustifyContent(default)) = default {
2473                    expr = expr.or_else(default);
2474                }
2475
2476                serde_json::to_string(&expr)
2477            }
2478            Self::AlignItems(x) => {
2479                let mut expr = responsive.then::<&AlignItems>(x);
2480
2481                if let Some(Self::AlignItems(default)) = default {
2482                    expr = expr.or_else(default);
2483                }
2484
2485                serde_json::to_string(&expr)
2486            }
2487            Self::TextAlign(x) => {
2488                let mut expr = responsive.then::<&TextAlign>(x);
2489
2490                if let Some(Self::TextAlign(default)) = default {
2491                    expr = expr.or_else(default);
2492                }
2493
2494                serde_json::to_string(&expr)
2495            }
2496            Self::WhiteSpace(x) => {
2497                let mut expr = responsive.then::<&WhiteSpace>(x);
2498
2499                if let Some(Self::WhiteSpace(default)) = default {
2500                    expr = expr.or_else(default);
2501                }
2502
2503                serde_json::to_string(&expr)
2504            }
2505            Self::TextDecoration(x) => {
2506                let mut expr = responsive.then::<&TextDecoration>(x);
2507
2508                if let Some(Self::TextDecoration(default)) = default {
2509                    expr = expr.or_else(default);
2510                }
2511
2512                serde_json::to_string(&expr)
2513            }
2514            Self::Classes(x) | Self::FontFamily(x) => {
2515                let mut expr = responsive.then::<&Vec<String>>(x);
2516
2517                if let Some(Self::Classes(default) | Self::FontFamily(default)) = default {
2518                    expr = expr.or_else(default);
2519                }
2520
2521                serde_json::to_string(&expr)
2522            }
2523            Self::FontWeight(x) => {
2524                let mut expr = responsive.then::<&FontWeight>(x);
2525
2526                if let Some(Self::FontWeight(default)) = default {
2527                    expr = expr.or_else(default);
2528                }
2529
2530                serde_json::to_string(&expr)
2531            }
2532            Self::Flex(x) => {
2533                let mut expr = responsive.then::<&Flex>(x);
2534
2535                if let Some(Self::Flex(default)) = default {
2536                    expr = expr.or_else(default);
2537                }
2538
2539                serde_json::to_string(&expr)
2540            }
2541            Self::Width(x)
2542            | Self::MinWidth(x)
2543            | Self::MaxWidth(x)
2544            | Self::Height(x)
2545            | Self::MinHeight(x)
2546            | Self::MaxHeight(x)
2547            | Self::ColumnGap(x)
2548            | Self::RowGap(x)
2549            | Self::Opacity(x)
2550            | Self::Left(x)
2551            | Self::Right(x)
2552            | Self::Top(x)
2553            | Self::Bottom(x)
2554            | Self::TranslateX(x)
2555            | Self::TranslateY(x)
2556            | Self::BorderTopLeftRadius(x)
2557            | Self::BorderTopRightRadius(x)
2558            | Self::BorderBottomLeftRadius(x)
2559            | Self::BorderBottomRightRadius(x)
2560            | Self::MarginLeft(x)
2561            | Self::MarginRight(x)
2562            | Self::MarginTop(x)
2563            | Self::MarginBottom(x)
2564            | Self::PaddingLeft(x)
2565            | Self::PaddingRight(x)
2566            | Self::PaddingTop(x)
2567            | Self::PaddingBottom(x)
2568            | Self::FontSize(x)
2569            | Self::GridCellSize(x) => {
2570                let mut expr = responsive.then::<&Number>(x);
2571
2572                if let Some(
2573                    Self::Width(default)
2574                    | Self::MinWidth(default)
2575                    | Self::MaxWidth(default)
2576                    | Self::Height(default)
2577                    | Self::MinHeight(default)
2578                    | Self::MaxHeight(default)
2579                    | Self::ColumnGap(default)
2580                    | Self::RowGap(default)
2581                    | Self::Opacity(default)
2582                    | Self::Left(default)
2583                    | Self::Right(default)
2584                    | Self::Top(default)
2585                    | Self::Bottom(default)
2586                    | Self::TranslateX(default)
2587                    | Self::TranslateY(default)
2588                    | Self::BorderTopLeftRadius(default)
2589                    | Self::BorderTopRightRadius(default)
2590                    | Self::BorderBottomLeftRadius(default)
2591                    | Self::BorderBottomRightRadius(default)
2592                    | Self::MarginLeft(default)
2593                    | Self::MarginRight(default)
2594                    | Self::MarginTop(default)
2595                    | Self::MarginBottom(default)
2596                    | Self::PaddingLeft(default)
2597                    | Self::PaddingRight(default)
2598                    | Self::PaddingTop(default)
2599                    | Self::PaddingBottom(default)
2600                    | Self::FontSize(default)
2601                    | Self::GridCellSize(default),
2602                ) = default
2603                {
2604                    expr = expr.or_else(default);
2605                }
2606
2607                serde_json::to_string(&expr)
2608            }
2609            Self::Cursor(x) => {
2610                let mut expr = responsive.then::<&Cursor>(x);
2611
2612                if let Some(Self::Cursor(default)) = default {
2613                    expr = expr.or_else(default);
2614                }
2615
2616                serde_json::to_string(&expr)
2617            }
2618            Self::UserSelect(x) => {
2619                let mut expr = responsive.then::<&UserSelect>(x);
2620
2621                if let Some(Self::UserSelect(default)) = default {
2622                    expr = expr.or_else(default);
2623                }
2624
2625                serde_json::to_string(&expr)
2626            }
2627            Self::OverflowWrap(x) => {
2628                let mut expr = responsive.then::<&OverflowWrap>(x);
2629
2630                if let Some(Self::OverflowWrap(default)) = default {
2631                    expr = expr.or_else(default);
2632                }
2633
2634                serde_json::to_string(&expr)
2635            }
2636            Self::TextOverflow(x) => {
2637                let mut expr = responsive.then::<&TextOverflow>(x);
2638
2639                if let Some(Self::TextOverflow(default)) = default {
2640                    expr = expr.or_else(default);
2641                }
2642
2643                serde_json::to_string(&expr)
2644            }
2645            Self::Position(x) => {
2646                let mut expr = responsive.then::<&Position>(x);
2647
2648                if let Some(Self::Position(default)) = default {
2649                    expr = expr.or_else(default);
2650                }
2651
2652                serde_json::to_string(&expr)
2653            }
2654            Self::BorderTop(x)
2655            | Self::BorderRight(x)
2656            | Self::BorderBottom(x)
2657            | Self::BorderLeft(x) => {
2658                let mut expr = responsive.then::<&(Color, Number)>(x);
2659
2660                if let Some(
2661                    Self::BorderTop(default)
2662                    | Self::BorderRight(default)
2663                    | Self::BorderBottom(default)
2664                    | Self::BorderLeft(default),
2665                ) = default
2666                {
2667                    expr = expr.or_else(default);
2668                }
2669
2670                serde_json::to_string(&expr)
2671            }
2672            Self::Background(x) | Self::Color(x) => {
2673                let mut expr = responsive.then::<&Color>(x);
2674
2675                if let Some(Self::Background(default) | Self::Color(default)) = default {
2676                    expr = expr.or_else(default);
2677                }
2678
2679                serde_json::to_string(&expr)
2680            }
2681            Self::Hidden(x) => {
2682                let mut expr = responsive.then::<&bool>(x);
2683
2684                if let Some(Self::Hidden(default)) = default {
2685                    expr = expr.or_else(default);
2686                }
2687
2688                serde_json::to_string(&expr)
2689            }
2690            Self::Visibility(x) => {
2691                let mut expr = responsive.then::<&Visibility>(x);
2692
2693                if let Some(Self::Visibility(default)) = default {
2694                    expr = expr.or_else(default);
2695                }
2696
2697                serde_json::to_string(&expr)
2698            }
2699        }
2700    }
2701}
2702
2703/// Pattern matches on [`OverrideItem`] variants and executes an action with the inner value.
2704///
2705/// This macro simplifies handling of [`OverrideItem`] enums by matching all variants
2706/// and binding the inner value to a specified identifier for use in an action expression.
2707///
2708/// # Parameters
2709///
2710/// * `$val` - The [`OverrideItem`] value to match against
2711/// * `$name` - Identifier to bind the inner value to in each match arm
2712/// * `$action` - Expression to execute for each matched variant, using `$name`
2713///
2714/// # Examples
2715///
2716/// ```rust,ignore
2717/// use hyperchad_transformer::{OverrideItem, Number, override_item};
2718/// let item = OverrideItem::Width(Number::from(100));
2719/// let result: String = override_item!(&item, val, {
2720///     // Action to perform with the value
2721///     format!("Width: {:?}", val)
2722/// });
2723/// ```
2724#[macro_export]
2725macro_rules! override_item {
2726    ($val:expr, $name:ident, $action:expr) => {{
2727        match $val {
2728            OverrideItem::StrId($name) => $action,
2729            OverrideItem::Data($name) => $action,
2730            OverrideItem::Direction($name) => $action,
2731            OverrideItem::OverflowX($name) | OverrideItem::OverflowY($name) => $action,
2732            OverrideItem::JustifyContent($name) => $action,
2733            OverrideItem::AlignItems($name) => $action,
2734            OverrideItem::TextAlign($name) => $action,
2735            OverrideItem::WhiteSpace($name) => $action,
2736            OverrideItem::TextDecoration($name) => $action,
2737            OverrideItem::Classes($name) | OverrideItem::FontFamily($name) => $action,
2738            OverrideItem::FontWeight($name) => $action,
2739            OverrideItem::Flex($name) => $action,
2740            OverrideItem::Width($name)
2741            | OverrideItem::MinWidth($name)
2742            | OverrideItem::MaxWidth($name)
2743            | OverrideItem::Height($name)
2744            | OverrideItem::MinHeight($name)
2745            | OverrideItem::MaxHeight($name)
2746            | OverrideItem::ColumnGap($name)
2747            | OverrideItem::RowGap($name)
2748            | OverrideItem::Opacity($name)
2749            | OverrideItem::Left($name)
2750            | OverrideItem::Right($name)
2751            | OverrideItem::Top($name)
2752            | OverrideItem::Bottom($name)
2753            | OverrideItem::TranslateX($name)
2754            | OverrideItem::TranslateY($name)
2755            | OverrideItem::BorderTopLeftRadius($name)
2756            | OverrideItem::BorderTopRightRadius($name)
2757            | OverrideItem::BorderBottomLeftRadius($name)
2758            | OverrideItem::BorderBottomRightRadius($name)
2759            | OverrideItem::MarginLeft($name)
2760            | OverrideItem::MarginRight($name)
2761            | OverrideItem::MarginTop($name)
2762            | OverrideItem::MarginBottom($name)
2763            | OverrideItem::PaddingLeft($name)
2764            | OverrideItem::PaddingRight($name)
2765            | OverrideItem::PaddingTop($name)
2766            | OverrideItem::PaddingBottom($name)
2767            | OverrideItem::FontSize($name)
2768            | OverrideItem::GridCellSize($name) => $action,
2769            OverrideItem::Cursor($name) => $action,
2770            OverrideItem::UserSelect($name) => $action,
2771            OverrideItem::OverflowWrap($name) => $action,
2772            OverrideItem::TextOverflow($name) => $action,
2773            OverrideItem::Position($name) => $action,
2774            OverrideItem::BorderTop($name)
2775            | OverrideItem::BorderRight($name)
2776            | OverrideItem::BorderBottom($name)
2777            | OverrideItem::BorderLeft($name) => $action,
2778            OverrideItem::Background($name) | OverrideItem::Color($name) => $action,
2779            OverrideItem::Hidden($name) | OverrideItem::Debug($name) => $action,
2780            OverrideItem::Visibility($name) => $action,
2781        }
2782    }};
2783}
2784
2785/// Represents a layout container with style properties and child elements.
2786///
2787/// The main building block for constructing UI layouts. Contains all layout and styling
2788/// properties needed for rendering, along with child containers forming a tree structure.
2789#[derive(Clone, Debug, Default, PartialEq)]
2790pub struct Container {
2791    /// Unique numeric identifier.
2792    pub id: usize,
2793    /// Optional string identifier.
2794    pub str_id: Option<String>,
2795    /// CSS class names.
2796    pub classes: Vec<String>,
2797    /// Custom data attributes.
2798    pub data: BTreeMap<String, String>,
2799    /// Element type and content.
2800    pub element: Element,
2801    /// Child containers.
2802    pub children: Vec<Self>,
2803    /// Layout direction (row or column).
2804    pub direction: LayoutDirection,
2805    /// Horizontal overflow behavior.
2806    pub overflow_x: LayoutOverflow,
2807    /// Vertical overflow behavior.
2808    pub overflow_y: LayoutOverflow,
2809    /// Grid cell size for grid layouts.
2810    pub grid_cell_size: Option<Number>,
2811    /// Main axis alignment (flex-start, center, space-between, etc.).
2812    pub justify_content: Option<JustifyContent>,
2813    /// Cross axis alignment (flex-start, center, stretch, etc.).
2814    pub align_items: Option<AlignItems>,
2815    /// Text alignment (left, center, right, justify).
2816    pub text_align: Option<TextAlign>,
2817    /// White space handling (normal, nowrap, pre, etc.).
2818    pub white_space: Option<WhiteSpace>,
2819    /// Text decoration styling.
2820    pub text_decoration: Option<TextDecoration>,
2821    /// Font family list.
2822    pub font_family: Option<Vec<String>>,
2823    /// Font weight.
2824    pub font_weight: Option<FontWeight>,
2825    /// Width of the container.
2826    pub width: Option<Number>,
2827    /// Minimum width constraint.
2828    pub min_width: Option<Number>,
2829    /// Maximum width constraint.
2830    pub max_width: Option<Number>,
2831    /// Height of the container.
2832    pub height: Option<Number>,
2833    /// Minimum height constraint.
2834    pub min_height: Option<Number>,
2835    /// Maximum height constraint.
2836    pub max_height: Option<Number>,
2837    /// Flex sizing (grow, shrink, basis).
2838    pub flex: Option<Flex>,
2839    /// Gap between columns in flex/grid layouts.
2840    pub column_gap: Option<Number>,
2841    /// Gap between rows in flex/grid layouts.
2842    pub row_gap: Option<Number>,
2843    /// Opacity (0.0 to 1.0).
2844    pub opacity: Option<Number>,
2845    /// Left position for positioned elements.
2846    pub left: Option<Number>,
2847    /// Right position for positioned elements.
2848    pub right: Option<Number>,
2849    /// Top position for positioned elements.
2850    pub top: Option<Number>,
2851    /// Bottom position for positioned elements.
2852    pub bottom: Option<Number>,
2853    /// Horizontal translation transform.
2854    pub translate_x: Option<Number>,
2855    /// Vertical translation transform.
2856    pub translate_y: Option<Number>,
2857    /// Cursor style.
2858    pub cursor: Option<Cursor>,
2859    /// User selection behavior.
2860    pub user_select: Option<UserSelect>,
2861    /// Text wrapping behavior.
2862    pub overflow_wrap: Option<OverflowWrap>,
2863    /// Text overflow handling.
2864    pub text_overflow: Option<TextOverflow>,
2865    /// Position type (static, relative, absolute, fixed).
2866    pub position: Option<Position>,
2867    /// Background color.
2868    pub background: Option<Color>,
2869    /// Top border (color and width).
2870    pub border_top: Option<(Color, Number)>,
2871    /// Right border (color and width).
2872    pub border_right: Option<(Color, Number)>,
2873    /// Bottom border (color and width).
2874    pub border_bottom: Option<(Color, Number)>,
2875    /// Left border (color and width).
2876    pub border_left: Option<(Color, Number)>,
2877    /// Top-left border radius.
2878    pub border_top_left_radius: Option<Number>,
2879    /// Top-right border radius.
2880    pub border_top_right_radius: Option<Number>,
2881    /// Bottom-left border radius.
2882    pub border_bottom_left_radius: Option<Number>,
2883    /// Bottom-right border radius.
2884    pub border_bottom_right_radius: Option<Number>,
2885    /// Left margin.
2886    pub margin_left: Option<Number>,
2887    /// Right margin.
2888    pub margin_right: Option<Number>,
2889    /// Top margin.
2890    pub margin_top: Option<Number>,
2891    /// Bottom margin.
2892    pub margin_bottom: Option<Number>,
2893    /// Left padding.
2894    pub padding_left: Option<Number>,
2895    /// Right padding.
2896    pub padding_right: Option<Number>,
2897    /// Top padding.
2898    pub padding_top: Option<Number>,
2899    /// Bottom padding.
2900    pub padding_bottom: Option<Number>,
2901    /// Font size.
2902    pub font_size: Option<Number>,
2903    /// Text color.
2904    pub color: Option<Color>,
2905    /// Custom state data for dynamic behavior.
2906    pub state: Option<Value>,
2907    /// Whether the container is hidden.
2908    pub hidden: Option<bool>,
2909    /// Whether to render debug information.
2910    pub debug: Option<bool>,
2911    /// CSS visibility property.
2912    pub visibility: Option<Visibility>,
2913    /// Associated route for navigation.
2914    pub route: Option<Route>,
2915    /// Interactive actions bound to this container.
2916    pub actions: Vec<Action>,
2917    /// Conditional style overrides.
2918    pub overrides: Vec<ConfigOverride>,
2919    /// Calculated left margin in pixels (requires `layout` feature).
2920    #[cfg(feature = "layout")]
2921    pub calculated_margin_left: Option<f32>,
2922    /// Calculated right margin in pixels (requires `layout` feature).
2923    #[cfg(feature = "layout")]
2924    pub calculated_margin_right: Option<f32>,
2925    /// Calculated top margin in pixels (requires `layout` feature).
2926    #[cfg(feature = "layout")]
2927    pub calculated_margin_top: Option<f32>,
2928    /// Calculated bottom margin in pixels (requires `layout` feature).
2929    #[cfg(feature = "layout")]
2930    pub calculated_margin_bottom: Option<f32>,
2931    /// Calculated left padding in pixels (requires `layout` feature).
2932    #[cfg(feature = "layout")]
2933    pub calculated_padding_left: Option<f32>,
2934    /// Calculated right padding in pixels (requires `layout` feature).
2935    #[cfg(feature = "layout")]
2936    pub calculated_padding_right: Option<f32>,
2937    /// Calculated top padding in pixels (requires `layout` feature).
2938    #[cfg(feature = "layout")]
2939    pub calculated_padding_top: Option<f32>,
2940    /// Calculated bottom padding in pixels (requires `layout` feature).
2941    #[cfg(feature = "layout")]
2942    pub calculated_padding_bottom: Option<f32>,
2943    /// Calculated minimum width in pixels (requires `layout` feature).
2944    #[cfg(feature = "layout")]
2945    pub calculated_min_width: Option<f32>,
2946    /// Calculated minimum width based on children (requires `layout` feature).
2947    #[cfg(feature = "layout")]
2948    pub calculated_child_min_width: Option<f32>,
2949    /// Calculated maximum width in pixels (requires `layout` feature).
2950    #[cfg(feature = "layout")]
2951    pub calculated_max_width: Option<f32>,
2952    /// Calculated preferred width in pixels (requires `layout` feature).
2953    #[cfg(feature = "layout")]
2954    pub calculated_preferred_width: Option<f32>,
2955    /// Calculated final width in pixels (requires `layout` feature).
2956    #[cfg(feature = "layout")]
2957    pub calculated_width: Option<f32>,
2958    /// Calculated minimum height in pixels (requires `layout` feature).
2959    #[cfg(feature = "layout")]
2960    pub calculated_min_height: Option<f32>,
2961    /// Calculated minimum height based on children (requires `layout` feature).
2962    #[cfg(feature = "layout")]
2963    pub calculated_child_min_height: Option<f32>,
2964    /// Calculated maximum height in pixels (requires `layout` feature).
2965    #[cfg(feature = "layout")]
2966    pub calculated_max_height: Option<f32>,
2967    /// Calculated preferred height in pixels (requires `layout` feature).
2968    #[cfg(feature = "layout")]
2969    pub calculated_preferred_height: Option<f32>,
2970    /// Calculated final height in pixels (requires `layout` feature).
2971    #[cfg(feature = "layout")]
2972    pub calculated_height: Option<f32>,
2973    /// Calculated x-coordinate position in pixels (requires `layout` feature).
2974    #[cfg(feature = "layout")]
2975    pub calculated_x: Option<f32>,
2976    /// Calculated y-coordinate position in pixels (requires `layout` feature).
2977    #[cfg(feature = "layout")]
2978    pub calculated_y: Option<f32>,
2979    /// Calculated layout position (requires `layout` feature).
2980    #[cfg(feature = "layout")]
2981    pub calculated_position: Option<hyperchad_transformer_models::LayoutPosition>,
2982    /// Calculated top border with resolved color and pixel width (requires `layout` feature).
2983    #[cfg(feature = "layout")]
2984    pub calculated_border_top: Option<(Color, f32)>,
2985    /// Calculated right border with resolved color and pixel width (requires `layout` feature).
2986    #[cfg(feature = "layout")]
2987    pub calculated_border_right: Option<(Color, f32)>,
2988    /// Calculated bottom border with resolved color and pixel width (requires `layout` feature).
2989    #[cfg(feature = "layout")]
2990    pub calculated_border_bottom: Option<(Color, f32)>,
2991    /// Calculated left border with resolved color and pixel width (requires `layout` feature).
2992    #[cfg(feature = "layout")]
2993    pub calculated_border_left: Option<(Color, f32)>,
2994    /// Calculated top-left border radius in pixels (requires `layout` feature).
2995    #[cfg(feature = "layout")]
2996    pub calculated_border_top_left_radius: Option<f32>,
2997    /// Calculated top-right border radius in pixels (requires `layout` feature).
2998    #[cfg(feature = "layout")]
2999    pub calculated_border_top_right_radius: Option<f32>,
3000    /// Calculated bottom-left border radius in pixels (requires `layout` feature).
3001    #[cfg(feature = "layout")]
3002    pub calculated_border_bottom_left_radius: Option<f32>,
3003    /// Calculated bottom-right border radius in pixels (requires `layout` feature).
3004    #[cfg(feature = "layout")]
3005    pub calculated_border_bottom_right_radius: Option<f32>,
3006    /// Calculated column gap in pixels (requires `layout` feature).
3007    #[cfg(feature = "layout")]
3008    pub calculated_column_gap: Option<f32>,
3009    /// Calculated row gap in pixels (requires `layout` feature).
3010    #[cfg(feature = "layout")]
3011    pub calculated_row_gap: Option<f32>,
3012    /// Calculated opacity value (requires `layout` feature).
3013    #[cfg(feature = "layout")]
3014    pub calculated_opacity: Option<f32>,
3015    /// Calculated font size in pixels (requires `layout` feature).
3016    #[cfg(feature = "layout")]
3017    pub calculated_font_size: Option<f32>,
3018    /// Right scrollbar offset in pixels (requires `layout` feature).
3019    #[cfg(feature = "layout")]
3020    pub scrollbar_right: Option<f32>,
3021    /// Bottom scrollbar offset in pixels (requires `layout` feature).
3022    #[cfg(feature = "layout")]
3023    pub scrollbar_bottom: Option<f32>,
3024    /// Calculated x-axis offset for scrolling (requires `layout-offset` feature).
3025    #[cfg(feature = "layout-offset")]
3026    pub calculated_offset_x: Option<f32>,
3027    /// Calculated y-axis offset for scrolling (requires `layout-offset` feature).
3028    #[cfg(feature = "layout-offset")]
3029    pub calculated_offset_y: Option<f32>,
3030}
3031
3032impl AsRef<Self> for Container {
3033    fn as_ref(&self) -> &Self {
3034        self
3035    }
3036}
3037
3038impl Container {
3039    /// Returns an iterator over config overrides for this container and optionally its children.
3040    ///
3041    /// # Parameters
3042    ///
3043    /// * `recurse` - If true, includes overrides from all descendant containers
3044    pub fn iter_overrides(&self, recurse: bool) -> impl Iterator<Item = (&Self, &ConfigOverride)> {
3045        let mut iter: Box<dyn Iterator<Item = (&Self, &ConfigOverride)>> =
3046            if self.overrides.is_empty() {
3047                Box::new(std::iter::empty())
3048            } else {
3049                Box::new(self.overrides.iter().map(move |x| (self, x)))
3050            };
3051
3052        if recurse {
3053            for child in &self.children {
3054                iter = Box::new(iter.chain(child.iter_overrides(true)));
3055            }
3056        }
3057
3058        iter
3059    }
3060
3061    /// Creates a breadth-first search iterator for traversing the container tree.
3062    ///
3063    /// Returns a `BfsPaths` structure that can be used to traverse containers level by level.
3064    #[must_use]
3065    pub fn bfs(&self) -> BfsPaths {
3066        // Collect nodes in pre-order, recording their path
3067        fn collect_paths(
3068            node: &Container,
3069            path: &[usize],
3070            paths: &mut Vec<Vec<usize>>,
3071            levels: &mut Vec<Vec<usize>>,
3072        ) {
3073            if !node.children.is_empty() {
3074                // Store the path to this node
3075                paths.push(path.to_owned());
3076
3077                // Add this node's index to the appropriate level
3078                let level = path.len(); // Path length = level + 1 (root is at index 0)
3079                if levels.len() <= level {
3080                    levels.resize(level + 1, Vec::new());
3081                }
3082                levels[level].push(paths.len() - 1);
3083                // Process children
3084                for (i, child) in node.children.iter().enumerate() {
3085                    let mut child_path = path.to_owned();
3086                    child_path.push(i);
3087                    collect_paths(child, &child_path, paths, levels);
3088                }
3089            }
3090        }
3091
3092        // Collect nodes by level
3093        let mut levels: Vec<Vec<usize>> = Vec::new();
3094
3095        // Start by collecting all paths to nodes
3096        let mut paths: Vec<Vec<usize>> = Vec::new();
3097        collect_paths(self, &[], &mut paths, &mut levels);
3098
3099        BfsPaths { levels, paths }
3100    }
3101
3102    /// Performs a breadth-first search traversal with a visitor function.
3103    ///
3104    /// Calls the visitor function for each container in breadth-first order.
3105    #[must_use]
3106    pub fn bfs_visit(&self, mut visitor: impl FnMut(&Self)) -> BfsPaths {
3107        // Collect nodes in pre-order, recording their path
3108        fn collect_paths(
3109            node: &Container,
3110            path: &[usize],
3111            paths: &mut Vec<Vec<usize>>,
3112            levels: &mut Vec<Vec<usize>>,
3113            visitor: &mut impl FnMut(&Container),
3114        ) {
3115            if !node.children.is_empty() {
3116                // Store the path to this node
3117                paths.push(path.to_owned());
3118
3119                // Add this node's index to the appropriate level
3120                let level = path.len(); // Path length = level + 1 (root is at index 0)
3121                if levels.len() <= level {
3122                    levels.resize(level + 1, Vec::new());
3123                }
3124                levels[level].push(paths.len() - 1);
3125                // Process children
3126                for (i, child) in node.children.iter().enumerate() {
3127                    visitor(child);
3128                    let mut child_path = path.to_owned();
3129                    child_path.push(i);
3130                    collect_paths(child, &child_path, paths, levels, visitor);
3131                }
3132            }
3133        }
3134
3135        // Collect nodes by level
3136        let mut levels: Vec<Vec<usize>> = Vec::new();
3137
3138        // Start by collecting all paths to nodes
3139        let mut paths: Vec<Vec<usize>> = Vec::new();
3140
3141        visitor(self);
3142        collect_paths(self, &[], &mut paths, &mut levels, &mut visitor);
3143
3144        BfsPaths { levels, paths }
3145    }
3146
3147    /// Performs a breadth-first search traversal with a mutable visitor function.
3148    ///
3149    /// Calls the visitor function for each container in breadth-first order, allowing mutation.
3150    #[must_use]
3151    pub fn bfs_visit_mut(&mut self, mut visitor: impl FnMut(&mut Self)) -> BfsPaths {
3152        // Collect nodes in pre-order, recording their path
3153        fn collect_paths(
3154            node: &mut Container,
3155            path: &[usize],
3156            paths: &mut Vec<Vec<usize>>,
3157            levels: &mut Vec<Vec<usize>>,
3158            visitor: &mut impl FnMut(&mut Container),
3159        ) {
3160            if !node.children.is_empty() {
3161                // Store the path to this node
3162                paths.push(path.to_owned());
3163
3164                // Add this node's index to the appropriate level
3165                let level = path.len(); // Path length = level + 1 (root is at index 0)
3166                if levels.len() <= level {
3167                    levels.resize(level + 1, Vec::new());
3168                }
3169                levels[level].push(paths.len() - 1);
3170                // Process children
3171                for (i, child) in node.children.iter_mut().enumerate() {
3172                    visitor(child);
3173                    let mut child_path = path.to_owned();
3174                    child_path.push(i);
3175                    collect_paths(child, &child_path, paths, levels, visitor);
3176                }
3177            }
3178        }
3179
3180        // Collect nodes by level
3181        let mut levels: Vec<Vec<usize>> = Vec::new();
3182
3183        // Start by collecting all paths to nodes
3184        let mut paths: Vec<Vec<usize>> = Vec::new();
3185
3186        visitor(self);
3187        collect_paths(self, &[], &mut paths, &mut levels, &mut visitor);
3188
3189        BfsPaths { levels, paths }
3190    }
3191}
3192
3193impl From<&Container> for BfsPaths {
3194    fn from(root: &Container) -> Self {
3195        root.bfs()
3196    }
3197}
3198
3199/// Breadth-first search traversal paths for container trees.
3200///
3201/// Stores level-ordered paths for efficient tree traversal.
3202pub struct BfsPaths {
3203    levels: Vec<Vec<usize>>,
3204    paths: Vec<Vec<usize>>,
3205}
3206
3207impl BfsPaths {
3208    /// Traverses containers in breadth-first order using the visitor function.
3209    pub fn traverse(&self, root: &Container, mut visitor: impl FnMut(&Container)) {
3210        // Follow paths to apply visitor to each node
3211        for level_nodes in &self.levels {
3212            for &node_idx in level_nodes {
3213                let path = &self.paths[node_idx];
3214
3215                // Follow the path to find the node
3216                let mut current = root;
3217
3218                for &child_idx in path {
3219                    current = &current.children[child_idx];
3220                }
3221
3222                visitor(current);
3223            }
3224        }
3225    }
3226
3227    /// Traverses containers in breadth-first order using the mutable visitor function.
3228    pub fn traverse_mut(&self, root: &mut Container, mut visitor: impl FnMut(&mut Container)) {
3229        // Follow paths to apply visitor to each node
3230        for level_nodes in &self.levels {
3231            for &node_idx in level_nodes {
3232                let path = &self.paths[node_idx];
3233
3234                // Follow the path to find the node
3235                let mut current = &mut *root;
3236
3237                for &child_idx in path {
3238                    current = &mut current.children[child_idx];
3239                }
3240
3241                visitor(current);
3242            }
3243        }
3244    }
3245
3246    /// Traverses containers in breadth-first order, propagating parent data.
3247    ///
3248    /// The visitor function receives references to both the current container and accumulated parent data.
3249    pub fn traverse_with_parents<R: Clone>(
3250        &self,
3251        inclusive: bool,
3252        initial: R,
3253        root: &Container,
3254        mut parent: impl FnMut(&Container, R) -> R,
3255        mut visitor: impl FnMut(&Container, R),
3256    ) {
3257        // Follow paths to apply visitor to each node
3258        for level_nodes in &self.levels {
3259            for &node_idx in level_nodes {
3260                let path = &self.paths[node_idx];
3261
3262                // Follow the path to find the node
3263                let mut current = root;
3264                let mut data = initial.clone();
3265
3266                for &child_idx in path {
3267                    data = parent(current, data);
3268                    current = &current.children[child_idx];
3269                }
3270
3271                if inclusive {
3272                    data = parent(current, data);
3273                }
3274
3275                visitor(current, data);
3276            }
3277        }
3278    }
3279
3280    /// Traverses containers in breadth-first order, propagating parent data with mutable access.
3281    ///
3282    /// The visitor function receives mutable references to both the current container and accumulated parent data.
3283    pub fn traverse_with_parents_mut<R: Clone>(
3284        &self,
3285        inclusive: bool,
3286        initial: R,
3287        root: &mut Container,
3288        mut parent: impl FnMut(&mut Container, R) -> R,
3289        mut visitor: impl FnMut(&mut Container, R),
3290    ) {
3291        // Follow paths to apply visitor to each node
3292        for level_nodes in &self.levels {
3293            for &node_idx in level_nodes {
3294                let path = &self.paths[node_idx];
3295
3296                // Follow the path to find the node
3297                let mut current = &mut *root;
3298                let mut data = initial.clone();
3299
3300                for &child_idx in path {
3301                    data = parent(current, data);
3302                    current = &mut current.children[child_idx];
3303                }
3304
3305                if inclusive {
3306                    data = parent(current, data);
3307                }
3308
3309                visitor(current, data);
3310            }
3311        }
3312    }
3313
3314    /// Traverses containers in breadth-first order, propagating parent data by reference.
3315    ///
3316    /// Similar to `traverse_with_parents` but doesn't require `Clone`.
3317    pub fn traverse_with_parents_ref<R>(
3318        &self,
3319        inclusive: bool,
3320        data: &mut R,
3321        root: &Container,
3322        mut parent: impl FnMut(&Container, &mut R),
3323        mut visitor: impl FnMut(&Container, &mut R),
3324    ) {
3325        // Follow paths to apply visitor to each node
3326        for level_nodes in &self.levels {
3327            for &node_idx in level_nodes {
3328                let path = &self.paths[node_idx];
3329
3330                // Follow the path to find the node
3331                let mut current = root;
3332
3333                for &child_idx in path {
3334                    parent(current, data);
3335                    current = &current.children[child_idx];
3336                }
3337
3338                if inclusive {
3339                    parent(current, data);
3340                }
3341
3342                visitor(current, data);
3343            }
3344        }
3345    }
3346
3347    /// Traverses containers in breadth-first order, propagating parent data by reference with mutable container access.
3348    pub fn traverse_with_parents_ref_mut<R>(
3349        &self,
3350        inclusive: bool,
3351        data: &mut R,
3352        root: &mut Container,
3353        mut parent: impl FnMut(&mut Container, &mut R),
3354        mut visitor: impl FnMut(&mut Container, &mut R),
3355    ) {
3356        // Follow paths to apply visitor to each node
3357        for level_nodes in &self.levels {
3358            for &node_idx in level_nodes {
3359                let path = &self.paths[node_idx];
3360
3361                // Follow the path to find the node
3362                let mut current = &mut *root;
3363
3364                for &child_idx in path {
3365                    parent(current, data);
3366                    current = &mut current.children[child_idx];
3367                }
3368
3369                if inclusive {
3370                    parent(current, data);
3371                }
3372
3373                visitor(current, data);
3374            }
3375        }
3376    }
3377
3378    /// Traverses containers in reverse breadth-first order (bottom-up).
3379    pub fn traverse_rev(&self, root: &Container, mut visitor: impl FnMut(&Container)) {
3380        // Follow paths to apply visitor to each node
3381        for level_nodes in self.levels.iter().rev() {
3382            for &node_idx in level_nodes {
3383                let path = &self.paths[node_idx];
3384
3385                // Follow the path to find the node
3386                let mut current = root;
3387
3388                for &child_idx in path {
3389                    current = &current.children[child_idx];
3390                }
3391
3392                visitor(current);
3393            }
3394        }
3395    }
3396
3397    /// Traverses containers in reverse breadth-first order with mutable access.
3398    pub fn traverse_rev_mut(&self, root: &mut Container, mut visitor: impl FnMut(&mut Container)) {
3399        // Follow paths to apply visitor to each node
3400        for level_nodes in self.levels.iter().rev() {
3401            for &node_idx in level_nodes {
3402                let path = &self.paths[node_idx];
3403
3404                // Follow the path to find the node
3405                let mut current = &mut *root;
3406
3407                for &child_idx in path {
3408                    current = &mut current.children[child_idx];
3409                }
3410
3411                visitor(current);
3412            }
3413        }
3414    }
3415
3416    /// Traverses containers in reverse breadth-first order, propagating parent data.
3417    pub fn traverse_rev_with_parents<R: Clone>(
3418        &self,
3419        inclusive: bool,
3420        initial: R,
3421        root: &Container,
3422        mut parent: impl FnMut(&Container, R) -> R,
3423        mut visitor: impl FnMut(&Container, R),
3424    ) {
3425        // Follow paths to apply visitor to each node
3426        for level_nodes in self.levels.iter().rev() {
3427            for &node_idx in level_nodes {
3428                let path = &self.paths[node_idx];
3429
3430                // Follow the path to find the node
3431                let mut current = root;
3432                let mut data = initial.clone();
3433
3434                for &child_idx in path {
3435                    data = parent(current, data);
3436                    current = &current.children[child_idx];
3437                }
3438
3439                if inclusive {
3440                    data = parent(current, data);
3441                }
3442
3443                visitor(current, data);
3444            }
3445        }
3446    }
3447
3448    /// Traverses containers in reverse breadth-first order, propagating parent data with mutable access.
3449    pub fn traverse_rev_with_parents_mut<R: Clone>(
3450        &self,
3451        inclusive: bool,
3452        initial: R,
3453        root: &mut Container,
3454        mut parent: impl FnMut(&mut Container, R) -> R,
3455        mut visitor: impl FnMut(&mut Container, R),
3456    ) {
3457        // Follow paths to apply visitor to each node
3458        for level_nodes in self.levels.iter().rev() {
3459            for &node_idx in level_nodes {
3460                let path = &self.paths[node_idx];
3461
3462                // Follow the path to find the node
3463                let mut current = &mut *root;
3464                let mut data = initial.clone();
3465
3466                for &child_idx in path {
3467                    data = parent(current, data);
3468                    current = &mut current.children[child_idx];
3469                }
3470
3471                if inclusive {
3472                    data = parent(current, data);
3473                }
3474
3475                visitor(current, data);
3476            }
3477        }
3478    }
3479
3480    /// Traverses containers in reverse breadth-first order, propagating parent data by reference.
3481    pub fn traverse_rev_with_parents_ref<R>(
3482        &self,
3483        inclusive: bool,
3484        initial: R,
3485        root: &Container,
3486        mut parent: impl FnMut(&Container, R) -> R,
3487        mut visitor: impl FnMut(&Container, &R),
3488    ) {
3489        let mut data = initial;
3490
3491        // Follow paths to apply visitor to each node
3492        for level_nodes in self.levels.iter().rev() {
3493            for &node_idx in level_nodes {
3494                let path = &self.paths[node_idx];
3495
3496                // Follow the path to find the node
3497                let mut current = root;
3498
3499                for &child_idx in path {
3500                    data = parent(current, data);
3501                    current = &current.children[child_idx];
3502                }
3503
3504                if inclusive {
3505                    data = parent(current, data);
3506                }
3507
3508                visitor(current, &data);
3509            }
3510        }
3511    }
3512
3513    /// Traverses containers in reverse breadth-first order, propagating parent data by reference with mutable container access.
3514    pub fn traverse_rev_with_parents_ref_mut<R>(
3515        &self,
3516        inclusive: bool,
3517        initial: R,
3518        root: &mut Container,
3519        mut parent: impl FnMut(&mut Container, R) -> R,
3520        mut visitor: impl FnMut(&mut Container, &R),
3521    ) {
3522        let mut data = initial;
3523
3524        // Follow paths to apply visitor to each node
3525        for level_nodes in self.levels.iter().rev() {
3526            for &node_idx in level_nodes {
3527                let path = &self.paths[node_idx];
3528
3529                // Follow the path to find the node
3530                let mut current = &mut *root;
3531
3532                for &child_idx in path {
3533                    data = parent(current, data);
3534                    current = &mut current.children[child_idx];
3535                }
3536
3537                if inclusive {
3538                    data = parent(current, data);
3539                }
3540
3541                visitor(current, &data);
3542            }
3543        }
3544    }
3545}
3546
3547impl From<Vec<Self>> for Container {
3548    fn from(value: Vec<Self>) -> Self {
3549        Self {
3550            element: Element::Div,
3551            children: value,
3552            ..Default::default()
3553        }
3554    }
3555}
3556
3557fn visible_elements(elements: &[Container]) -> impl Iterator<Item = &Container> {
3558    elements.iter().filter(|x| x.hidden != Some(true))
3559}
3560
3561fn visible_elements_mut(elements: &mut [Container]) -> impl Iterator<Item = &mut Container> {
3562    elements.iter_mut().filter(|x| x.hidden != Some(true))
3563}
3564
3565fn relative_positioned_elements(elements: &[Container]) -> impl Iterator<Item = &Container> {
3566    visible_elements(elements).filter(|x| x.position.is_none_or(Position::is_relative))
3567}
3568
3569fn relative_positioned_elements_mut(
3570    elements: &mut [Container],
3571) -> impl Iterator<Item = &mut Container> {
3572    visible_elements_mut(elements).filter(|x| x.position.is_none_or(Position::is_relative))
3573}
3574
3575fn absolute_positioned_elements(elements: &[Container]) -> impl Iterator<Item = &Container> {
3576    visible_elements(elements).filter(|x| x.position == Some(Position::Absolute))
3577}
3578
3579fn absolute_positioned_elements_mut(
3580    elements: &mut [Container],
3581) -> impl Iterator<Item = &mut Container> {
3582    visible_elements_mut(elements).filter(|x| x.position == Some(Position::Absolute))
3583}
3584
3585fn fixed_positioned_elements(elements: &[Container]) -> impl Iterator<Item = &Container> {
3586    visible_elements(elements).filter(|x| x.is_fixed())
3587}
3588
3589fn fixed_positioned_elements_mut(
3590    elements: &mut [Container],
3591) -> impl Iterator<Item = &mut Container> {
3592    visible_elements_mut(elements).filter(|x| x.is_fixed())
3593}
3594
3595impl Container {
3596    /// Checks if this container has fixed or sticky positioning.
3597    ///
3598    /// Returns `true` if the container's position is set to [`Position::Fixed`] or [`Position::Sticky`],
3599    /// which removes it from the normal document flow and positions it relative to the viewport or
3600    /// scroll container.
3601    #[must_use]
3602    pub const fn is_fixed(&self) -> bool {
3603        matches!(self.position, Some(Position::Fixed | Position::Sticky))
3604    }
3605
3606    /// Checks if this container contains raw HTML content.
3607    ///
3608    /// Returns `true` if the element is [`Element::Raw`], which means it contains unescaped HTML
3609    /// that will be rendered directly without further processing.
3610    ///
3611    /// For escaped text content, see [`Self::is_text`].
3612    #[must_use]
3613    pub const fn is_raw(&self) -> bool {
3614        matches!(self.element, Element::Raw { .. })
3615    }
3616
3617    /// Checks if this container contains escaped text content.
3618    ///
3619    /// Returns `true` if the element is [`Element::Text`], which means it contains text
3620    /// that will be HTML-escaped when rendered to prevent XSS attacks.
3621    ///
3622    /// For raw unescaped HTML content, see [`Self::is_raw`].
3623    #[must_use]
3624    pub const fn is_text(&self) -> bool {
3625        matches!(self.element, Element::Text { .. })
3626    }
3627}
3628
3629#[cfg_attr(feature = "profiling", profiling::all_functions)]
3630impl Container {
3631    /// Checks if this container is visible (not hidden).
3632    #[must_use]
3633    pub fn is_visible(&self) -> bool {
3634        self.hidden != Some(true)
3635    }
3636
3637    /// Checks if this container is hidden.
3638    #[must_use]
3639    pub fn is_hidden(&self) -> bool {
3640        self.hidden == Some(true)
3641    }
3642
3643    /// Checks if this container is a span element.
3644    #[must_use]
3645    pub fn is_span(&self) -> bool {
3646        matches!(
3647            self.element,
3648            Element::Raw { .. }
3649                | Element::Text { .. }
3650                | Element::Span
3651                | Element::Anchor { .. }
3652                | Element::Input { .. }
3653                | Element::Button { .. }
3654                | Element::Image { .. }
3655        ) && self.children.iter().all(Self::is_span)
3656    }
3657
3658    /// Checks if this container uses flexbox layout.
3659    #[must_use]
3660    pub fn is_flex_container(&self) -> bool {
3661        self.direction == LayoutDirection::Row
3662            || self.justify_content.is_some()
3663            || self.align_items.is_some()
3664            || self.children.iter().any(|x| x.flex.is_some())
3665            || self.column_gap.is_some()
3666    }
3667
3668    /// Returns an iterator over visible child elements.
3669    pub fn visible_elements(&self) -> impl Iterator<Item = &Self> {
3670        visible_elements(&self.children)
3671    }
3672
3673    /// Returns a mutable iterator over visible child elements.
3674    pub fn visible_elements_mut(&mut self) -> impl Iterator<Item = &mut Self> {
3675        visible_elements_mut(&mut self.children)
3676    }
3677
3678    /// Returns an iterator over relatively positioned child elements.
3679    pub fn relative_positioned_elements(&self) -> impl Iterator<Item = &Self> {
3680        relative_positioned_elements(&self.children)
3681    }
3682
3683    /// Returns a mutable iterator over relatively positioned child elements.
3684    pub fn relative_positioned_elements_mut(&mut self) -> impl Iterator<Item = &mut Self> {
3685        relative_positioned_elements_mut(&mut self.children)
3686    }
3687
3688    /// Returns an iterator over absolutely positioned child elements.
3689    pub fn absolute_positioned_elements(&self) -> impl Iterator<Item = &Self> {
3690        absolute_positioned_elements(&self.children)
3691    }
3692
3693    /// Returns a mutable iterator over absolutely positioned child elements.
3694    pub fn absolute_positioned_elements_mut(&mut self) -> impl Iterator<Item = &mut Self> {
3695        absolute_positioned_elements_mut(&mut self.children)
3696    }
3697
3698    /// Returns an iterator over fixed positioned child elements.
3699    pub fn fixed_positioned_elements(&self) -> impl Iterator<Item = &Self> {
3700        fixed_positioned_elements(&self.children)
3701    }
3702
3703    /// Returns a mutable iterator over fixed positioned child elements.
3704    pub fn fixed_positioned_elements_mut(&mut self) -> impl Iterator<Item = &mut Self> {
3705        fixed_positioned_elements_mut(&mut self.children)
3706    }
3707
3708    /// Finds a descendant container by its numeric ID.
3709    #[must_use]
3710    pub fn find_element_by_id(&self, id: usize) -> Option<&Self> {
3711        if self.id == id {
3712            return Some(self);
3713        }
3714        self.children.iter().find_map(|x| x.find_element_by_id(id))
3715    }
3716
3717    /// Finds a descendant container by its numeric ID (mutable).
3718    #[must_use]
3719    pub fn find_element_by_id_mut(&mut self, id: usize) -> Option<&mut Self> {
3720        if self.id == id {
3721            return Some(self);
3722        }
3723        self.children
3724            .iter_mut()
3725            .find_map(|x| x.find_element_by_id_mut(id))
3726    }
3727
3728    /// Finds a descendant container by its string ID.
3729    #[must_use]
3730    pub fn find_element_by_str_id(&self, str_id: &str) -> Option<&Self> {
3731        if self.str_id.as_ref().is_some_and(|x| x == str_id) {
3732            return Some(self);
3733        }
3734        self.children
3735            .iter()
3736            .find_map(|x| x.find_element_by_str_id(str_id))
3737    }
3738
3739    /// Finds a descendant container by CSS class name.
3740    #[must_use]
3741    pub fn find_element_by_class(&self, class: &str) -> Option<&Self> {
3742        if self.classes.iter().any(|x| x == class) {
3743            return Some(self);
3744        }
3745        self.children
3746            .iter()
3747            .find_map(|x| x.find_element_by_class(class))
3748    }
3749
3750    /// Finds a descendant container by its string ID (mutable).
3751    #[must_use]
3752    pub fn find_element_by_str_id_mut(&mut self, str_id: &str) -> Option<&mut Self> {
3753        if self.str_id.as_ref().is_some_and(|x| x == str_id) {
3754            return Some(self);
3755        }
3756        self.children
3757            .iter_mut()
3758            .find_map(|x| x.find_element_by_str_id_mut(str_id))
3759    }
3760
3761    /// Finds the parent container of this container within the root tree.
3762    #[must_use]
3763    pub fn find_parent<'a>(&self, root: &'a mut Self) -> Option<&'a Self> {
3764        if root.children.iter().any(|x| x.id == self.id) {
3765            Some(root)
3766        } else {
3767            root.children
3768                .iter()
3769                .find(|x| x.children.iter().any(|x| x.id == self.id))
3770        }
3771    }
3772
3773    /// Finds the parent container of a child with the given numeric ID.
3774    #[must_use]
3775    pub fn find_parent_by_id(&self, id: usize) -> Option<&Self> {
3776        if self.children.iter().any(|x| x.id == id) {
3777            Some(self)
3778        } else {
3779            self.children.iter().find_map(|x| x.find_parent_by_id(id))
3780        }
3781    }
3782
3783    /// Finds the parent container of a child with the given numeric ID (mutable).
3784    #[must_use]
3785    pub fn find_parent_by_id_mut(&mut self, id: usize) -> Option<&mut Self> {
3786        if self.children.iter().any(|x| x.id == id) {
3787            Some(self)
3788        } else {
3789            self.children
3790                .iter_mut()
3791                .find_map(|x| x.find_parent_by_id_mut(id))
3792        }
3793    }
3794
3795    /// Finds the parent container of a child with the given string ID (mutable).
3796    #[must_use]
3797    pub fn find_parent_by_str_id_mut(&mut self, id: &str) -> Option<&mut Self> {
3798        if self
3799            .children
3800            .iter()
3801            .filter_map(|x| x.str_id.as_ref())
3802            .map(String::as_str)
3803            .any(|x| x == id)
3804        {
3805            Some(self)
3806        } else {
3807            self.children
3808                .iter_mut()
3809                .find_map(|x| x.find_parent_by_str_id_mut(id))
3810        }
3811    }
3812
3813    /// Replaces this container with multiple elements in the tree.
3814    ///
3815    /// Finds this container's parent in the tree and replaces this container with the provided
3816    /// replacement elements. Returns the original container that was replaced.
3817    ///
3818    /// # Panics
3819    ///
3820    /// * If the `Container` is the root node
3821    /// * If the `Container` is not properly attached to the tree
3822    #[must_use]
3823    pub fn replace_with_elements(&mut self, replacement: Vec<Self>, root: &mut Self) -> Self {
3824        let Some(parent) = root.find_parent_by_id_mut(self.id) else {
3825            panic!("Cannot replace the root node with multiple elements");
3826        };
3827
3828        let index = parent
3829            .children
3830            .iter()
3831            .enumerate()
3832            .find_map(|(i, x)| if x.id == self.id { Some(i) } else { None })
3833            .unwrap_or_else(|| panic!("Container is not attached properly to tree"));
3834
3835        let original = parent.children.remove(index);
3836
3837        for (i, element) in replacement.into_iter().enumerate() {
3838            parent.children.insert(index + i, element);
3839        }
3840
3841        original
3842    }
3843
3844    /// Replaces all children of a container identified by numeric ID.
3845    ///
3846    /// Finds the container with the given ID and replaces all of its children with the provided
3847    /// replacement elements. Returns the original children that were replaced, or `None` if
3848    /// no container with the given ID exists.
3849    ///
3850    /// # Panics
3851    ///
3852    /// * If the `Container` is not properly attached to the tree
3853    pub fn replace_id_children_with_elements(
3854        &mut self,
3855        replacement: Vec<Self>,
3856        id: usize,
3857    ) -> Option<Vec<Self>> {
3858        let parent = self.find_element_by_id_mut(id)?;
3859
3860        let original = parent.children.drain(..).collect::<Vec<_>>();
3861
3862        for element in replacement {
3863            parent.children.push(element);
3864        }
3865
3866        Some(original)
3867    }
3868
3869    /// Replaces all children of a container identified by string ID.
3870    ///
3871    /// Finds the container with the given string ID and replaces all of its children with the
3872    /// provided replacement elements. Returns the original children that were replaced, or
3873    /// `None` if no container with the given string ID exists.
3874    ///
3875    /// # Panics
3876    ///
3877    /// * If the `Container` is not properly attached to the tree
3878    pub fn replace_str_id_children_with_elements(
3879        &mut self,
3880        replacement: Vec<Self>,
3881        id: &str,
3882    ) -> Option<Vec<Self>> {
3883        let parent = self.find_element_by_str_id_mut(id)?;
3884
3885        let original = parent.children.drain(..).collect::<Vec<_>>();
3886
3887        for element in replacement {
3888            parent.children.push(element);
3889        }
3890
3891        Some(original)
3892    }
3893
3894    /// Replaces all children of a container by numeric ID and recalculates layout (requires `layout` feature).
3895    ///
3896    /// Finds the container with the given ID, replaces all of its children with the provided
3897    /// replacement elements, and then performs a partial layout recalculation. Returns `true`
3898    /// if the container was found and replaced, `false` otherwise.
3899    ///
3900    /// # Panics
3901    ///
3902    /// * If the `Container` is not properly attached to the tree
3903    #[cfg(feature = "layout")]
3904    pub fn replace_id_children_with_elements_calc(
3905        &mut self,
3906        calculator: &impl layout::Calc,
3907        replacement: Vec<Self>,
3908        id: usize,
3909    ) -> bool {
3910        let Some(parent_id) = self.find_element_by_id(id).map(|x| x.id) else {
3911            return false;
3912        };
3913
3914        self.replace_id_children_with_elements(replacement, id);
3915
3916        self.partial_calc(calculator, parent_id);
3917
3918        true
3919    }
3920
3921    /// Replaces all children of a container by string ID and recalculates layout (requires `layout` feature).
3922    ///
3923    /// Finds the container with the given string ID, replaces all of its children with the
3924    /// provided replacement elements, and then performs a partial layout recalculation. Returns
3925    /// `true` if the container was found and replaced, `false` otherwise.
3926    ///
3927    /// # Panics
3928    ///
3929    /// * If the `Container` is not properly attached to the tree
3930    #[cfg(feature = "layout")]
3931    pub fn replace_str_id_children_with_elements_calc(
3932        &mut self,
3933        calculator: &impl layout::Calc,
3934        replacement: Vec<Self>,
3935        id: &str,
3936    ) -> bool {
3937        let Some(parent_id) = self.find_element_by_str_id(id).map(|x| x.id) else {
3938            return false;
3939        };
3940
3941        self.replace_str_id_children_with_elements(replacement, id);
3942
3943        self.partial_calc(calculator, parent_id);
3944
3945        true
3946    }
3947
3948    /// Replaces a container identified by numeric ID with multiple elements.
3949    ///
3950    /// Finds the container with the given ID and replaces it with the provided replacement
3951    /// elements. Returns the original container that was replaced, or `None` if no container
3952    /// with the given ID exists.
3953    ///
3954    /// # Panics
3955    ///
3956    /// * If the `Container` is not properly attached to the tree
3957    pub fn replace_id_with_elements(&mut self, replacement: Vec<Self>, id: usize) -> Option<Self> {
3958        let parent = self.find_parent_by_id_mut(id)?;
3959
3960        let index = parent
3961            .children
3962            .iter()
3963            .enumerate()
3964            .find_map(|(i, x)| if x.id == id { Some(i) } else { None })?;
3965
3966        let original = parent.children.remove(index);
3967
3968        for (i, element) in replacement.into_iter().enumerate() {
3969            parent.children.insert(index + i, element);
3970        }
3971
3972        Some(original)
3973    }
3974
3975    /// Replaces a container by numeric ID with multiple elements and recalculates layout (requires `layout` feature).
3976    ///
3977    /// Finds the container with the given ID, replaces it with the provided replacement elements,
3978    /// and then performs a partial layout recalculation. Returns `true` if the container was
3979    /// found and replaced, `false` otherwise.
3980    ///
3981    /// # Panics
3982    ///
3983    /// * If the `Container` is not properly attached to the tree
3984    #[cfg(feature = "layout")]
3985    pub fn replace_id_with_elements_calc(
3986        &mut self,
3987        calculator: &impl layout::Calc,
3988        replacement: Vec<Self>,
3989        id: usize,
3990    ) -> bool {
3991        let Some(parent_id) = self.find_parent_by_id_mut(id).map(|x| x.id) else {
3992            return false;
3993        };
3994
3995        self.replace_id_with_elements(replacement, id);
3996
3997        self.partial_calc(calculator, parent_id);
3998
3999        true
4000    }
4001
4002    /// Replaces a container identified by string ID with multiple elements.
4003    ///
4004    /// Finds the container with the given string ID and replaces it with the provided replacement
4005    /// elements. Returns the original container that was replaced, or `None` if no container
4006    /// with the given string ID exists.
4007    ///
4008    /// # Panics
4009    ///
4010    /// * If the `Container` is not properly attached to the tree
4011    pub fn replace_str_id_with_elements(
4012        &mut self,
4013        replacement: Vec<Self>,
4014        id: &str,
4015    ) -> Option<Self> {
4016        let parent = self.find_parent_by_str_id_mut(id)?;
4017
4018        let index = parent
4019            .children
4020            .iter()
4021            .enumerate()
4022            .find_map(|(i, x)| {
4023                if x.str_id.as_ref().is_some_and(|x| x.as_str() == id) {
4024                    Some(i)
4025                } else {
4026                    None
4027                }
4028            })
4029            .unwrap_or_else(|| panic!("Container is not attached properly to tree"));
4030
4031        let original = parent.children.remove(index);
4032
4033        for (i, element) in replacement.into_iter().enumerate() {
4034            parent.children.insert(index + i, element);
4035        }
4036
4037        Some(original)
4038    }
4039
4040    /// Replaces a container by string ID with multiple elements and recalculates layout (requires `layout` feature).
4041    ///
4042    /// Finds the container with the given string ID, replaces it with the provided replacement
4043    /// elements, and then performs a partial layout recalculation. Returns the replaced container
4044    /// if found, or `None` otherwise.
4045    ///
4046    /// # Panics
4047    ///
4048    /// * If the `Container` is not properly attached to the tree
4049    #[cfg(feature = "layout")]
4050    pub fn replace_str_id_with_elements_calc(
4051        &mut self,
4052        calculator: &impl layout::Calc,
4053        replacement: Vec<Self>,
4054        id: &str,
4055    ) -> Option<Self> {
4056        let parent_id = self.find_parent_by_str_id_mut(id)?.id;
4057
4058        let element = self.replace_str_id_with_elements(replacement, id);
4059
4060        self.partial_calc(calculator, parent_id);
4061
4062        element
4063    }
4064
4065    /// Performs a partial layout calculation starting from the specified container (requires `layout` feature).
4066    #[cfg(feature = "layout")]
4067    pub fn partial_calc(&mut self, calculator: &impl layout::Calc, id: usize) {
4068        let Some(parent) = self.find_parent_by_id_mut(id) else {
4069            return;
4070        };
4071
4072        if calculator.calc(parent) {
4073            calculator.calc(self);
4074        }
4075    }
4076}
4077
4078#[derive(Default, Clone, Debug, PartialEq)]
4079/// HTML element type with associated properties.
4080///
4081/// Represents different HTML elements that can be used in a container,
4082/// with element-specific properties like image sources, anchor hrefs, etc.
4083pub enum Element {
4084    /// Generic div container (default).
4085    #[default]
4086    Div,
4087    /// Raw HTML content (will NOT be escaped when rendered).
4088    ///
4089    /// Use this for intentionally injecting HTML markup. For regular text content
4090    /// that should be safely escaped, use [`Element::Text`] instead.
4091    Raw {
4092        /// The raw HTML value (rendered without escaping).
4093        value: String,
4094    },
4095    /// Escaped text content (HTML entities will be escaped when rendered).
4096    ///
4097    /// This is the safe default for displaying user-provided or dynamic text content.
4098    /// Characters like `<`, `>`, and `&` will be escaped to prevent XSS attacks.
4099    Text {
4100        /// The text value (will be HTML-escaped in HTML renderer).
4101        value: String,
4102    },
4103    /// Aside element for sidebar content.
4104    Aside,
4105    /// Main content element.
4106    Main,
4107    /// Header element for page or section headers.
4108    Header,
4109    /// Footer element for page or section footers.
4110    Footer,
4111    /// Section element for thematic grouping of content.
4112    Section,
4113    /// Form element for user input.
4114    Form {
4115        /// Form submission URL.
4116        action: Option<String>,
4117        /// HTTP method for form submission (GET, POST, etc.).
4118        method: Option<String>,
4119    },
4120    /// Inline span element for text styling.
4121    Span,
4122    /// Input element for form fields.
4123    Input {
4124        /// The input type and configuration.
4125        input: Input,
4126        /// Form field name for submission.
4127        name: Option<String>,
4128        /// Whether the input should automatically receive focus.
4129        autofocus: Option<bool>,
4130    },
4131    /// Button element for user interaction.
4132    Button {
4133        /// Button type (submit, reset, button).
4134        r#type: Option<String>,
4135    },
4136    /// Image element with responsive loading support.
4137    Image {
4138        /// Image source URL.
4139        source: Option<String>,
4140        /// Alternative text for accessibility.
4141        alt: Option<String>,
4142        /// How the image should fit its container.
4143        fit: Option<ImageFit>,
4144        /// Responsive image source set.
4145        source_set: Option<String>,
4146        /// Sizes attribute for responsive images.
4147        sizes: Option<Number>,
4148        /// Loading strategy (lazy, eager).
4149        loading: Option<ImageLoading>,
4150    },
4151    /// Anchor element for hyperlinks.
4152    Anchor {
4153        /// Link target behavior (_blank, _self, etc.).
4154        target: Option<LinkTarget>,
4155        /// Link destination URL.
4156        href: Option<String>,
4157    },
4158    /// Heading element (h1-h6).
4159    Heading {
4160        /// Heading level (1-6).
4161        size: HeaderSize,
4162    },
4163    /// Unordered list element (ul).
4164    UnorderedList,
4165    /// Ordered list element (ol).
4166    OrderedList,
4167    /// List item element (li).
4168    ListItem,
4169    /// Table element for tabular data.
4170    Table,
4171    /// Table head element (thead).
4172    THead,
4173    /// Table header cell element (th).
4174    TH {
4175        /// Row span for multi-row headers.
4176        rows: Option<Number>,
4177        /// Column span for multi-column headers.
4178        columns: Option<Number>,
4179    },
4180    /// Table body element (tbody).
4181    TBody,
4182    /// Table row element (tr).
4183    TR,
4184    /// Table data cell element (td).
4185    TD {
4186        /// Row span for multi-row cells.
4187        rows: Option<Number>,
4188        /// Column span for multi-column cells.
4189        columns: Option<Number>,
4190    },
4191    /// Canvas element for drawing graphics (requires `canvas` feature).
4192    #[cfg(feature = "canvas")]
4193    Canvas,
4194    /// Textarea element for multi-line text input.
4195    Textarea {
4196        /// Current text value.
4197        value: String,
4198        /// Placeholder text when empty.
4199        placeholder: Option<String>,
4200        /// Form field name for submission.
4201        name: Option<String>,
4202        /// Visible number of text rows.
4203        rows: Option<Number>,
4204        /// Visible number of text columns.
4205        cols: Option<Number>,
4206    },
4207    /// Details disclosure element for expandable content.
4208    Details {
4209        /// Whether the details are initially open.
4210        open: Option<bool>,
4211    },
4212    /// Summary element for details disclosure heading.
4213    Summary,
4214    /// Select element for dropdown menus.
4215    Select {
4216        /// Form field name for submission.
4217        name: Option<String>,
4218        /// The value of the currently selected option.
4219        selected: Option<String>,
4220        /// Whether multiple options can be selected.
4221        multiple: Option<bool>,
4222        /// Whether the select is disabled.
4223        disabled: Option<bool>,
4224        /// Whether the select should automatically receive focus.
4225        autofocus: Option<bool>,
4226    },
4227    /// Option element for select menus.
4228    Option {
4229        /// Option value for form submission.
4230        value: Option<String>,
4231        /// Whether this option is disabled.
4232        disabled: Option<bool>,
4233    },
4234}
4235
4236#[derive(Default)]
4237struct Attrs {
4238    values: Vec<(String, String)>,
4239}
4240
4241#[derive(Debug)]
4242/// Represents a value that may have been replaced with a string.
4243///
4244/// Used for tracking template substitutions and variable replacements.
4245pub enum MaybeReplaced<T: std::fmt::Display> {
4246    /// Original unreplaced value.
4247    NotReplaced(T),
4248    /// Value replaced with a string.
4249    Replaced(String),
4250}
4251
4252#[cfg_attr(feature = "profiling", profiling::all_functions)]
4253impl Attrs {
4254    fn new() -> Self {
4255        Self::default()
4256    }
4257
4258    #[allow(unused)]
4259    fn with_attr<K: Into<String>, V: std::fmt::Display + 'static>(
4260        mut self,
4261        name: K,
4262        value: V,
4263    ) -> Self {
4264        self.add(name, value);
4265        self
4266    }
4267
4268    fn with_attr_opt<K: Into<String>, V: std::fmt::Display + 'static>(
4269        mut self,
4270        name: K,
4271        value: Option<V>,
4272    ) -> Self {
4273        self.add_opt(name, value);
4274        self
4275    }
4276
4277    fn to_string_pad_left(&self) -> String {
4278        if self.values.is_empty() {
4279            String::new()
4280        } else {
4281            format!(
4282                " {}",
4283                self.values
4284                    .iter()
4285                    .map(|(name, value)| format!("{name}=\"{value}\""))
4286                    .collect::<Vec<_>>()
4287                    .join(" ")
4288            )
4289        }
4290    }
4291
4292    #[allow(unused)]
4293    fn replace_or_add<V: std::fmt::Display>(&mut self, name: &str, new_value: V) -> Option<String> {
4294        match self.replace(name, new_value) {
4295            MaybeReplaced::NotReplaced(x) => {
4296                self.add(name, x);
4297                None
4298            }
4299            MaybeReplaced::Replaced(x) => Some(x),
4300        }
4301    }
4302
4303    #[allow(unused)]
4304    fn replace<V: std::fmt::Display>(&mut self, name: &str, new_value: V) -> MaybeReplaced<V> {
4305        for (key, value) in &mut self.values {
4306            if key == name {
4307                let mut encoded =
4308                    html_escape::encode_double_quoted_attribute(new_value.to_string().as_str())
4309                        .to_string()
4310                        .replace('\n', "&#10;");
4311
4312                std::mem::swap(value, &mut encoded);
4313
4314                let old_value = encoded;
4315
4316                return MaybeReplaced::Replaced(old_value);
4317            }
4318        }
4319
4320        MaybeReplaced::NotReplaced(new_value)
4321    }
4322
4323    fn add<K: Into<String>, V: std::fmt::Display>(&mut self, name: K, value: V) {
4324        self.values.push((
4325            name.into(),
4326            html_escape::encode_double_quoted_attribute(value.to_string().as_str())
4327                .to_string()
4328                .replace('\n', "&#10;"),
4329        ));
4330    }
4331
4332    fn add_opt<K: Into<String>, V: std::fmt::Display>(&mut self, name: K, value: Option<V>) {
4333        if let Some(value) = value {
4334            self.values.push((
4335                name.into(),
4336                html_escape::encode_double_quoted_attribute(value.to_string().as_str())
4337                    .to_string()
4338                    .replace('\n', "&#10;"),
4339            ));
4340        }
4341    }
4342
4343    #[cfg(feature = "layout")]
4344    fn add_opt_skip_default<K: Into<String>, V: Default + PartialEq + std::fmt::Display>(
4345        &mut self,
4346        name: K,
4347        value: Option<V>,
4348        skip_default: bool,
4349    ) {
4350        if let Some(value) = value {
4351            if skip_default && value == Default::default() {
4352                return;
4353            }
4354            self.values.push((
4355                name.into(),
4356                html_escape::encode_double_quoted_attribute(value.to_string().as_str())
4357                    .to_string()
4358                    .replace('\n', "&#10;"),
4359            ));
4360        }
4361    }
4362}
4363
4364#[cfg_attr(feature = "profiling", profiling::all_functions)]
4365impl Container {
4366    /// Gets the base value for an override item type from the container's fields.
4367    ///
4368    /// This is used during serialization to preserve the container's base value
4369    /// as the default when an override is present.
4370    #[must_use]
4371    #[cfg(any(test, feature = "logic"))]
4372    fn get_base_value_for_override(&self, item: &OverrideItem) -> Option<OverrideItem> {
4373        match item {
4374            OverrideItem::StrId(_) => self.str_id.clone().map(OverrideItem::StrId),
4375            OverrideItem::Classes(_) => Some(OverrideItem::Classes(self.classes.clone())),
4376            OverrideItem::Direction(_) => Some(OverrideItem::Direction(self.direction)),
4377            OverrideItem::OverflowX(_) => Some(OverrideItem::OverflowX(self.overflow_x)),
4378            OverrideItem::OverflowY(_) => Some(OverrideItem::OverflowY(self.overflow_y)),
4379            OverrideItem::GridCellSize(_) => {
4380                self.grid_cell_size.clone().map(OverrideItem::GridCellSize)
4381            }
4382            OverrideItem::JustifyContent(_) => {
4383                self.justify_content.map(OverrideItem::JustifyContent)
4384            }
4385            OverrideItem::AlignItems(_) => self.align_items.map(OverrideItem::AlignItems),
4386            OverrideItem::TextAlign(_) => self.text_align.map(OverrideItem::TextAlign),
4387            OverrideItem::WhiteSpace(_) => self.white_space.map(OverrideItem::WhiteSpace),
4388            OverrideItem::TextDecoration(_) => self
4389                .text_decoration
4390                .clone()
4391                .map(OverrideItem::TextDecoration),
4392            OverrideItem::FontFamily(_) => self.font_family.clone().map(OverrideItem::FontFamily),
4393            OverrideItem::FontWeight(_) => self.font_weight.map(OverrideItem::FontWeight),
4394            OverrideItem::Width(_) => self.width.clone().map(OverrideItem::Width),
4395            OverrideItem::MinWidth(_) => self.min_width.clone().map(OverrideItem::MinWidth),
4396            OverrideItem::MaxWidth(_) => self.max_width.clone().map(OverrideItem::MaxWidth),
4397            OverrideItem::Height(_) => self.height.clone().map(OverrideItem::Height),
4398            OverrideItem::MinHeight(_) => self.min_height.clone().map(OverrideItem::MinHeight),
4399            OverrideItem::MaxHeight(_) => self.max_height.clone().map(OverrideItem::MaxHeight),
4400            OverrideItem::Flex(_) => self.flex.clone().map(OverrideItem::Flex),
4401            OverrideItem::ColumnGap(_) => self.column_gap.clone().map(OverrideItem::ColumnGap),
4402            OverrideItem::RowGap(_) => self.row_gap.clone().map(OverrideItem::RowGap),
4403            OverrideItem::Opacity(_) => self.opacity.clone().map(OverrideItem::Opacity),
4404            OverrideItem::Left(_) => self.left.clone().map(OverrideItem::Left),
4405            OverrideItem::Right(_) => self.right.clone().map(OverrideItem::Right),
4406            OverrideItem::Top(_) => self.top.clone().map(OverrideItem::Top),
4407            OverrideItem::Bottom(_) => self.bottom.clone().map(OverrideItem::Bottom),
4408            OverrideItem::TranslateX(_) => self.translate_x.clone().map(OverrideItem::TranslateX),
4409            OverrideItem::TranslateY(_) => self.translate_y.clone().map(OverrideItem::TranslateY),
4410            OverrideItem::Cursor(_) => self.cursor.map(OverrideItem::Cursor),
4411            OverrideItem::UserSelect(_) => self.user_select.map(OverrideItem::UserSelect),
4412            OverrideItem::OverflowWrap(_) => self.overflow_wrap.map(OverrideItem::OverflowWrap),
4413            OverrideItem::TextOverflow(_) => self.text_overflow.map(OverrideItem::TextOverflow),
4414            OverrideItem::Position(_) => self.position.map(OverrideItem::Position),
4415            OverrideItem::Background(_) => self.background.map(OverrideItem::Background),
4416            OverrideItem::BorderTop(_) => self.border_top.clone().map(OverrideItem::BorderTop),
4417            OverrideItem::BorderRight(_) => {
4418                self.border_right.clone().map(OverrideItem::BorderRight)
4419            }
4420            OverrideItem::BorderBottom(_) => {
4421                self.border_bottom.clone().map(OverrideItem::BorderBottom)
4422            }
4423            OverrideItem::BorderLeft(_) => self.border_left.clone().map(OverrideItem::BorderLeft),
4424            OverrideItem::BorderTopLeftRadius(_) => self
4425                .border_top_left_radius
4426                .clone()
4427                .map(OverrideItem::BorderTopLeftRadius),
4428            OverrideItem::BorderTopRightRadius(_) => self
4429                .border_top_right_radius
4430                .clone()
4431                .map(OverrideItem::BorderTopRightRadius),
4432            OverrideItem::BorderBottomLeftRadius(_) => self
4433                .border_bottom_left_radius
4434                .clone()
4435                .map(OverrideItem::BorderBottomLeftRadius),
4436            OverrideItem::BorderBottomRightRadius(_) => self
4437                .border_bottom_right_radius
4438                .clone()
4439                .map(OverrideItem::BorderBottomRightRadius),
4440            OverrideItem::MarginLeft(_) => self.margin_left.clone().map(OverrideItem::MarginLeft),
4441            OverrideItem::MarginRight(_) => {
4442                self.margin_right.clone().map(OverrideItem::MarginRight)
4443            }
4444            OverrideItem::MarginTop(_) => self.margin_top.clone().map(OverrideItem::MarginTop),
4445            OverrideItem::MarginBottom(_) => {
4446                self.margin_bottom.clone().map(OverrideItem::MarginBottom)
4447            }
4448            OverrideItem::PaddingLeft(_) => {
4449                self.padding_left.clone().map(OverrideItem::PaddingLeft)
4450            }
4451            OverrideItem::PaddingRight(_) => {
4452                self.padding_right.clone().map(OverrideItem::PaddingRight)
4453            }
4454            OverrideItem::PaddingTop(_) => self.padding_top.clone().map(OverrideItem::PaddingTop),
4455            OverrideItem::PaddingBottom(_) => {
4456                self.padding_bottom.clone().map(OverrideItem::PaddingBottom)
4457            }
4458            OverrideItem::FontSize(_) => self.font_size.clone().map(OverrideItem::FontSize),
4459            OverrideItem::Color(_) => self.color.map(OverrideItem::Color),
4460            OverrideItem::Hidden(_) => self.hidden.map(OverrideItem::Hidden),
4461            OverrideItem::Visibility(_) => self.visibility.map(OverrideItem::Visibility),
4462        }
4463    }
4464
4465    #[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
4466    fn attrs(&self, #[allow(unused)] with_debug_attrs: bool) -> Attrs {
4467        let mut attrs = Attrs { values: vec![] };
4468
4469        if with_debug_attrs {
4470            attrs.add("dbg-id", self.id);
4471        }
4472
4473        attrs.add_opt("id", self.str_id.as_ref());
4474
4475        match &self.element {
4476            Element::Image {
4477                fit,
4478                source_set,
4479                sizes,
4480                alt,
4481                loading,
4482                ..
4483            } => {
4484                attrs.add_opt("sx-fit", *fit);
4485                attrs.add_opt("loading", *loading);
4486                attrs.add_opt("srcset", source_set.as_ref());
4487                attrs.add_opt("sizes", sizes.as_ref());
4488                attrs.add_opt("alt", alt.as_ref());
4489            }
4490            Element::Anchor { target, .. } => {
4491                attrs.add_opt("target", target.as_ref());
4492            }
4493            Element::Input {
4494                name, autofocus, ..
4495            } => {
4496                attrs.add_opt("name", name.as_ref());
4497                attrs.add_opt("autofocus", autofocus.as_ref());
4498            }
4499            Element::Textarea {
4500                name,
4501                placeholder,
4502                rows,
4503                cols,
4504                ..
4505            } => {
4506                attrs.add_opt("name", name.as_ref());
4507                attrs.add_opt("placeholder", placeholder.as_ref());
4508                attrs.add_opt("rows", rows.as_ref());
4509                attrs.add_opt("cols", cols.as_ref());
4510            }
4511            Element::TH { rows, columns } => {
4512                attrs.add_opt("rows", rows.as_ref());
4513                attrs.add_opt("columns", columns.as_ref());
4514            }
4515            Element::TD { rows, columns } => {
4516                attrs.add_opt("rows", rows.as_ref());
4517                attrs.add_opt("columns", columns.as_ref());
4518            }
4519            Element::Button { r#type } => {
4520                attrs.add_opt("type", r#type.as_ref());
4521            }
4522            Element::Select {
4523                name,
4524                selected,
4525                multiple,
4526                disabled,
4527                autofocus,
4528            } => {
4529                attrs.add_opt("name", name.as_ref());
4530                attrs.add_opt("sx-selected", selected.as_ref());
4531                if *multiple == Some(true) {
4532                    attrs.add("multiple", "multiple");
4533                }
4534                if *disabled == Some(true) {
4535                    attrs.add("disabled", "disabled");
4536                }
4537                if *autofocus == Some(true) {
4538                    attrs.add("autofocus", "autofocus");
4539                }
4540            }
4541            Element::Option { value, disabled } => {
4542                attrs.add_opt("value", value.as_ref());
4543                if *disabled == Some(true) {
4544                    attrs.add("disabled", "disabled");
4545                }
4546            }
4547            Element::Div
4548            | Element::Raw { .. }
4549            | Element::Text { .. }
4550            | Element::Aside
4551            | Element::Main
4552            | Element::Header
4553            | Element::Footer
4554            | Element::Section
4555            | Element::Form { .. }
4556            | Element::Span
4557            | Element::Heading { .. }
4558            | Element::UnorderedList
4559            | Element::OrderedList
4560            | Element::ListItem
4561            | Element::Table
4562            | Element::THead
4563            | Element::TBody
4564            | Element::TR
4565            | Element::Details { .. }
4566            | Element::Summary => {}
4567            #[cfg(feature = "canvas")]
4568            Element::Canvas => {}
4569        }
4570
4571        let mut data = self.data.iter().collect::<Vec<_>>();
4572        data.sort_by(|(a, _), (b, _)| (*a).cmp(b));
4573
4574        if !self.classes.is_empty() {
4575            attrs.add("class", self.classes.join(" "));
4576        }
4577
4578        for (name, value) in data {
4579            attrs.add(format!("data-{name}"), value);
4580        }
4581
4582        if let Some(route) = &self.route {
4583            match route {
4584                Route::Get {
4585                    route,
4586                    trigger,
4587                    target,
4588                    strategy,
4589                } => {
4590                    attrs.add("hx-get", route);
4591                    attrs.add_opt("hx-trigger", trigger.clone());
4592                    if !matches!(target, hyperchad_transformer_models::Selector::SelfTarget) {
4593                        attrs.add("hx-target", target);
4594                    }
4595                    attrs.add("hx-swap", strategy);
4596                }
4597                Route::Post {
4598                    route,
4599                    trigger,
4600                    target,
4601                    strategy,
4602                } => {
4603                    attrs.add("hx-post", route);
4604                    attrs.add_opt("hx-trigger", trigger.clone());
4605                    if !matches!(target, hyperchad_transformer_models::Selector::SelfTarget) {
4606                        attrs.add("hx-target", target);
4607                    }
4608                    attrs.add("hx-swap", strategy);
4609                }
4610                Route::Put {
4611                    route,
4612                    trigger,
4613                    target,
4614                    strategy,
4615                } => {
4616                    attrs.add("hx-put", route);
4617                    attrs.add_opt("hx-trigger", trigger.clone());
4618                    if !matches!(target, hyperchad_transformer_models::Selector::SelfTarget) {
4619                        attrs.add("hx-target", target);
4620                    }
4621                    attrs.add("hx-swap", strategy);
4622                }
4623                Route::Delete {
4624                    route,
4625                    trigger,
4626                    target,
4627                    strategy,
4628                } => {
4629                    attrs.add("hx-delete", route);
4630                    attrs.add_opt("hx-trigger", trigger.clone());
4631                    if !matches!(target, hyperchad_transformer_models::Selector::SelfTarget) {
4632                        attrs.add("hx-target", target);
4633                    }
4634                    attrs.add("hx-swap", strategy);
4635                }
4636                Route::Patch {
4637                    route,
4638                    trigger,
4639                    target,
4640                    strategy,
4641                } => {
4642                    attrs.add("hx-patch", route);
4643                    attrs.add_opt("hx-trigger", trigger.clone());
4644                    if !matches!(target, hyperchad_transformer_models::Selector::SelfTarget) {
4645                        attrs.add("hx-target", target);
4646                    }
4647                    attrs.add("hx-swap", strategy);
4648                }
4649            }
4650        }
4651
4652        attrs.add_opt("sx-justify-content", self.justify_content.as_ref());
4653        attrs.add_opt("sx-align-items", self.align_items.as_ref());
4654
4655        attrs.add_opt("sx-text-align", self.text_align.as_ref());
4656        attrs.add_opt("sx-white-space", self.white_space.as_ref());
4657
4658        if let Some(text_decoration) = &self.text_decoration {
4659            attrs.add_opt("sx-text-decoration-color", text_decoration.color);
4660            attrs.add(
4661                "sx-text-decoration-line",
4662                text_decoration
4663                    .line
4664                    .iter()
4665                    .map(ToString::to_string)
4666                    .collect::<Vec<_>>()
4667                    .join(" "),
4668            );
4669            attrs.add_opt("sx-text-decoration-style", text_decoration.style);
4670            attrs.add_opt(
4671                "sx-text-decoration-thickness",
4672                text_decoration.thickness.as_ref(),
4673            );
4674        }
4675
4676        if let Some(font_family) = &self.font_family {
4677            attrs.add("sx-font-family", font_family.join(","));
4678        }
4679
4680        attrs.add_opt("sx-font-weight", self.font_weight);
4681
4682        match self.element {
4683            Element::TR => {
4684                if self.direction != LayoutDirection::Row {
4685                    attrs.add("sx-dir", self.direction);
4686                }
4687            }
4688            _ => {
4689                if self.direction != LayoutDirection::default() {
4690                    attrs.add("sx-dir", self.direction);
4691                }
4692            }
4693        }
4694
4695        attrs.add_opt("sx-position", self.position);
4696
4697        attrs.add_opt("sx-background", self.background);
4698
4699        attrs.add_opt("sx-width", self.width.as_ref());
4700        attrs.add_opt("sx-min-width", self.min_width.as_ref());
4701        attrs.add_opt("sx-max-width", self.max_width.as_ref());
4702        attrs.add_opt("sx-height", self.height.as_ref());
4703        attrs.add_opt("sx-min-height", self.min_height.as_ref());
4704        attrs.add_opt("sx-max-height", self.max_height.as_ref());
4705
4706        if let Some(flex) = &self.flex {
4707            attrs.add("sx-flex-grow", &flex.grow);
4708            attrs.add("sx-flex-shrink", &flex.shrink);
4709            attrs.add("sx-flex-basis", &flex.basis);
4710        }
4711
4712        attrs.add_opt("sx-col-gap", self.column_gap.as_ref());
4713        attrs.add_opt("sx-row-gap", self.row_gap.as_ref());
4714        attrs.add_opt("sx-grid-cell-size", self.grid_cell_size.as_ref());
4715
4716        attrs.add_opt("sx-opacity", self.opacity.as_ref());
4717
4718        attrs.add_opt("sx-left", self.left.as_ref());
4719        attrs.add_opt("sx-right", self.right.as_ref());
4720        attrs.add_opt("sx-top", self.top.as_ref());
4721        attrs.add_opt("sx-bottom", self.bottom.as_ref());
4722
4723        attrs.add_opt("sx-translate-x", self.translate_x.as_ref());
4724        attrs.add_opt("sx-translate-y", self.translate_y.as_ref());
4725
4726        attrs.add_opt("sx-cursor", self.cursor.as_ref());
4727        attrs.add_opt("sx-user-select", self.user_select.as_ref());
4728        attrs.add_opt("sx-overflow-wrap", self.overflow_wrap.as_ref());
4729        attrs.add_opt("sx-text-overflow", self.text_overflow.as_ref());
4730
4731        attrs.add_opt("sx-padding-left", self.padding_left.as_ref());
4732        attrs.add_opt("sx-padding-right", self.padding_right.as_ref());
4733        attrs.add_opt("sx-padding-top", self.padding_top.as_ref());
4734        attrs.add_opt("sx-padding-bottom", self.padding_bottom.as_ref());
4735
4736        attrs.add_opt("sx-margin-left", self.margin_left.as_ref());
4737        attrs.add_opt("sx-margin-right", self.margin_right.as_ref());
4738        attrs.add_opt("sx-margin-top", self.margin_top.as_ref());
4739        attrs.add_opt("sx-margin-bottom", self.margin_bottom.as_ref());
4740
4741        attrs.add_opt("sx-hidden", self.hidden.as_ref());
4742        attrs.add_opt("sx-visibility", self.visibility.as_ref());
4743
4744        attrs.add_opt("sx-font-size", self.font_size.as_ref());
4745        attrs.add_opt("sx-color", self.color.as_ref());
4746
4747        attrs.add_opt("debug", self.debug.as_ref());
4748
4749        attrs.add_opt(
4750            "sx-border-left",
4751            self.border_left
4752                .as_ref()
4753                .map(|(color, size)| format!("{size}, {color}")),
4754        );
4755        attrs.add_opt(
4756            "sx-border-right",
4757            self.border_right
4758                .as_ref()
4759                .map(|(color, size)| format!("{size}, {color}")),
4760        );
4761        attrs.add_opt(
4762            "sx-border-top",
4763            self.border_top
4764                .as_ref()
4765                .map(|(color, size)| format!("{size}, {color}")),
4766        );
4767        attrs.add_opt(
4768            "sx-border-bottom",
4769            self.border_bottom
4770                .as_ref()
4771                .map(|(color, size)| format!("{size}, {color}")),
4772        );
4773        attrs.add_opt(
4774            "sx-border-top-left-radius",
4775            self.border_top_left_radius.as_ref(),
4776        );
4777        attrs.add_opt(
4778            "sx-border-top-right-radius",
4779            self.border_top_right_radius.as_ref(),
4780        );
4781        attrs.add_opt(
4782            "sx-border-bottom-left-radius",
4783            self.border_bottom_left_radius.as_ref(),
4784        );
4785        attrs.add_opt(
4786            "sx-border-bottom-right-radius",
4787            self.border_bottom_right_radius.as_ref(),
4788        );
4789
4790        attrs.add_opt("state", self.state.as_ref());
4791
4792        for action in &self.actions {
4793            match &action.trigger {
4794                hyperchad_actions::ActionTrigger::Click => {
4795                    attrs.add("fx-click", action.effect.to_string());
4796                }
4797                hyperchad_actions::ActionTrigger::ClickOutside => {
4798                    attrs.add("fx-click-outside", action.effect.to_string());
4799                }
4800                hyperchad_actions::ActionTrigger::MouseDown => {
4801                    attrs.add("fx-mouse-down", action.effect.to_string());
4802                }
4803                hyperchad_actions::ActionTrigger::KeyDown => {
4804                    attrs.add("fx-key-down", action.effect.to_string());
4805                }
4806                hyperchad_actions::ActionTrigger::Hover => {
4807                    attrs.add("fx-hover", action.effect.to_string());
4808                }
4809                hyperchad_actions::ActionTrigger::Change => {
4810                    attrs.add("fx-change", action.effect.to_string());
4811                }
4812                hyperchad_actions::ActionTrigger::Resize => {
4813                    attrs.add("fx-resize", action.effect.to_string());
4814                }
4815                hyperchad_actions::ActionTrigger::Immediate => {
4816                    attrs.add("fx-immediate", action.effect.to_string());
4817                }
4818                hyperchad_actions::ActionTrigger::HttpBeforeRequest => {
4819                    attrs.add("fx-http-before-request", action.effect.to_string());
4820                }
4821                hyperchad_actions::ActionTrigger::HttpAfterRequest => {
4822                    attrs.add("fx-http-after-request", action.effect.to_string());
4823                }
4824                hyperchad_actions::ActionTrigger::HttpRequestSuccess => {
4825                    attrs.add("fx-http-success", action.effect.to_string());
4826                }
4827                hyperchad_actions::ActionTrigger::HttpRequestError => {
4828                    attrs.add("fx-http-error", action.effect.to_string());
4829                }
4830                hyperchad_actions::ActionTrigger::HttpRequestAbort => {
4831                    attrs.add("fx-http-abort", action.effect.to_string());
4832                }
4833                hyperchad_actions::ActionTrigger::HttpRequestTimeout => {
4834                    attrs.add("fx-http-timeout", action.effect.to_string());
4835                }
4836                hyperchad_actions::ActionTrigger::Event(..) => {
4837                    attrs.add("fx-event", action.effect.to_string());
4838                }
4839            }
4840        }
4841
4842        match self.overflow_x {
4843            LayoutOverflow::Auto => {
4844                attrs.add("sx-overflow-x", "auto");
4845            }
4846            LayoutOverflow::Scroll => {
4847                attrs.add("sx-overflow-x", "scroll");
4848            }
4849            LayoutOverflow::Expand => {}
4850            LayoutOverflow::Squash => {
4851                attrs.add("sx-overflow-x", "squash");
4852            }
4853            LayoutOverflow::Wrap { grid } => {
4854                attrs.add("sx-overflow-x", if grid { "wrap-grid" } else { "wrap" });
4855            }
4856            LayoutOverflow::Hidden => {
4857                attrs.add("sx-overflow-x", "hidden");
4858            }
4859        }
4860        match self.overflow_y {
4861            LayoutOverflow::Auto => {
4862                attrs.add("sx-overflow-y", "auto");
4863            }
4864            LayoutOverflow::Scroll => {
4865                attrs.add("sx-overflow-y", "scroll");
4866            }
4867            LayoutOverflow::Expand => {}
4868            LayoutOverflow::Squash => {
4869                attrs.add("sx-overflow-y", "squash");
4870            }
4871            LayoutOverflow::Wrap { grid } => {
4872                attrs.add("sx-overflow-y", if grid { "wrap-grid" } else { "wrap" });
4873            }
4874            LayoutOverflow::Hidden => {
4875                attrs.add("sx-overflow-y", "hidden");
4876            }
4877        }
4878
4879        #[cfg(feature = "layout")]
4880        if with_debug_attrs {
4881            let skip_default =
4882                matches!(var("SKIP_DEFAULT_DEBUG_ATTRS").as_deref(), Ok("1" | "true"));
4883
4884            attrs.add_opt_skip_default("calc-x", self.calculated_x, skip_default);
4885            attrs.add_opt_skip_default("calc-y", self.calculated_y, skip_default);
4886            attrs.add_opt_skip_default("calc-min-width", self.calculated_min_width, skip_default);
4887            attrs.add_opt_skip_default(
4888                "calc-child-min-width",
4889                self.calculated_child_min_width,
4890                skip_default,
4891            );
4892            attrs.add_opt_skip_default("calc-max-width", self.calculated_max_width, skip_default);
4893            attrs.add_opt_skip_default(
4894                "calc-preferred-width",
4895                self.calculated_preferred_width,
4896                skip_default,
4897            );
4898            attrs.add_opt_skip_default("calc-width", self.calculated_width, skip_default);
4899            attrs.add_opt_skip_default("calc-min-height", self.calculated_min_height, skip_default);
4900            attrs.add_opt_skip_default(
4901                "calc-child-min-height",
4902                self.calculated_child_min_height,
4903                skip_default,
4904            );
4905            attrs.add_opt_skip_default("calc-max-height", self.calculated_max_height, skip_default);
4906            attrs.add_opt_skip_default(
4907                "calc-preferred-height",
4908                self.calculated_preferred_height,
4909                skip_default,
4910            );
4911            attrs.add_opt_skip_default("calc-height", self.calculated_height, skip_default);
4912            attrs.add_opt_skip_default(
4913                "calc-margin-left",
4914                self.calculated_margin_left,
4915                skip_default,
4916            );
4917            attrs.add_opt_skip_default(
4918                "calc-margin-right",
4919                self.calculated_margin_right,
4920                skip_default,
4921            );
4922            attrs.add_opt_skip_default("calc-margin-top", self.calculated_margin_top, skip_default);
4923            attrs.add_opt_skip_default(
4924                "calc-margin-bottom",
4925                self.calculated_margin_bottom,
4926                skip_default,
4927            );
4928            attrs.add_opt_skip_default(
4929                "calc-padding-left",
4930                self.calculated_padding_left,
4931                skip_default,
4932            );
4933            attrs.add_opt_skip_default(
4934                "calc-padding-right",
4935                self.calculated_padding_right,
4936                skip_default,
4937            );
4938            attrs.add_opt_skip_default(
4939                "calc-padding-top",
4940                self.calculated_padding_top,
4941                skip_default,
4942            );
4943            attrs.add_opt_skip_default(
4944                "calc-padding-bottom",
4945                self.calculated_padding_bottom,
4946                skip_default,
4947            );
4948            attrs.add_opt_skip_default(
4949                "calc-border-left",
4950                self.calculated_border_left
4951                    .map(|(color, size)| format!("{size}, {color}")),
4952                skip_default,
4953            );
4954            attrs.add_opt_skip_default(
4955                "calc-border-right",
4956                self.calculated_border_right
4957                    .map(|(color, size)| format!("{size}, {color}")),
4958                skip_default,
4959            );
4960            attrs.add_opt_skip_default(
4961                "calc-border-top",
4962                self.calculated_border_top
4963                    .map(|(color, size)| format!("{size}, {color}")),
4964                skip_default,
4965            );
4966            attrs.add_opt_skip_default(
4967                "calc-border-bottom",
4968                self.calculated_border_bottom
4969                    .map(|(color, size)| format!("{size}, {color}")),
4970                skip_default,
4971            );
4972            attrs.add_opt_skip_default(
4973                "calc-border-top-left-radius",
4974                self.calculated_border_top_left_radius,
4975                skip_default,
4976            );
4977            attrs.add_opt_skip_default(
4978                "calc-border-top-right-radius",
4979                self.calculated_border_top_right_radius,
4980                skip_default,
4981            );
4982            attrs.add_opt_skip_default(
4983                "calc-border-bottom-left-radius",
4984                self.calculated_border_bottom_left_radius,
4985                skip_default,
4986            );
4987            attrs.add_opt_skip_default(
4988                "calc-border-bottom-right-radius",
4989                self.calculated_border_bottom_right_radius,
4990                skip_default,
4991            );
4992            attrs.add_opt_skip_default("calc-col-gap", self.calculated_column_gap, skip_default);
4993            attrs.add_opt_skip_default("calc-row-gap", self.calculated_row_gap, skip_default);
4994            attrs.add_opt_skip_default("calc-opacity", self.calculated_opacity, skip_default);
4995            attrs.add_opt_skip_default("calc-font-size", self.calculated_font_size, skip_default);
4996            attrs.add_opt_skip_default("calc-scrollbar-right", self.scrollbar_right, skip_default);
4997            attrs.add_opt_skip_default(
4998                "calc-scrollbar-bottom",
4999                self.scrollbar_bottom,
5000                skip_default,
5001            );
5002
5003            if let Some(hyperchad_transformer_models::LayoutPosition::Wrap { row, col }) =
5004                &self.calculated_position
5005            {
5006                attrs.add("calc-row", *row);
5007                attrs.add("calc-col", *col);
5008            }
5009            #[cfg(feature = "layout-offset")]
5010            {
5011                attrs.add_opt_skip_default("calc-offset-x", self.calculated_offset_x, skip_default);
5012                attrs.add_opt_skip_default("calc-offset-y", self.calculated_offset_y, skip_default);
5013            }
5014        }
5015
5016        #[cfg(feature = "logic")]
5017        for config in &self.overrides {
5018            for item in &config.overrides {
5019                let name = override_item_to_attr_name(item);
5020
5021                match &config.condition {
5022                    OverrideCondition::ResponsiveTarget { name: target } => {
5023                        match item {
5024                            OverrideItem::Flex(..) => {
5025                                attrs.values.retain(|(x, _)| {
5026                                    !matches!(
5027                                        x.as_str(),
5028                                        "sx-flex-grow" | "sx-flex-basis" | "sx-flex-shrink",
5029                                    )
5030                                });
5031                            }
5032                            OverrideItem::TextDecoration(..) => {
5033                                attrs.values.retain(|(x, _)| {
5034                                    !matches!(
5035                                        x.as_str(),
5036                                        "sx-text-decoration-line"
5037                                            | "sx-text-decoration-style"
5038                                            | "sx-text-decoration-color"
5039                                            | "sx-text-decoration-thickness",
5040                                    )
5041                                });
5042                            }
5043                            _ => {}
5044                        }
5045
5046                        // Use config.default if set, otherwise fall back to the
5047                        // container's base value for this field. This preserves
5048                        // the base value during round-trip serialization.
5049                        let default_value = config
5050                            .default
5051                            .clone()
5052                            .or_else(|| self.get_base_value_for_override(item));
5053
5054                        attrs.replace_or_add(
5055                            name,
5056                            item.as_json_if_expression_string(
5057                                hyperchad_actions::logic::Responsive::Target(target.clone()),
5058                                default_value.as_ref(),
5059                            )
5060                            .unwrap(),
5061                        );
5062                    }
5063                }
5064            }
5065        }
5066
5067        attrs.values.sort_by(|(a, _), (b, _)| a.cmp(b));
5068
5069        attrs
5070    }
5071
5072    fn attrs_to_string_pad_left(&self, with_debug_attrs: bool) -> String {
5073        self.attrs(with_debug_attrs).to_string_pad_left()
5074    }
5075
5076    #[cfg_attr(feature = "profiling", profiling::function)]
5077    #[allow(clippy::too_many_lines)]
5078    fn display(
5079        &self,
5080        f: &mut dyn Write,
5081        with_debug_attrs: bool,
5082        wrap_raw_in_element: bool,
5083    ) -> Result<(), std::io::Error> {
5084        match &self.element {
5085            Element::Raw { value } => {
5086                if wrap_raw_in_element {
5087                    f.write_fmt(format_args!(
5088                        "<raw{attrs}>",
5089                        attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5090                    ))?;
5091                    f.write_fmt(format_args!("{value}</raw>"))?;
5092                } else {
5093                    f.write_fmt(format_args!("{value}"))?;
5094                }
5095            }
5096            Element::Text { value } => {
5097                if wrap_raw_in_element {
5098                    f.write_fmt(format_args!(
5099                        "<text{attrs}>",
5100                        attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5101                    ))?;
5102                    f.write_fmt(format_args!("{value}</text>"))?;
5103                } else {
5104                    f.write_fmt(format_args!("{value}"))?;
5105                }
5106            }
5107            Element::Div => {
5108                f.write_fmt(format_args!(
5109                    "<div{attrs}>",
5110                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5111                ))?;
5112                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5113                f.write_fmt(format_args!("</div>"))?;
5114            }
5115            Element::Aside => {
5116                f.write_fmt(format_args!(
5117                    "<aside{attrs}>",
5118                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5119                ))?;
5120                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5121                f.write_fmt(format_args!("</aside>"))?;
5122            }
5123
5124            Element::Main => {
5125                f.write_fmt(format_args!(
5126                    "<main{attrs}>",
5127                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5128                ))?;
5129                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5130                f.write_fmt(format_args!("</main>"))?;
5131            }
5132            Element::Header => {
5133                f.write_fmt(format_args!(
5134                    "<header{attrs}>",
5135                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5136                ))?;
5137                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5138                f.write_fmt(format_args!("</header>"))?;
5139            }
5140            Element::Footer => {
5141                f.write_fmt(format_args!(
5142                    "<footer{attrs}>",
5143                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5144                ))?;
5145                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5146                f.write_fmt(format_args!("</footer>"))?;
5147            }
5148            Element::Section => {
5149                f.write_fmt(format_args!(
5150                    "<section{attrs}>",
5151                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5152                ))?;
5153                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5154                f.write_fmt(format_args!("</section>"))?;
5155            }
5156            Element::Form { action, method } => {
5157                let action_attr = action
5158                    .as_ref()
5159                    .map_or_else(String::new, |a| format!(r#" action="{a}""#));
5160                let method_attr = method
5161                    .as_ref()
5162                    .map_or_else(String::new, |m| format!(r#" method="{m}""#));
5163                f.write_fmt(format_args!(
5164                    "<form{action}{method}{attrs}>",
5165                    action = action_attr,
5166                    method = method_attr,
5167                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5168                ))?;
5169                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5170                f.write_fmt(format_args!("</form>"))?;
5171            }
5172            Element::Span => {
5173                f.write_fmt(format_args!(
5174                    "<span{attrs}>",
5175                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5176                ))?;
5177                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5178                f.write_fmt(format_args!("</span>"))?;
5179            }
5180            Element::Input { input, .. } => {
5181                input.display(f, self.attrs(with_debug_attrs))?;
5182            }
5183            Element::Textarea { value, .. } => {
5184                f.write_fmt(format_args!(
5185                    "<textarea{attrs}>{value}</textarea>",
5186                    attrs = self.attrs_to_string_pad_left(with_debug_attrs),
5187                    value = html_escape::encode_text(value)
5188                ))?;
5189            }
5190            Element::Button { .. } => {
5191                f.write_fmt(format_args!(
5192                    "<button{attrs}>",
5193                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5194                ))?;
5195                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5196                f.write_fmt(format_args!("</button>"))?;
5197            }
5198            Element::Image { source, .. } => {
5199                f.write_fmt(format_args!(
5200                    "<img{src_attr}{attrs} />",
5201                    attrs = self.attrs_to_string_pad_left(with_debug_attrs),
5202                    src_attr = Attrs::new()
5203                        .with_attr_opt("src", source.to_owned())
5204                        .to_string_pad_left()
5205                ))?;
5206            }
5207            Element::Anchor { href, .. } => {
5208                f.write_fmt(format_args!(
5209                    "<a{href_attr}{attrs}>",
5210                    attrs = self.attrs_to_string_pad_left(with_debug_attrs),
5211                    href_attr = Attrs::new()
5212                        .with_attr_opt("href", href.to_owned())
5213                        .to_string_pad_left(),
5214                ))?;
5215                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5216                f.write_fmt(format_args!("</a>"))?;
5217            }
5218            Element::Heading { size } => {
5219                f.write_fmt(format_args!(
5220                    "<{size}{attrs}>",
5221                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5222                ))?;
5223                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5224                f.write_fmt(format_args!("</{size}>"))?;
5225            }
5226            Element::UnorderedList => {
5227                f.write_fmt(format_args!(
5228                    "<ul{attrs}>",
5229                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5230                ))?;
5231                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5232                f.write_fmt(format_args!("</ul>"))?;
5233            }
5234            Element::OrderedList => {
5235                f.write_fmt(format_args!(
5236                    "<ol{attrs}>",
5237                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5238                ))?;
5239                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5240                f.write_fmt(format_args!("</ol>"))?;
5241            }
5242            Element::ListItem => {
5243                f.write_fmt(format_args!(
5244                    "<li{attrs}>",
5245                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5246                ))?;
5247                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5248                f.write_fmt(format_args!("</li>"))?;
5249            }
5250            Element::Table => {
5251                f.write_fmt(format_args!(
5252                    "<table{attrs}>",
5253                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5254                ))?;
5255                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5256                f.write_fmt(format_args!("</table>"))?;
5257            }
5258            Element::THead => {
5259                f.write_fmt(format_args!(
5260                    "<thead{attrs}>",
5261                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5262                ))?;
5263                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5264                f.write_fmt(format_args!("</thead>"))?;
5265            }
5266            Element::TH { .. } => {
5267                f.write_fmt(format_args!(
5268                    "<th{attrs}>",
5269                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5270                ))?;
5271                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5272                f.write_fmt(format_args!("</th>"))?;
5273            }
5274            Element::TBody => {
5275                f.write_fmt(format_args!(
5276                    "<tbody{attrs}>",
5277                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5278                ))?;
5279                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5280                f.write_fmt(format_args!("</tbody>"))?;
5281            }
5282            Element::TR => {
5283                f.write_fmt(format_args!(
5284                    "<tr{attrs}>",
5285                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5286                ))?;
5287                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5288                f.write_fmt(format_args!("</tr>"))?;
5289            }
5290            Element::TD { .. } => {
5291                f.write_fmt(format_args!(
5292                    "<td{attrs}>",
5293                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5294                ))?;
5295                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5296                f.write_fmt(format_args!("</td>"))?;
5297            }
5298            #[cfg(feature = "canvas")]
5299            Element::Canvas => {
5300                f.write_fmt(format_args!(
5301                    "<canvas{attrs}>",
5302                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5303                ))?;
5304                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5305                f.write_fmt(format_args!("</canvas>"))?;
5306            }
5307            Element::Details { open } => {
5308                f.write_fmt(format_args!("<details"))?;
5309                f.write_fmt(format_args!(
5310                    "{attrs}",
5311                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5312                ))?;
5313                if matches!(open, Some(true)) {
5314                    f.write_fmt(format_args!(" open"))?;
5315                }
5316                f.write_fmt(format_args!(">"))?;
5317                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5318                f.write_fmt(format_args!("</details>"))?;
5319            }
5320            Element::Summary => {
5321                f.write_fmt(format_args!(
5322                    "<summary{attrs}>",
5323                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5324                ))?;
5325                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5326                f.write_fmt(format_args!("</summary>"))?;
5327            }
5328            Element::Select { .. } => {
5329                f.write_fmt(format_args!(
5330                    "<select{attrs}>",
5331                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5332                ))?;
5333                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5334                f.write_fmt(format_args!("</select>"))?;
5335            }
5336            Element::Option { .. } => {
5337                f.write_fmt(format_args!(
5338                    "<option{attrs}>",
5339                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
5340                ))?;
5341                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
5342                f.write_fmt(format_args!("</option>"))?;
5343            }
5344        }
5345
5346        Ok(())
5347    }
5348
5349    /// Converts this container to an HTML string with default formatting.
5350    ///
5351    /// Generates HTML representation of the container and its children with basic options.
5352    /// For more control over formatting and syntax highlighting, use [`display_to_string`](Self::display_to_string).
5353    ///
5354    /// # Errors
5355    ///
5356    /// * If fails to write to the writer
5357    /// * If invalid UTF-8 characters
5358    #[cfg_attr(feature = "profiling", profiling::function)]
5359    #[allow(clippy::fn_params_excessive_bools)]
5360    pub fn display_to_string_default(
5361        &self,
5362        with_debug_attrs: bool,
5363        wrap_raw_in_element: bool,
5364    ) -> Result<String, Box<dyn std::error::Error>> {
5365        self.display_to_string(
5366            with_debug_attrs,
5367            wrap_raw_in_element,
5368            #[cfg(feature = "format")]
5369            false,
5370            #[cfg(feature = "syntax-highlighting")]
5371            false,
5372        )
5373    }
5374
5375    /// Converts this container to a formatted HTML string with syntax highlighting.
5376    ///
5377    /// Generates HTML representation of the container and its children with pretty formatting
5378    /// and syntax highlighting enabled (if the respective features are enabled).
5379    ///
5380    /// # Errors
5381    ///
5382    /// * If fails to write to the writer
5383    /// * If invalid UTF-8 characters
5384    #[cfg_attr(feature = "profiling", profiling::function)]
5385    #[allow(clippy::fn_params_excessive_bools)]
5386    pub fn display_to_string_default_pretty(
5387        &self,
5388        with_debug_attrs: bool,
5389        wrap_raw_in_element: bool,
5390    ) -> Result<String, Box<dyn std::error::Error>> {
5391        self.display_to_string(
5392            with_debug_attrs,
5393            wrap_raw_in_element,
5394            #[cfg(feature = "format")]
5395            true,
5396            #[cfg(feature = "syntax-highlighting")]
5397            true,
5398        )
5399    }
5400
5401    /// Converts this container to an HTML string with full formatting control.
5402    ///
5403    /// Generates HTML representation of the container and its children with options for
5404    /// debug attributes, raw element wrapping, formatting, and syntax highlighting.
5405    ///
5406    /// # Errors
5407    ///
5408    /// * If fails to write to the writer
5409    /// * If invalid UTF-8 characters
5410    ///
5411    /// # Panics
5412    ///
5413    /// * If syntax highlighting fails
5414    #[cfg_attr(feature = "profiling", profiling::function)]
5415    #[allow(clippy::fn_params_excessive_bools)]
5416    pub fn display_to_string(
5417        &self,
5418        with_debug_attrs: bool,
5419        wrap_raw_in_element: bool,
5420        #[cfg(feature = "format")] format: bool,
5421        #[cfg(feature = "syntax-highlighting")] highlight: bool,
5422    ) -> Result<String, Box<dyn std::error::Error>> {
5423        let mut data = Vec::new();
5424
5425        let _ = self.display(&mut data, with_debug_attrs, wrap_raw_in_element);
5426
5427        #[cfg(feature = "format")]
5428        let data = if format {
5429            if data[0] == b'<' {
5430                use xml::{reader::ParserConfig, writer::EmitterConfig};
5431                let data: &[u8] = &data;
5432
5433                let reader = ParserConfig::new()
5434                    .trim_whitespace(true)
5435                    .ignore_comments(false)
5436                    .create_reader(data);
5437
5438                let mut dest = Vec::new();
5439
5440                let mut writer = EmitterConfig::new()
5441                    .perform_indent(true)
5442                    .normalize_empty_elements(false)
5443                    .autopad_comments(false)
5444                    .write_document_declaration(false)
5445                    .create_writer(&mut dest);
5446
5447                for event in reader {
5448                    if let Some(event) = event?.as_writer_event() {
5449                        writer.write(event)?;
5450                    }
5451                }
5452
5453                dest
5454            } else {
5455                data
5456            }
5457        } else {
5458            data
5459        };
5460
5461        let xml = String::from_utf8(data)?;
5462
5463        // Remove doctype header thing
5464        let xml = if let Some((_, xml)) = xml.split_once('\n') {
5465            xml.to_string()
5466        } else {
5467            xml
5468        };
5469
5470        #[cfg(feature = "syntax-highlighting")]
5471        if highlight {
5472            use std::sync::LazyLock;
5473
5474            use syntect::highlighting::ThemeSet;
5475            use syntect::parsing::{SyntaxReference, SyntaxSet};
5476
5477            static PS: LazyLock<SyntaxSet> = LazyLock::new(SyntaxSet::load_defaults_newlines);
5478            static TS: LazyLock<ThemeSet> = LazyLock::new(ThemeSet::load_defaults);
5479            static SYNTAX: LazyLock<SyntaxReference> =
5480                LazyLock::new(|| PS.find_syntax_by_extension("xml").unwrap().clone());
5481
5482            let mut h =
5483                syntect::easy::HighlightLines::new(&SYNTAX, &TS.themes["base16-ocean.dark"]);
5484            let highlighted = syntect::util::LinesWithEndings::from(&xml)
5485                .map(|line| {
5486                    let ranges: Vec<(syntect::highlighting::Style, &str)> =
5487                        h.highlight_line(line, &PS).unwrap();
5488                    syntect::util::as_24_bit_terminal_escaped(&ranges[..], false)
5489                })
5490                .collect::<String>();
5491
5492            return Ok(highlighted);
5493        }
5494
5495        Ok(xml)
5496    }
5497}
5498
5499#[cfg(feature = "logic")]
5500const fn override_item_to_attr_name(item: &OverrideItem) -> &'static str {
5501    match item {
5502        OverrideItem::StrId(..) => "id",
5503        OverrideItem::Classes(..) => "class",
5504        OverrideItem::Direction(..) => "sx-dir",
5505        OverrideItem::OverflowX(..) => "sx-overflow-x",
5506        OverrideItem::OverflowY(..) => "sx-overflow-y",
5507        OverrideItem::GridCellSize(..) => "sx-grid-cell-size",
5508        OverrideItem::JustifyContent(..) => "sx-justify-content",
5509        OverrideItem::AlignItems(..) => "sx-align-items",
5510        OverrideItem::TextAlign(..) => "sx-text-align",
5511        OverrideItem::WhiteSpace(..) => "sx-white-space",
5512        OverrideItem::TextDecoration(..) => "sx-text-decoration",
5513        OverrideItem::FontFamily(..) => "sx-font-family",
5514        OverrideItem::FontWeight(..) => "sx-font-weight",
5515        OverrideItem::Width(..) => "sx-width",
5516        OverrideItem::MinWidth(..) => "sx-min-width",
5517        OverrideItem::MaxWidth(..) => "sx-max-width",
5518        OverrideItem::Height(..) => "sx-height",
5519        OverrideItem::MinHeight(..) => "sx-min-height",
5520        OverrideItem::MaxHeight(..) => "sx-max-height",
5521        OverrideItem::Flex(..) => "sx-flex",
5522        OverrideItem::ColumnGap(..) => "sx-column-gap",
5523        OverrideItem::RowGap(..) => "sx-row-gap",
5524        OverrideItem::Opacity(..) => "sx-opacity",
5525        OverrideItem::Left(..) => "sx-left",
5526        OverrideItem::Right(..) => "sx-right",
5527        OverrideItem::Top(..) => "sx-top",
5528        OverrideItem::Bottom(..) => "sx-bottom",
5529        OverrideItem::TranslateX(..) => "sx-translate-x",
5530        OverrideItem::TranslateY(..) => "sx-translate-y",
5531        OverrideItem::Cursor(..) => "sx-cursor",
5532        OverrideItem::UserSelect(..) => "sx-user-select",
5533        OverrideItem::OverflowWrap(..) => "sx-overflow-wrap",
5534        OverrideItem::TextOverflow(..) => "sx-text-overflow",
5535        OverrideItem::Position(..) => "sx-position",
5536        OverrideItem::Background(..) => "sx-background",
5537        OverrideItem::BorderTop(..) => "sx-border-top",
5538        OverrideItem::BorderRight(..) => "sx-border-right",
5539        OverrideItem::BorderBottom(..) => "sx-border-bottom",
5540        OverrideItem::BorderLeft(..) => "sx-border-left",
5541        OverrideItem::BorderTopLeftRadius(..) => "sx-border-top-left-radius",
5542        OverrideItem::BorderTopRightRadius(..) => "sx-border-top-right-radius",
5543        OverrideItem::BorderBottomLeftRadius(..) => "sx-border-bottom-left-radius",
5544        OverrideItem::BorderBottomRightRadius(..) => "sx-border-bottom-right-radius",
5545        OverrideItem::MarginLeft(..) => "sx-margin-left",
5546        OverrideItem::MarginRight(..) => "sx-margin-right",
5547        OverrideItem::MarginTop(..) => "sx-margin-top",
5548        OverrideItem::MarginBottom(..) => "sx-margin-bottom",
5549        OverrideItem::PaddingLeft(..) => "sx-padding-left",
5550        OverrideItem::PaddingRight(..) => "sx-padding-right",
5551        OverrideItem::PaddingTop(..) => "sx-padding-top",
5552        OverrideItem::PaddingBottom(..) => "sx-padding-bottom",
5553        OverrideItem::FontSize(..) => "sx-font-size",
5554        OverrideItem::Color(..) => "sx-color",
5555        OverrideItem::Hidden(..) => "sx-hidden",
5556        OverrideItem::Visibility(..) => "sx-visibility",
5557    }
5558}
5559
5560#[cfg_attr(feature = "profiling", profiling::all_functions)]
5561impl std::fmt::Display for Container {
5562    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5563        f.write_str(
5564            &self
5565                .display_to_string(
5566                    if cfg!(test) {
5567                        true
5568                    } else {
5569                        matches!(var("DEBUG_ATTRS").as_deref(), Ok("1" | "true"))
5570                    },
5571                    if cfg!(test) {
5572                        true
5573                    } else {
5574                        matches!(var("DEBUG_RAW_ATTRS").as_deref(), Ok("1" | "true"))
5575                    },
5576                    #[cfg(feature = "format")]
5577                    true,
5578                    #[cfg(feature = "syntax-highlighting")]
5579                    true,
5580                )
5581                .unwrap_or_else(|e| panic!("Failed to display container: {e:?} ({self:?})")),
5582        )?;
5583
5584        Ok(())
5585    }
5586}
5587
5588fn display_elements(
5589    elements: &[Container],
5590    f: &mut dyn Write,
5591    with_debug_attrs: bool,
5592    wrap_raw_in_element: bool,
5593) -> Result<(), std::io::Error> {
5594    for element in elements {
5595        element.display(f, with_debug_attrs, wrap_raw_in_element)?;
5596    }
5597
5598    Ok(())
5599}
5600
5601impl Element {
5602    /// Checks if this element type can contain child containers.
5603    ///
5604    /// Returns `true` for container elements (div, section, button, etc.) and `false` for
5605    /// self-closing or content elements (input, img, textarea, raw HTML).
5606    ///
5607    /// Container elements that allow children:
5608    /// * Structural: `Div`, `Aside`, `Main`, `Header`, `Footer`, `Section`
5609    /// * Interactive: `Form`, `Button`, `Anchor`, `Details`, `Summary`
5610    /// * Text: `Span`, `Heading`
5611    /// * Lists: `UnorderedList`, `OrderedList`, `ListItem`
5612    /// * Tables: `Table`, `THead`, `TH`, `TBody`, `TR`, `TD`
5613    ///
5614    /// Elements that do not allow children:
5615    /// * Input elements
5616    /// * Images
5617    /// * Textarea elements
5618    /// * Raw HTML content
5619    /// * Canvas elements (when feature enabled)
5620    #[must_use]
5621    pub const fn allows_children(&self) -> bool {
5622        match self {
5623            Self::Div
5624            | Self::Aside
5625            | Self::Main
5626            | Self::Header
5627            | Self::Footer
5628            | Self::Section
5629            | Self::Form { .. }
5630            | Self::Span
5631            | Self::Button { .. }
5632            | Self::Anchor { .. }
5633            | Self::Heading { .. }
5634            | Self::UnorderedList
5635            | Self::OrderedList
5636            | Self::ListItem
5637            | Self::Table
5638            | Self::THead
5639            | Self::TH { .. }
5640            | Self::TBody
5641            | Self::TR
5642            | Self::TD { .. }
5643            | Self::Details { .. }
5644            | Self::Summary
5645            | Self::Select { .. }
5646            | Self::Option { .. } => true,
5647            Self::Input { .. }
5648            | Self::Raw { .. }
5649            | Self::Text { .. }
5650            | Self::Image { .. }
5651            | Self::Textarea { .. } => false,
5652            #[cfg(feature = "canvas")]
5653            Self::Canvas => false,
5654        }
5655    }
5656
5657    /// Returns the display name of this element type as a static string.
5658    ///
5659    /// This is primarily used for debugging and error messages. The returned string matches
5660    /// the variant name (e.g., "Div", "Button", "Heading").
5661    #[must_use]
5662    pub const fn tag_display_str(&self) -> &'static str {
5663        match self {
5664            Self::Raw { .. } => "Raw",
5665            Self::Text { .. } => "Text",
5666            Self::Div { .. } => "Div",
5667            Self::Aside { .. } => "Aside",
5668            Self::Main { .. } => "Main",
5669            Self::Header { .. } => "Header",
5670            Self::Footer { .. } => "Footer",
5671            Self::Section { .. } => "Section",
5672            Self::Form { .. } => "Form",
5673            Self::Span { .. } => "Span",
5674            Self::Input { .. } => "Input",
5675            Self::Button { .. } => "Button",
5676            Self::Image { .. } => "Image",
5677            Self::Anchor { .. } => "Anchor",
5678            Self::Heading { .. } => "Heading",
5679            Self::UnorderedList { .. } => "UnorderedList",
5680            Self::OrderedList { .. } => "OrderedList",
5681            Self::ListItem { .. } => "ListItem",
5682            Self::Table { .. } => "Table",
5683            Self::THead { .. } => "THead",
5684            Self::TH { .. } => "TH",
5685            Self::TBody { .. } => "TBody",
5686            Self::TR { .. } => "TR",
5687            Self::TD { .. } => "TD",
5688            #[cfg(feature = "canvas")]
5689            Self::Canvas { .. } => "Canvas",
5690            Self::Textarea { .. } => "Textarea",
5691            Self::Details { .. } => "Details",
5692            Self::Summary { .. } => "Summary",
5693            Self::Select { .. } => "Select",
5694            Self::Option { .. } => "Option",
5695        }
5696    }
5697}
5698
5699/// Iterator over table rows and headings with immutable references.
5700pub struct TableIter<'a> {
5701    /// Iterator over heading row cells, if present.
5702    pub headings:
5703        Option<Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'a Container> + 'a>> + 'a>>,
5704    /// Iterator over table body row cells.
5705    pub rows: Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'a Container> + 'a>> + 'a>,
5706}
5707
5708/// Iterator over table rows and headings with mutable references.
5709pub struct TableIterMut<'a> {
5710    /// Iterator over heading row cells, if present.
5711    pub headings:
5712        Option<Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'a mut Container> + 'a>> + 'a>>,
5713    /// Iterator over table body row cells.
5714    pub rows: Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'a mut Container> + 'a>> + 'a>,
5715}
5716
5717#[cfg_attr(feature = "profiling", profiling::all_functions)]
5718impl Container {
5719    /// Creates an iterator over table rows and heading cells.
5720    ///
5721    /// Returns a [`TableIter`] that provides iterators over the table's heading row (if present)
5722    /// and body rows. Each row iterator yields individual cell containers.
5723    ///
5724    /// # Panics
5725    ///
5726    /// Will panic if `Element` is not a table
5727    #[must_use]
5728    pub fn table_iter<'a, 'b>(&'a self) -> TableIter<'b>
5729    where
5730        'a: 'b,
5731    {
5732        moosicbox_assert::assert_or_panic!(self.element == Element::Table, "Not a table");
5733
5734        let mut rows_builder: Option<Vec<Box<dyn Iterator<Item = &'b Self>>>> = None;
5735        let mut headings: Option<Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b Self>>>>> =
5736            None;
5737        let mut rows: Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b Self>>> + 'b> =
5738            Box::new(std::iter::empty());
5739
5740        for element in &self.children {
5741            match &element.element {
5742                Element::THead => {
5743                    headings =
5744                        Some(Box::new(element.children.iter().map(|x| {
5745                            Box::new(x.children.iter()) as Box<dyn Iterator<Item = &Self>>
5746                        }))
5747                            as Box<
5748                                dyn Iterator<Item = Box<dyn Iterator<Item = &'b Self>>> + 'b,
5749                            >);
5750                }
5751                Element::TBody => {
5752                    rows =
5753                        Box::new(element.children.iter().map(|x| {
5754                            Box::new(x.children.iter()) as Box<dyn Iterator<Item = &Self>>
5755                        }))
5756                            as Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b Self>>>>;
5757                }
5758                Element::TR => {
5759                    if let Some(builder) = &mut rows_builder {
5760                        builder
5761                            .push(Box::new(element.children.iter())
5762                                as Box<dyn Iterator<Item = &'b Self>>);
5763                    } else {
5764                        rows_builder
5765                            .replace(vec![Box::new(element.children.iter())
5766                                as Box<dyn Iterator<Item = &'b Self>>]);
5767                    }
5768                }
5769                _ => {
5770                    panic!("Invalid table element: {element}");
5771                }
5772            }
5773        }
5774
5775        if let Some(rows_builder) = rows_builder {
5776            rows = Box::new(rows_builder.into_iter());
5777        }
5778
5779        TableIter { headings, rows }
5780    }
5781
5782    /// Creates a mutable iterator over table rows and heading cells.
5783    ///
5784    /// Returns a [`TableIterMut`] that provides mutable iterators over the table's heading row
5785    /// (if present) and body rows. Each row iterator yields mutable references to cell containers.
5786    ///
5787    /// # Panics
5788    ///
5789    /// Will panic if `Element` is not a table
5790    #[must_use]
5791    pub fn table_iter_mut<'a, 'b>(&'a mut self) -> TableIterMut<'b>
5792    where
5793        'a: 'b,
5794    {
5795        self.table_iter_mut_with_observer(None::<fn(&mut Self)>)
5796    }
5797
5798    /// Creates a mutable iterator over table rows with an observer callback.
5799    ///
5800    /// Returns a [`TableIterMut`] that provides mutable iterators over the table's heading row
5801    /// (if present) and body rows. The optional observer function is called for each row element
5802    /// during iteration, allowing for side effects or validation.
5803    ///
5804    /// # Panics
5805    ///
5806    /// Will panic if `Element` is not a table
5807    #[must_use]
5808    pub fn table_iter_mut_with_observer<'a, 'b>(
5809        &'a mut self,
5810        mut observer: Option<impl FnMut(&mut Self)>,
5811    ) -> TableIterMut<'b>
5812    where
5813        'a: 'b,
5814    {
5815        moosicbox_assert::assert_or_panic!(self.element == Element::Table, "Not a table");
5816
5817        let mut rows_builder: Option<Vec<Box<dyn Iterator<Item = &'b mut Self>>>> = None;
5818        let mut headings: Option<
5819            Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b mut Self>>> + 'b>,
5820        > = None;
5821        let mut rows: Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b mut Self>>> + 'b> =
5822            Box::new(std::iter::empty());
5823
5824        for container in &mut self.children {
5825            if let Some(observer) = &mut observer {
5826                match container.element {
5827                    Element::THead | Element::TBody | Element::TR => {
5828                        observer(container);
5829                    }
5830                    _ => {}
5831                }
5832            }
5833            match container.element {
5834                Element::THead => {
5835                    headings = Some(Box::new(container.children.iter_mut().map(|x| {
5836                        Box::new(x.children.iter_mut()) as Box<dyn Iterator<Item = &mut Self>>
5837                    }))
5838                        as Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b mut Self>>> + 'b>);
5839                }
5840                Element::TBody => {
5841                    rows = Box::new(container.children.iter_mut().map(|x| {
5842                        Box::new(x.children.iter_mut()) as Box<dyn Iterator<Item = &mut Self>>
5843                    }))
5844                        as Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b mut Self>>>>;
5845                }
5846                Element::TR => {
5847                    if let Some(builder) = &mut rows_builder {
5848                        builder.push(Box::new(container.children.iter_mut())
5849                            as Box<dyn Iterator<Item = &'b mut Self>>);
5850                    } else {
5851                        rows_builder.replace(vec![Box::new(container.children.iter_mut())
5852                            as Box<dyn Iterator<Item = &'b mut Self>>]);
5853                    }
5854                }
5855                _ => {
5856                    panic!("Invalid table container: {container}");
5857                }
5858            }
5859        }
5860
5861        if let Some(rows_builder) = rows_builder {
5862            rows = Box::new(rows_builder.into_iter());
5863        }
5864
5865        TableIterMut { headings, rows }
5866    }
5867}
5868
5869#[derive(Copy, Clone, Debug, PartialEq, Eq)]
5870/// HTML heading level sizes.
5871pub enum HeaderSize {
5872    /// Level 1 heading.
5873    H1,
5874    /// Level 2 heading.
5875    H2,
5876    /// Level 3 heading.
5877    H3,
5878    /// Level 4 heading.
5879    H4,
5880    /// Level 5 heading.
5881    H5,
5882    /// Level 6 heading.
5883    H6,
5884}
5885
5886impl std::fmt::Display for HeaderSize {
5887    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5888        match self {
5889            Self::H1 => f.write_str("h1"),
5890            Self::H2 => f.write_str("h2"),
5891            Self::H3 => f.write_str("h3"),
5892            Self::H4 => f.write_str("h4"),
5893            Self::H5 => f.write_str("h5"),
5894            Self::H6 => f.write_str("h6"),
5895        }
5896    }
5897}
5898
5899impl From<HeaderSize> for u8 {
5900    fn from(value: HeaderSize) -> Self {
5901        match value {
5902            HeaderSize::H1 => 1,
5903            HeaderSize::H2 => 2,
5904            HeaderSize::H3 => 3,
5905            HeaderSize::H4 => 4,
5906            HeaderSize::H5 => 5,
5907            HeaderSize::H6 => 6,
5908        }
5909    }
5910}
5911
5912impl From<HeaderSize> for Number {
5913    fn from(value: HeaderSize) -> Self {
5914        Self::Integer(match value {
5915            HeaderSize::H1 => 1,
5916            HeaderSize::H2 => 2,
5917            HeaderSize::H3 => 3,
5918            HeaderSize::H4 => 4,
5919            HeaderSize::H5 => 5,
5920            HeaderSize::H6 => 6,
5921        })
5922    }
5923}
5924
5925#[derive(Clone, Debug, PartialEq, Eq)]
5926/// HTML input element types with associated properties.
5927pub enum Input {
5928    /// Checkbox input.
5929    Checkbox {
5930        /// Whether the checkbox is checked.
5931        checked: Option<bool>,
5932    },
5933    /// Text input field.
5934    Text {
5935        /// Current input value.
5936        value: Option<String>,
5937        /// Placeholder text.
5938        placeholder: Option<String>,
5939    },
5940    /// Password input field.
5941    Password {
5942        /// Current password value.
5943        value: Option<String>,
5944        /// Placeholder text.
5945        placeholder: Option<String>,
5946    },
5947    /// Hidden input field.
5948    Hidden {
5949        /// Hidden value.
5950        value: Option<String>,
5951    },
5952}
5953
5954#[cfg_attr(feature = "profiling", profiling::all_functions)]
5955impl Input {
5956    fn display(&self, f: &mut dyn Write, attrs: Attrs) -> Result<(), std::io::Error> {
5957        match self {
5958            Self::Checkbox { checked } => {
5959                let attrs = attrs.with_attr_opt("checked", checked.map(|x| x.to_string()));
5960                f.write_fmt(format_args!(
5961                    "<input type=\"checkbox\"{attrs} />",
5962                    attrs = attrs.to_string_pad_left(),
5963                ))?;
5964            }
5965            Self::Text { value, placeholder } => {
5966                let attrs = attrs
5967                    .with_attr_opt("value", value.to_owned())
5968                    .with_attr_opt("placeholder", placeholder.to_owned());
5969                f.write_fmt(format_args!(
5970                    "<input type=\"text\"{attrs} />",
5971                    attrs = attrs.to_string_pad_left(),
5972                ))?;
5973            }
5974            Self::Password { value, placeholder } => {
5975                let attrs = attrs
5976                    .with_attr_opt("value", value.to_owned())
5977                    .with_attr_opt("placeholder", placeholder.to_owned());
5978                f.write_fmt(format_args!(
5979                    "<input type=\"password\"{attrs} />",
5980                    attrs = attrs.to_string_pad_left(),
5981                ))?;
5982            }
5983            Self::Hidden { value } => {
5984                let attrs = attrs.with_attr_opt("value", value.to_owned());
5985                f.write_fmt(format_args!(
5986                    "<input type=\"hidden\"{attrs} />",
5987                    attrs = attrs.to_string_pad_left(),
5988                ))?;
5989            }
5990        }
5991
5992        Ok(())
5993    }
5994}
5995
5996#[cfg_attr(feature = "profiling", profiling::all_functions)]
5997impl std::fmt::Display for Input {
5998    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5999        match self {
6000            Self::Checkbox { checked } => {
6001                let attrs = Attrs::new().with_attr_opt("checked", checked.map(|x| x.to_string()));
6002                f.write_fmt(format_args!(
6003                    "<input type=\"checkbox\"{attrs} />",
6004                    attrs = attrs.to_string_pad_left(),
6005                ))
6006            }
6007            Self::Text { value, placeholder } => {
6008                let attrs = Attrs::new()
6009                    .with_attr_opt("value", value.to_owned())
6010                    .with_attr_opt("placeholder", placeholder.to_owned());
6011                f.write_fmt(format_args!(
6012                    "<input type=\"text\"{attrs} />",
6013                    attrs = attrs.to_string_pad_left(),
6014                ))
6015            }
6016            Self::Password { value, placeholder } => {
6017                let attrs = Attrs::new()
6018                    .with_attr_opt("value", value.to_owned())
6019                    .with_attr_opt("placeholder", placeholder.to_owned());
6020                f.write_fmt(format_args!(
6021                    "<input type=\"password\"{attrs} />",
6022                    attrs = attrs.to_string_pad_left(),
6023                ))
6024            }
6025            Self::Hidden { value } => {
6026                let attrs = Attrs::new().with_attr_opt("value", value.to_owned());
6027                f.write_fmt(format_args!(
6028                    "<input type=\"hidden\"{attrs} />",
6029                    attrs = attrs.to_string_pad_left(),
6030                ))
6031            }
6032        }
6033    }
6034}