hyperchad_transformer/
lib.rs

1#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
2#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
3#![allow(clippy::multiple_crate_versions)]
4
5use std::{any::Any, collections::HashMap, io::Write};
6
7use hyperchad_actions::Action;
8use hyperchad_color::Color;
9use hyperchad_transformer_models::{
10    AlignItems, Cursor, ImageFit, ImageLoading, JustifyContent, LayoutDirection, LayoutOverflow,
11    LinkTarget, Position, Route, TextAlign, TextDecorationLine, TextDecorationStyle, Visibility,
12};
13use parse::parse_number;
14use serde::{Deserialize, Serialize, de::Error};
15use serde_json::Value;
16
17pub use hyperchad_actions as actions;
18pub use hyperchad_transformer_models as models;
19use strum::{EnumDiscriminants, EnumIter};
20
21#[cfg(test)]
22pub mod arb;
23#[cfg(any(test, feature = "html"))]
24pub mod html;
25#[cfg(feature = "layout")]
26pub mod layout;
27pub mod parse;
28
29#[derive(Clone, Debug, PartialEq, EnumDiscriminants, Serialize, Deserialize)]
30#[strum_discriminants(derive(EnumIter))]
31#[strum_discriminants(name(CalculationType))]
32pub enum Calculation {
33    Number(Box<Number>),
34    Add(Box<Calculation>, Box<Calculation>),
35    Subtract(Box<Calculation>, Box<Calculation>),
36    Multiply(Box<Calculation>, Box<Calculation>),
37    Divide(Box<Calculation>, Box<Calculation>),
38    Grouping(Box<Calculation>),
39    Min(Box<Calculation>, Box<Calculation>),
40    Max(Box<Calculation>, Box<Calculation>),
41}
42
43impl Calculation {
44    fn calc(&self, container: f32, view_width: f32, view_height: f32) -> f32 {
45        match self {
46            Self::Number(number) => number.calc(container, view_width, view_height),
47            Self::Add(left, right) => {
48                left.calc(container, view_width, view_height)
49                    + right.calc(container, view_width, view_height)
50            }
51            Self::Subtract(left, right) => {
52                left.calc(container, view_width, view_height)
53                    - right.calc(container, view_width, view_height)
54            }
55            Self::Multiply(left, right) => {
56                left.calc(container, view_width, view_height)
57                    * right.calc(container, view_width, view_height)
58            }
59            Self::Divide(left, right) => {
60                left.calc(container, view_width, view_height)
61                    / right.calc(container, view_width, view_height)
62            }
63            Self::Grouping(value) => value.calc(container, view_width, view_height),
64            Self::Min(left, right) => {
65                let a = left.calc(container, view_width, view_height);
66                let b = right.calc(container, view_width, view_height);
67                if a > b { b } else { a }
68            }
69            Self::Max(left, right) => {
70                let a = left.calc(container, view_width, view_height);
71                let b = right.calc(container, view_width, view_height);
72                if a > b { a } else { b }
73            }
74        }
75    }
76
77    #[must_use]
78    pub fn as_dynamic(&self) -> Option<&Self> {
79        match self {
80            Self::Number(x) => {
81                if x.is_dynamic() {
82                    Some(self)
83                } else {
84                    None
85                }
86            }
87            Self::Add(a, b)
88            | Self::Subtract(a, b)
89            | Self::Multiply(a, b)
90            | Self::Divide(a, b)
91            | Self::Min(a, b)
92            | Self::Max(a, b) => {
93                if a.is_dynamic() || b.is_dynamic() {
94                    Some(self)
95                } else {
96                    None
97                }
98            }
99            Self::Grouping(x) => {
100                if x.is_dynamic() {
101                    Some(self)
102                } else {
103                    None
104                }
105            }
106        }
107    }
108
109    #[must_use]
110    pub fn is_dynamic(&self) -> bool {
111        self.as_dynamic().is_some()
112    }
113
114    #[must_use]
115    pub fn as_fixed(&self) -> Option<&Self> {
116        match self {
117            Self::Number(x) => {
118                if x.is_fixed() {
119                    Some(self)
120                } else {
121                    None
122                }
123            }
124            Self::Add(a, b)
125            | Self::Subtract(a, b)
126            | Self::Multiply(a, b)
127            | Self::Divide(a, b)
128            | Self::Min(a, b)
129            | Self::Max(a, b) => {
130                if a.is_fixed() && b.is_fixed() {
131                    Some(self)
132                } else {
133                    None
134                }
135            }
136            Self::Grouping(x) => {
137                if x.is_fixed() {
138                    Some(self)
139                } else {
140                    None
141                }
142            }
143        }
144    }
145
146    #[must_use]
147    pub fn is_fixed(&self) -> bool {
148        self.as_fixed().is_some()
149    }
150}
151
152impl std::fmt::Display for Calculation {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        match self {
155            Self::Number(number) => f.write_str(&number.to_string()),
156            Self::Add(left, right) => f.write_fmt(format_args!("{left} + {right}")),
157            Self::Subtract(left, right) => f.write_fmt(format_args!("{left} - {right}")),
158            Self::Multiply(left, right) => f.write_fmt(format_args!("{left} * {right}")),
159            Self::Divide(left, right) => f.write_fmt(format_args!("{left} / {right}")),
160            Self::Grouping(value) => f.write_fmt(format_args!("({value})")),
161            Self::Min(left, right) => f.write_fmt(format_args!("min({left}, {right})")),
162            Self::Max(left, right) => f.write_fmt(format_args!("max({left}, {right})")),
163        }
164    }
165}
166
167#[derive(Clone, Debug, EnumDiscriminants)]
168#[strum_discriminants(derive(EnumIter))]
169#[strum_discriminants(name(NumberType))]
170pub enum Number {
171    Real(f32),
172    Integer(i64),
173    RealPercent(f32),
174    IntegerPercent(i64),
175    RealDvw(f32),
176    IntegerDvw(i64),
177    RealDvh(f32),
178    IntegerDvh(i64),
179    RealVw(f32),
180    IntegerVw(i64),
181    RealVh(f32),
182    IntegerVh(i64),
183    Calc(Calculation),
184}
185
186impl Serialize for Number {
187    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
188    where
189        S: serde::Serializer,
190    {
191        match self {
192            Self::Real(x) => x.serialize(serializer),
193            Self::Integer(x) => x.serialize(serializer),
194            Self::RealPercent(x) => format!("{x}%").serialize(serializer),
195            Self::IntegerPercent(x) => format!("{x}%").serialize(serializer),
196            Self::RealDvw(x) => format!("{x}dvw").serialize(serializer),
197            Self::IntegerDvw(x) => format!("{x}dvw").serialize(serializer),
198            Self::RealDvh(x) => format!("{x}dvh").serialize(serializer),
199            Self::IntegerDvh(x) => format!("{x}dvh").serialize(serializer),
200            Self::RealVw(x) => format!("{x}vw").serialize(serializer),
201            Self::IntegerVw(x) => format!("{x}vw").serialize(serializer),
202            Self::RealVh(x) => format!("{x}vh").serialize(serializer),
203            Self::IntegerVh(x) => format!("{x}vh").serialize(serializer),
204            Self::Calc(calculation) => format!("calc({calculation})").serialize(serializer),
205        }
206    }
207}
208
209impl<'de> Deserialize<'de> for Number {
210    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
211    where
212        D: serde::Deserializer<'de>,
213    {
214        #[derive(Deserialize)]
215        #[serde(rename = "Number")]
216        enum NumberInner {
217            Real(f32),
218            Integer(i64),
219            RealPercent(f32),
220            IntegerPercent(i64),
221            RealDvw(f32),
222            IntegerDvw(i64),
223            RealDvh(f32),
224            IntegerDvh(i64),
225            RealVw(f32),
226            IntegerVw(i64),
227            RealVh(f32),
228            IntegerVh(i64),
229            Calc(Calculation),
230        }
231
232        impl From<NumberInner> for Number {
233            fn from(value: NumberInner) -> Self {
234                match value {
235                    NumberInner::Real(x) => Self::Real(x),
236                    NumberInner::Integer(x) => Self::Integer(x),
237                    NumberInner::RealPercent(x) => Self::RealPercent(x),
238                    NumberInner::IntegerPercent(x) => Self::IntegerPercent(x),
239                    NumberInner::RealDvw(x) => Self::RealDvw(x),
240                    NumberInner::IntegerDvw(x) => Self::IntegerDvw(x),
241                    NumberInner::RealDvh(x) => Self::RealDvh(x),
242                    NumberInner::IntegerDvh(x) => Self::IntegerDvh(x),
243                    NumberInner::RealVw(x) => Self::RealVw(x),
244                    NumberInner::IntegerVw(x) => Self::IntegerVw(x),
245                    NumberInner::RealVh(x) => Self::RealVh(x),
246                    NumberInner::IntegerVh(x) => Self::IntegerVh(x),
247                    NumberInner::Calc(calculation) => Self::Calc(calculation),
248                }
249            }
250        }
251
252        log::trace!("attempting to deserialize Number");
253        let value: Value = Value::deserialize(deserializer)?;
254        log::trace!("deserialized Number to {value:?}");
255
256        Ok(if value.is_i64() {
257            #[allow(clippy::cast_possible_wrap)]
258            Self::Integer(value.as_i64().unwrap())
259        } else if value.is_u64() {
260            #[allow(clippy::cast_possible_wrap)]
261            Self::Integer(value.as_u64().unwrap() as i64)
262        } else if value.is_f64() {
263            #[allow(clippy::cast_possible_truncation)]
264            Self::Real(value.as_f64().unwrap() as f32)
265        } else if value.is_string() {
266            parse_number(value.as_str().unwrap()).map_err(D::Error::custom)?
267        } else {
268            serde_json::from_value::<NumberInner>(value)
269                .map_err(D::Error::custom)?
270                .into()
271        })
272    }
273}
274
275impl Number {
276    #[must_use]
277    pub fn calc(&self, container: f32, view_width: f32, view_height: f32) -> f32 {
278        match self {
279            Self::Real(x) => *x,
280            #[allow(clippy::cast_precision_loss)]
281            Self::Integer(x) => *x as f32,
282            Self::RealPercent(x) => container * (*x / 100.0),
283            #[allow(clippy::cast_precision_loss)]
284            Self::IntegerPercent(x) => container * (*x as f32 / 100.0),
285            Self::RealVw(x) | Self::RealDvw(x) => view_width * (*x / 100.0),
286            #[allow(clippy::cast_precision_loss)]
287            Self::IntegerVw(x) | Self::IntegerDvw(x) => view_width * (*x as f32 / 100.0),
288            Self::RealVh(x) | Self::RealDvh(x) => view_height * (*x / 100.0),
289            #[allow(clippy::cast_precision_loss)]
290            Self::IntegerVh(x) | Self::IntegerDvh(x) => view_height * (*x as f32 / 100.0),
291            Self::Calc(x) => x.calc(container, view_width, view_height),
292        }
293    }
294
295    #[must_use]
296    pub fn as_dynamic(&self) -> Option<&Self> {
297        match self {
298            Self::RealPercent(_) | Self::IntegerPercent(_) => Some(self),
299            Self::Real(_)
300            | Self::Integer(_)
301            | Self::RealDvw(_)
302            | Self::IntegerDvw(_)
303            | Self::RealDvh(_)
304            | Self::IntegerDvh(_)
305            | Self::RealVw(_)
306            | Self::IntegerVw(_)
307            | Self::RealVh(_)
308            | Self::IntegerVh(_) => None,
309            Self::Calc(x) => {
310                if x.is_dynamic() {
311                    Some(self)
312                } else {
313                    None
314                }
315            }
316        }
317    }
318
319    #[must_use]
320    pub fn is_dynamic(&self) -> bool {
321        self.as_dynamic().is_some()
322    }
323
324    #[must_use]
325    pub fn as_fixed(&self) -> Option<&Self> {
326        match self {
327            Self::RealPercent(_) | Self::IntegerPercent(_) => None,
328            Self::Real(_)
329            | Self::Integer(_)
330            | Self::RealDvw(_)
331            | Self::IntegerDvw(_)
332            | Self::RealDvh(_)
333            | Self::IntegerDvh(_)
334            | Self::RealVw(_)
335            | Self::IntegerVw(_)
336            | Self::RealVh(_)
337            | Self::IntegerVh(_) => Some(self),
338            Self::Calc(x) => {
339                if x.is_fixed() {
340                    Some(self)
341                } else {
342                    None
343                }
344            }
345        }
346    }
347
348    #[must_use]
349    pub fn is_fixed(&self) -> bool {
350        self.as_fixed().is_some()
351    }
352}
353
354#[cfg(test)]
355mod test_number_deserialize {
356    use pretty_assertions::assert_eq;
357    use quickcheck_macros::quickcheck;
358
359    use crate::Number;
360
361    #[quickcheck]
362    #[allow(clippy::needless_pass_by_value)]
363    fn can_serialize_then_deserialize(number: Number) {
364        log::trace!("number={number:?}");
365        let serialized = serde_json::to_string(&number).unwrap();
366        log::trace!("serialized={serialized}");
367        let deserialized = serde_json::from_str(&serialized).unwrap();
368        log::trace!("deserialized={deserialized:?}");
369
370        assert_eq!(number, deserialized);
371    }
372}
373
374static EPSILON: f32 = 0.00001;
375
376impl PartialEq for Number {
377    fn eq(&self, other: &Self) -> bool {
378        match (self, other) {
379            #[allow(clippy::cast_precision_loss)]
380            (Self::Real(float), Self::Integer(int))
381            | (Self::RealPercent(float), Self::IntegerPercent(int))
382            | (Self::RealVw(float), Self::IntegerVw(int))
383            | (Self::RealVh(float), Self::IntegerVh(int))
384            | (Self::RealDvw(float), Self::IntegerDvw(int))
385            | (Self::RealDvh(float), Self::IntegerDvh(int))
386            | (Self::Integer(int), Self::Real(float))
387            | (Self::IntegerPercent(int), Self::RealPercent(float))
388            | (Self::IntegerVw(int), Self::RealVw(float))
389            | (Self::IntegerVh(int), Self::RealVh(float))
390            | (Self::IntegerDvw(int), Self::RealDvw(float))
391            | (Self::IntegerDvh(int), Self::RealDvh(float)) => {
392                (*int as f32 - *float).abs() < EPSILON
393            }
394            (Self::Real(l), Self::Real(r))
395            | (Self::RealPercent(l), Self::RealPercent(r))
396            | (Self::RealVw(l), Self::RealVw(r))
397            | (Self::RealVh(l), Self::RealVh(r))
398            | (Self::RealDvw(l), Self::RealDvw(r))
399            | (Self::RealDvh(l), Self::RealDvh(r)) => {
400                l.is_infinite() && r.is_infinite()
401                    || l.is_nan() && r.is_nan()
402                    || (l - r).abs() < EPSILON
403            }
404            (Self::Integer(l), Self::Integer(r))
405            | (Self::IntegerPercent(l), Self::IntegerPercent(r))
406            | (Self::IntegerVw(l), Self::IntegerVw(r))
407            | (Self::IntegerVh(l), Self::IntegerVh(r))
408            | (Self::IntegerDvw(l), Self::IntegerDvw(r))
409            | (Self::IntegerDvh(l), Self::IntegerDvh(r)) => l == r,
410            (Self::Calc(l), Self::Calc(r)) => l == r,
411            _ => false,
412        }
413    }
414}
415
416impl std::fmt::Display for Number {
417    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418        match self {
419            Self::Real(x) => {
420                if x.abs() < EPSILON {
421                    return f.write_fmt(format_args!("0"));
422                }
423                f.write_fmt(format_args!("{x}"))
424            }
425            Self::Integer(x) => {
426                if *x == 0 {
427                    return f.write_fmt(format_args!("0"));
428                }
429                f.write_fmt(format_args!("{x}"))
430            }
431            Self::RealPercent(x) => {
432                if x.abs() < EPSILON {
433                    return f.write_fmt(format_args!("0%"));
434                }
435                f.write_fmt(format_args!("{x}%"))
436            }
437            Self::IntegerPercent(x) => {
438                if *x == 0 {
439                    return f.write_fmt(format_args!("0%"));
440                }
441                f.write_fmt(format_args!("{x}%"))
442            }
443            Self::RealVw(x) => {
444                if x.abs() < EPSILON {
445                    return f.write_fmt(format_args!("0vw"));
446                }
447                f.write_fmt(format_args!("{x}vw"))
448            }
449            Self::IntegerVw(x) => {
450                if *x == 0 {
451                    return f.write_fmt(format_args!("0vw"));
452                }
453                f.write_fmt(format_args!("{x}vw"))
454            }
455            Self::RealVh(x) => {
456                if x.abs() < EPSILON {
457                    return f.write_fmt(format_args!("0vh"));
458                }
459                f.write_fmt(format_args!("{x}vh"))
460            }
461            Self::IntegerVh(x) => {
462                if *x == 0 {
463                    return f.write_fmt(format_args!("0vh"));
464                }
465                f.write_fmt(format_args!("{x}vh"))
466            }
467            Self::RealDvw(x) => {
468                if x.abs() < EPSILON {
469                    return f.write_fmt(format_args!("0dvw"));
470                }
471                f.write_fmt(format_args!("{x}dvw"))
472            }
473            Self::IntegerDvw(x) => {
474                if *x == 0 {
475                    return f.write_fmt(format_args!("0dvw"));
476                }
477                f.write_fmt(format_args!("{x}dvw"))
478            }
479            Self::RealDvh(x) => {
480                if x.abs() < EPSILON {
481                    return f.write_fmt(format_args!("0dvh"));
482                }
483                f.write_fmt(format_args!("{x}dvh"))
484            }
485            Self::IntegerDvh(x) => {
486                if *x == 0 {
487                    return f.write_fmt(format_args!("0dvh"));
488                }
489                f.write_fmt(format_args!("{x}dvh"))
490            }
491            Self::Calc(x) => f.write_fmt(format_args!("calc({x})")),
492        }
493    }
494}
495
496impl Default for Number {
497    fn default() -> Self {
498        Self::Integer(0)
499    }
500}
501
502#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
503pub struct TextDecoration {
504    pub color: Option<Color>,
505    pub line: Vec<TextDecorationLine>,
506    pub style: Option<TextDecorationStyle>,
507    pub thickness: Option<Number>,
508}
509
510#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
511pub struct Flex {
512    pub grow: Number,
513    pub shrink: Number,
514    pub basis: Number,
515}
516
517impl Default for Flex {
518    fn default() -> Self {
519        Self {
520            grow: Number::Integer(1),
521            shrink: Number::Integer(1),
522            basis: Number::IntegerPercent(0),
523        }
524    }
525}
526
527#[derive(Clone, Debug)]
528pub enum ResponsiveTrigger {
529    MaxWidth(Number),
530    MaxHeight(Number),
531}
532
533#[derive(Clone, Debug, PartialEq)]
534pub struct ConfigOverride {
535    pub condition: OverrideCondition,
536    pub overrides: Vec<OverrideItem>,
537    pub default: Option<OverrideItem>,
538}
539
540#[derive(Clone, Debug, PartialEq, Eq)]
541pub enum OverrideCondition {
542    ResponsiveTarget { name: String },
543}
544
545impl From<String> for OverrideCondition {
546    fn from(value: String) -> Self {
547        Self::ResponsiveTarget { name: value }
548    }
549}
550
551impl From<&str> for OverrideCondition {
552    fn from(value: &str) -> Self {
553        value.to_string().into()
554    }
555}
556
557#[derive(Clone, Debug, PartialEq, EnumDiscriminants)]
558#[strum_discriminants(derive(EnumIter))]
559#[strum_discriminants(name(OverrideItemType))]
560pub enum OverrideItem {
561    StrId(String),
562    Classes(Vec<String>),
563    Direction(LayoutDirection),
564    OverflowX(LayoutOverflow),
565    OverflowY(LayoutOverflow),
566    GridCellSize(Number),
567    JustifyContent(JustifyContent),
568    AlignItems(AlignItems),
569    TextAlign(TextAlign),
570    TextDecoration(TextDecoration),
571    FontFamily(Vec<String>),
572    Width(Number),
573    MinWidth(Number),
574    MaxWidth(Number),
575    Height(Number),
576    MinHeight(Number),
577    MaxHeight(Number),
578    Flex(Flex),
579    ColumnGap(Number),
580    RowGap(Number),
581    Opacity(Number),
582    Left(Number),
583    Right(Number),
584    Top(Number),
585    Bottom(Number),
586    TranslateX(Number),
587    TranslateY(Number),
588    Cursor(Cursor),
589    Position(Position),
590    Background(Color),
591    BorderTop((Color, Number)),
592    BorderRight((Color, Number)),
593    BorderBottom((Color, Number)),
594    BorderLeft((Color, Number)),
595    BorderTopLeftRadius(Number),
596    BorderTopRightRadius(Number),
597    BorderBottomLeftRadius(Number),
598    BorderBottomRightRadius(Number),
599    MarginLeft(Number),
600    MarginRight(Number),
601    MarginTop(Number),
602    MarginBottom(Number),
603    PaddingLeft(Number),
604    PaddingRight(Number),
605    PaddingTop(Number),
606    PaddingBottom(Number),
607    FontSize(Number),
608    Color(Color),
609    Hidden(bool),
610    Visibility(Visibility),
611}
612
613impl OverrideItem {
614    /// # Errors
615    ///
616    /// * If the serialization fails
617    pub fn serialize(&self) -> Result<String, serde_json::Error> {
618        match self {
619            Self::StrId(x) => serde_json::to_string(x),
620            Self::Direction(x) => serde_json::to_string(x),
621            Self::OverflowX(x) | Self::OverflowY(x) => serde_json::to_string(x),
622            Self::JustifyContent(x) => serde_json::to_string(x),
623            Self::AlignItems(x) => serde_json::to_string(x),
624            Self::TextAlign(x) => serde_json::to_string(x),
625            Self::TextDecoration(x) => serde_json::to_string(x),
626            Self::Classes(x) | Self::FontFamily(x) => serde_json::to_string(x),
627            Self::Flex(x) => serde_json::to_string(x),
628            Self::Width(x)
629            | Self::MinWidth(x)
630            | Self::MaxWidth(x)
631            | Self::Height(x)
632            | Self::MinHeight(x)
633            | Self::MaxHeight(x)
634            | Self::ColumnGap(x)
635            | Self::RowGap(x)
636            | Self::Opacity(x)
637            | Self::Left(x)
638            | Self::Right(x)
639            | Self::Top(x)
640            | Self::Bottom(x)
641            | Self::TranslateX(x)
642            | Self::TranslateY(x)
643            | Self::BorderTopLeftRadius(x)
644            | Self::BorderTopRightRadius(x)
645            | Self::BorderBottomLeftRadius(x)
646            | Self::BorderBottomRightRadius(x)
647            | Self::MarginLeft(x)
648            | Self::MarginRight(x)
649            | Self::MarginTop(x)
650            | Self::MarginBottom(x)
651            | Self::PaddingLeft(x)
652            | Self::PaddingRight(x)
653            | Self::PaddingTop(x)
654            | Self::PaddingBottom(x)
655            | Self::FontSize(x)
656            | Self::GridCellSize(x) => serde_json::to_string(x),
657            Self::Cursor(x) => serde_json::to_string(x),
658            Self::Position(x) => serde_json::to_string(x),
659            Self::BorderTop(x)
660            | Self::BorderRight(x)
661            | Self::BorderBottom(x)
662            | Self::BorderLeft(x) => serde_json::to_string(x),
663            Self::Background(x) | Self::Color(x) => serde_json::to_string(x),
664            Self::Hidden(x) => serde_json::to_string(x),
665            Self::Visibility(x) => serde_json::to_string(x),
666        }
667    }
668
669    /// # Errors
670    ///
671    /// * If the serialization fails
672    pub fn as_value(&self) -> Result<Value, serde_json::Error> {
673        match self {
674            Self::StrId(x) => serde_json::to_value(x),
675            Self::Direction(x) => serde_json::to_value(x),
676            Self::OverflowX(x) | Self::OverflowY(x) => serde_json::to_value(x),
677            Self::JustifyContent(x) => serde_json::to_value(x),
678            Self::AlignItems(x) => serde_json::to_value(x),
679            Self::TextAlign(x) => serde_json::to_value(x),
680            Self::TextDecoration(x) => serde_json::to_value(x),
681            Self::Classes(x) | Self::FontFamily(x) => serde_json::to_value(x),
682            Self::Flex(x) => serde_json::to_value(x),
683            Self::Width(x)
684            | Self::MinWidth(x)
685            | Self::MaxWidth(x)
686            | Self::Height(x)
687            | Self::MinHeight(x)
688            | Self::MaxHeight(x)
689            | Self::ColumnGap(x)
690            | Self::RowGap(x)
691            | Self::Opacity(x)
692            | Self::Left(x)
693            | Self::Right(x)
694            | Self::Top(x)
695            | Self::Bottom(x)
696            | Self::TranslateX(x)
697            | Self::TranslateY(x)
698            | Self::BorderTopLeftRadius(x)
699            | Self::BorderTopRightRadius(x)
700            | Self::BorderBottomLeftRadius(x)
701            | Self::BorderBottomRightRadius(x)
702            | Self::MarginLeft(x)
703            | Self::MarginRight(x)
704            | Self::MarginTop(x)
705            | Self::MarginBottom(x)
706            | Self::PaddingLeft(x)
707            | Self::PaddingRight(x)
708            | Self::PaddingTop(x)
709            | Self::PaddingBottom(x)
710            | Self::FontSize(x)
711            | Self::GridCellSize(x) => serde_json::to_value(x),
712            Self::Cursor(x) => serde_json::to_value(x),
713            Self::Position(x) => serde_json::to_value(x),
714            Self::BorderTop(x)
715            | Self::BorderRight(x)
716            | Self::BorderBottom(x)
717            | Self::BorderLeft(x) => serde_json::to_value(x),
718            Self::Background(x) | Self::Color(x) => serde_json::to_value(x),
719            Self::Hidden(x) => serde_json::to_value(x),
720            Self::Visibility(x) => serde_json::to_value(x),
721        }
722    }
723
724    /// # Errors
725    ///
726    /// * If the serialization fails
727    #[must_use]
728    pub fn as_any<'a>(&'a self) -> Box<dyn Any + 'a> {
729        match self {
730            Self::StrId(x) => Box::new(x),
731            Self::Direction(x) => Box::new(x),
732            Self::OverflowX(x) | Self::OverflowY(x) => Box::new(x),
733            Self::JustifyContent(x) => Box::new(x),
734            Self::AlignItems(x) => Box::new(x),
735            Self::TextAlign(x) => Box::new(x),
736            Self::TextDecoration(x) => Box::new(x),
737            Self::Classes(x) | Self::FontFamily(x) => Box::new(x),
738            Self::Flex(x) => Box::new(x),
739            Self::Width(x)
740            | Self::MinWidth(x)
741            | Self::MaxWidth(x)
742            | Self::Height(x)
743            | Self::MinHeight(x)
744            | Self::MaxHeight(x)
745            | Self::ColumnGap(x)
746            | Self::RowGap(x)
747            | Self::Opacity(x)
748            | Self::Left(x)
749            | Self::Right(x)
750            | Self::Top(x)
751            | Self::Bottom(x)
752            | Self::TranslateX(x)
753            | Self::TranslateY(x)
754            | Self::BorderTopLeftRadius(x)
755            | Self::BorderTopRightRadius(x)
756            | Self::BorderBottomLeftRadius(x)
757            | Self::BorderBottomRightRadius(x)
758            | Self::MarginLeft(x)
759            | Self::MarginRight(x)
760            | Self::MarginTop(x)
761            | Self::MarginBottom(x)
762            | Self::PaddingLeft(x)
763            | Self::PaddingRight(x)
764            | Self::PaddingTop(x)
765            | Self::PaddingBottom(x)
766            | Self::FontSize(x)
767            | Self::GridCellSize(x) => Box::new(x),
768            Self::Cursor(x) => Box::new(x),
769            Self::Position(x) => Box::new(x),
770            Self::BorderTop(x)
771            | Self::BorderRight(x)
772            | Self::BorderBottom(x)
773            | Self::BorderLeft(x) => Box::new(x),
774            Self::Background(x) | Self::Color(x) => Box::new(x),
775            Self::Hidden(x) => Box::new(x),
776            Self::Visibility(x) => Box::new(x),
777        }
778    }
779
780    /// # Errors
781    ///
782    /// * If the serialization fails
783    #[cfg(feature = "logic")]
784    #[allow(clippy::too_many_lines)]
785    fn as_json_if_expression_string(
786        &self,
787        responsive: hyperchad_actions::logic::Responsive,
788        default: Option<&Self>,
789    ) -> Result<String, serde_json::Error> {
790        match self {
791            Self::StrId(x) => {
792                let mut expr = responsive.then::<&String>(x);
793
794                if let Some(Self::StrId(default)) = default {
795                    expr = expr.or_else(default);
796                }
797
798                serde_json::to_string(&expr)
799            }
800            Self::Direction(x) => {
801                let mut expr = responsive.then::<&LayoutDirection>(x);
802
803                if let Some(Self::Direction(default)) = default {
804                    expr = expr.or_else(default);
805                }
806
807                serde_json::to_string(&expr)
808            }
809            Self::OverflowX(x) | Self::OverflowY(x) => {
810                let mut expr = responsive.then::<&LayoutOverflow>(x);
811
812                if let Some(Self::OverflowX(default) | Self::OverflowY(default)) = default {
813                    expr = expr.or_else(default);
814                }
815
816                serde_json::to_string(&expr)
817            }
818            Self::JustifyContent(x) => {
819                let mut expr = responsive.then::<&JustifyContent>(x);
820
821                if let Some(Self::JustifyContent(default)) = default {
822                    expr = expr.or_else(default);
823                }
824
825                serde_json::to_string(&expr)
826            }
827            Self::AlignItems(x) => {
828                let mut expr = responsive.then::<&AlignItems>(x);
829
830                if let Some(Self::AlignItems(default)) = default {
831                    expr = expr.or_else(default);
832                }
833
834                serde_json::to_string(&expr)
835            }
836            Self::TextAlign(x) => {
837                let mut expr = responsive.then::<&TextAlign>(x);
838
839                if let Some(Self::TextAlign(default)) = default {
840                    expr = expr.or_else(default);
841                }
842
843                serde_json::to_string(&expr)
844            }
845            Self::TextDecoration(x) => {
846                let mut expr = responsive.then::<&TextDecoration>(x);
847
848                if let Some(Self::TextDecoration(default)) = default {
849                    expr = expr.or_else(default);
850                }
851
852                serde_json::to_string(&expr)
853            }
854            Self::Classes(x) | Self::FontFamily(x) => {
855                let mut expr = responsive.then::<&Vec<String>>(x);
856
857                if let Some(Self::Classes(default) | Self::FontFamily(default)) = default {
858                    expr = expr.or_else(default);
859                }
860
861                serde_json::to_string(&expr)
862            }
863            Self::Flex(x) => {
864                let mut expr = responsive.then::<&Flex>(x);
865
866                if let Some(Self::Flex(default)) = default {
867                    expr = expr.or_else(default);
868                }
869
870                serde_json::to_string(&expr)
871            }
872            Self::Width(x)
873            | Self::MinWidth(x)
874            | Self::MaxWidth(x)
875            | Self::Height(x)
876            | Self::MinHeight(x)
877            | Self::MaxHeight(x)
878            | Self::ColumnGap(x)
879            | Self::RowGap(x)
880            | Self::Opacity(x)
881            | Self::Left(x)
882            | Self::Right(x)
883            | Self::Top(x)
884            | Self::Bottom(x)
885            | Self::TranslateX(x)
886            | Self::TranslateY(x)
887            | Self::BorderTopLeftRadius(x)
888            | Self::BorderTopRightRadius(x)
889            | Self::BorderBottomLeftRadius(x)
890            | Self::BorderBottomRightRadius(x)
891            | Self::MarginLeft(x)
892            | Self::MarginRight(x)
893            | Self::MarginTop(x)
894            | Self::MarginBottom(x)
895            | Self::PaddingLeft(x)
896            | Self::PaddingRight(x)
897            | Self::PaddingTop(x)
898            | Self::PaddingBottom(x)
899            | Self::FontSize(x)
900            | Self::GridCellSize(x) => {
901                let mut expr = responsive.then::<&Number>(x);
902
903                if let Some(
904                    Self::Width(default)
905                    | Self::MinWidth(default)
906                    | Self::MaxWidth(default)
907                    | Self::Height(default)
908                    | Self::MinHeight(default)
909                    | Self::MaxHeight(default)
910                    | Self::ColumnGap(default)
911                    | Self::RowGap(default)
912                    | Self::Opacity(default)
913                    | Self::Left(default)
914                    | Self::Right(default)
915                    | Self::Top(default)
916                    | Self::Bottom(default)
917                    | Self::TranslateX(default)
918                    | Self::TranslateY(default)
919                    | Self::BorderTopLeftRadius(default)
920                    | Self::BorderTopRightRadius(default)
921                    | Self::BorderBottomLeftRadius(default)
922                    | Self::BorderBottomRightRadius(default)
923                    | Self::MarginLeft(default)
924                    | Self::MarginRight(default)
925                    | Self::MarginTop(default)
926                    | Self::MarginBottom(default)
927                    | Self::PaddingLeft(default)
928                    | Self::PaddingRight(default)
929                    | Self::PaddingTop(default)
930                    | Self::PaddingBottom(default)
931                    | Self::FontSize(default)
932                    | Self::GridCellSize(default),
933                ) = default
934                {
935                    expr = expr.or_else(default);
936                }
937
938                serde_json::to_string(&expr)
939            }
940            Self::Cursor(x) => {
941                let mut expr = responsive.then::<&Cursor>(x);
942
943                if let Some(Self::Cursor(default)) = default {
944                    expr = expr.or_else(default);
945                }
946
947                serde_json::to_string(&expr)
948            }
949            Self::Position(x) => {
950                let mut expr = responsive.then::<&Position>(x);
951
952                if let Some(Self::Position(default)) = default {
953                    expr = expr.or_else(default);
954                }
955
956                serde_json::to_string(&expr)
957            }
958            Self::BorderTop(x)
959            | Self::BorderRight(x)
960            | Self::BorderBottom(x)
961            | Self::BorderLeft(x) => {
962                let mut expr = responsive.then::<&(Color, Number)>(x);
963
964                if let Some(
965                    Self::BorderTop(default)
966                    | Self::BorderRight(default)
967                    | Self::BorderBottom(default)
968                    | Self::BorderLeft(default),
969                ) = default
970                {
971                    expr = expr.or_else(default);
972                }
973
974                serde_json::to_string(&expr)
975            }
976            Self::Background(x) | Self::Color(x) => {
977                let mut expr = responsive.then::<&Color>(x);
978
979                if let Some(Self::Background(default) | Self::Color(default)) = default {
980                    expr = expr.or_else(default);
981                }
982
983                serde_json::to_string(&expr)
984            }
985            Self::Hidden(x) => {
986                let mut expr = responsive.then::<&bool>(x);
987
988                if let Some(Self::Hidden(default)) = default {
989                    expr = expr.or_else(default);
990                }
991
992                serde_json::to_string(&expr)
993            }
994            Self::Visibility(x) => {
995                let mut expr = responsive.then::<&Visibility>(x);
996
997                if let Some(Self::Visibility(default)) = default {
998                    expr = expr.or_else(default);
999                }
1000
1001                serde_json::to_string(&expr)
1002            }
1003        }
1004    }
1005}
1006
1007#[macro_export]
1008macro_rules! override_item {
1009    ($val:expr, $name:ident, $action:expr) => {{
1010        match $val {
1011            OverrideItem::StrId($name) => $action,
1012            OverrideItem::Data($name) => $action,
1013            OverrideItem::Direction($name) => $action,
1014            OverrideItem::OverflowX($name) | OverrideItem::OverflowY($name) => $action,
1015            OverrideItem::JustifyContent($name) => $action,
1016            OverrideItem::AlignItems($name) => $action,
1017            OverrideItem::TextAlign($name) => $action,
1018            OverrideItem::TextDecoration($name) => $action,
1019            OverrideItem::Classes($name) | OverrideItem::FontFamily($name) => $action,
1020            OverrideItem::Flex($name) => $action,
1021            OverrideItem::Width($name)
1022            | OverrideItem::MinWidth($name)
1023            | OverrideItem::MaxWidth($name)
1024            | OverrideItem::Height($name)
1025            | OverrideItem::MinHeight($name)
1026            | OverrideItem::MaxHeight($name)
1027            | OverrideItem::ColumnGap($name)
1028            | OverrideItem::RowGap($name)
1029            | OverrideItem::Opacity($name)
1030            | OverrideItem::Left($name)
1031            | OverrideItem::Right($name)
1032            | OverrideItem::Top($name)
1033            | OverrideItem::Bottom($name)
1034            | OverrideItem::TranslateX($name)
1035            | OverrideItem::TranslateY($name)
1036            | OverrideItem::BorderTopLeftRadius($name)
1037            | OverrideItem::BorderTopRightRadius($name)
1038            | OverrideItem::BorderBottomLeftRadius($name)
1039            | OverrideItem::BorderBottomRightRadius($name)
1040            | OverrideItem::MarginLeft($name)
1041            | OverrideItem::MarginRight($name)
1042            | OverrideItem::MarginTop($name)
1043            | OverrideItem::MarginBottom($name)
1044            | OverrideItem::PaddingLeft($name)
1045            | OverrideItem::PaddingRight($name)
1046            | OverrideItem::PaddingTop($name)
1047            | OverrideItem::PaddingBottom($name)
1048            | OverrideItem::FontSize($name)
1049            | OverrideItem::GridCellSize($name) => $action,
1050            OverrideItem::Cursor($name) => $action,
1051            OverrideItem::Position($name) => $action,
1052            OverrideItem::BorderTop($name)
1053            | OverrideItem::BorderRight($name)
1054            | OverrideItem::BorderBottom($name)
1055            | OverrideItem::BorderLeft($name) => $action,
1056            OverrideItem::Background($name) | OverrideItem::Color($name) => $action,
1057            OverrideItem::Hidden($name) | OverrideItem::Debug($name) => $action,
1058            OverrideItem::Visibility($name) => $action,
1059        }
1060    }};
1061}
1062
1063#[derive(Clone, Debug, Default, PartialEq)]
1064pub struct Container {
1065    pub id: usize,
1066    pub str_id: Option<String>,
1067    pub classes: Vec<String>,
1068    pub data: HashMap<String, String>,
1069    pub element: Element,
1070    pub children: Vec<Container>,
1071    pub direction: LayoutDirection,
1072    pub overflow_x: LayoutOverflow,
1073    pub overflow_y: LayoutOverflow,
1074    pub grid_cell_size: Option<Number>,
1075    pub justify_content: Option<JustifyContent>,
1076    pub align_items: Option<AlignItems>,
1077    pub text_align: Option<TextAlign>,
1078    pub text_decoration: Option<TextDecoration>,
1079    pub font_family: Option<Vec<String>>,
1080    pub width: Option<Number>,
1081    pub min_width: Option<Number>,
1082    pub max_width: Option<Number>,
1083    pub height: Option<Number>,
1084    pub min_height: Option<Number>,
1085    pub max_height: Option<Number>,
1086    pub flex: Option<Flex>,
1087    pub column_gap: Option<Number>,
1088    pub row_gap: Option<Number>,
1089    pub opacity: Option<Number>,
1090    pub left: Option<Number>,
1091    pub right: Option<Number>,
1092    pub top: Option<Number>,
1093    pub bottom: Option<Number>,
1094    pub translate_x: Option<Number>,
1095    pub translate_y: Option<Number>,
1096    pub cursor: Option<Cursor>,
1097    pub position: Option<Position>,
1098    pub background: Option<Color>,
1099    pub border_top: Option<(Color, Number)>,
1100    pub border_right: Option<(Color, Number)>,
1101    pub border_bottom: Option<(Color, Number)>,
1102    pub border_left: Option<(Color, Number)>,
1103    pub border_top_left_radius: Option<Number>,
1104    pub border_top_right_radius: Option<Number>,
1105    pub border_bottom_left_radius: Option<Number>,
1106    pub border_bottom_right_radius: Option<Number>,
1107    pub margin_left: Option<Number>,
1108    pub margin_right: Option<Number>,
1109    pub margin_top: Option<Number>,
1110    pub margin_bottom: Option<Number>,
1111    pub padding_left: Option<Number>,
1112    pub padding_right: Option<Number>,
1113    pub padding_top: Option<Number>,
1114    pub padding_bottom: Option<Number>,
1115    pub font_size: Option<Number>,
1116    pub color: Option<Color>,
1117    pub state: Option<Value>,
1118    pub hidden: Option<bool>,
1119    pub debug: Option<bool>,
1120    pub visibility: Option<Visibility>,
1121    pub route: Option<Route>,
1122    pub actions: Vec<Action>,
1123    pub overrides: Vec<ConfigOverride>,
1124    #[cfg(feature = "layout")]
1125    pub calculated_margin_left: Option<f32>,
1126    #[cfg(feature = "layout")]
1127    pub calculated_margin_right: Option<f32>,
1128    #[cfg(feature = "layout")]
1129    pub calculated_margin_top: Option<f32>,
1130    #[cfg(feature = "layout")]
1131    pub calculated_margin_bottom: Option<f32>,
1132    #[cfg(feature = "layout")]
1133    pub calculated_padding_left: Option<f32>,
1134    #[cfg(feature = "layout")]
1135    pub calculated_padding_right: Option<f32>,
1136    #[cfg(feature = "layout")]
1137    pub calculated_padding_top: Option<f32>,
1138    #[cfg(feature = "layout")]
1139    pub calculated_padding_bottom: Option<f32>,
1140    #[cfg(feature = "layout")]
1141    pub calculated_min_width: Option<f32>,
1142    #[cfg(feature = "layout")]
1143    pub calculated_child_min_width: Option<f32>,
1144    #[cfg(feature = "layout")]
1145    pub calculated_max_width: Option<f32>,
1146    #[cfg(feature = "layout")]
1147    pub calculated_preferred_width: Option<f32>,
1148    #[cfg(feature = "layout")]
1149    pub calculated_width: Option<f32>,
1150    #[cfg(feature = "layout")]
1151    pub calculated_min_height: Option<f32>,
1152    #[cfg(feature = "layout")]
1153    pub calculated_child_min_height: Option<f32>,
1154    #[cfg(feature = "layout")]
1155    pub calculated_max_height: Option<f32>,
1156    #[cfg(feature = "layout")]
1157    pub calculated_preferred_height: Option<f32>,
1158    #[cfg(feature = "layout")]
1159    pub calculated_height: Option<f32>,
1160    #[cfg(feature = "layout")]
1161    pub calculated_x: Option<f32>,
1162    #[cfg(feature = "layout")]
1163    pub calculated_y: Option<f32>,
1164    #[cfg(feature = "layout")]
1165    pub calculated_position: Option<hyperchad_transformer_models::LayoutPosition>,
1166    #[cfg(feature = "layout")]
1167    pub calculated_border_top: Option<(Color, f32)>,
1168    #[cfg(feature = "layout")]
1169    pub calculated_border_right: Option<(Color, f32)>,
1170    #[cfg(feature = "layout")]
1171    pub calculated_border_bottom: Option<(Color, f32)>,
1172    #[cfg(feature = "layout")]
1173    pub calculated_border_left: Option<(Color, f32)>,
1174    #[cfg(feature = "layout")]
1175    pub calculated_border_top_left_radius: Option<f32>,
1176    #[cfg(feature = "layout")]
1177    pub calculated_border_top_right_radius: Option<f32>,
1178    #[cfg(feature = "layout")]
1179    pub calculated_border_bottom_left_radius: Option<f32>,
1180    #[cfg(feature = "layout")]
1181    pub calculated_border_bottom_right_radius: Option<f32>,
1182    #[cfg(feature = "layout")]
1183    pub calculated_column_gap: Option<f32>,
1184    #[cfg(feature = "layout")]
1185    pub calculated_row_gap: Option<f32>,
1186    #[cfg(feature = "layout")]
1187    pub calculated_opacity: Option<f32>,
1188    #[cfg(feature = "layout")]
1189    pub calculated_font_size: Option<f32>,
1190    #[cfg(feature = "layout")]
1191    pub scrollbar_right: Option<f32>,
1192    #[cfg(feature = "layout")]
1193    pub scrollbar_bottom: Option<f32>,
1194    #[cfg(feature = "layout-offset")]
1195    pub calculated_offset_x: Option<f32>,
1196    #[cfg(feature = "layout-offset")]
1197    pub calculated_offset_y: Option<f32>,
1198}
1199
1200impl AsRef<Self> for Container {
1201    fn as_ref(&self) -> &Self {
1202        self
1203    }
1204}
1205
1206impl Container {
1207    pub fn iter_overrides(&self, recurse: bool) -> impl Iterator<Item = (&Self, &ConfigOverride)> {
1208        let mut iter: Box<dyn Iterator<Item = (&Self, &ConfigOverride)>> =
1209            if self.overrides.is_empty() {
1210                Box::new(std::iter::empty())
1211            } else {
1212                Box::new(self.overrides.iter().map(move |x| (self, x)))
1213            };
1214
1215        if recurse {
1216            for child in &self.children {
1217                iter = Box::new(iter.chain(child.iter_overrides(true)));
1218            }
1219        }
1220
1221        iter
1222    }
1223
1224    #[must_use]
1225    pub fn bfs(&self) -> BfsPaths {
1226        // Collect nodes in pre-order, recording their path
1227        fn collect_paths(
1228            node: &Container,
1229            path: &[usize],
1230            paths: &mut Vec<Vec<usize>>,
1231            levels: &mut Vec<Vec<usize>>,
1232        ) {
1233            if !node.children.is_empty() {
1234                // Store the path to this node
1235                paths.push(path.to_owned());
1236
1237                // Add this node's index to the appropriate level
1238                let level = path.len(); // Path length = level + 1 (root is at index 0)
1239                if levels.len() <= level {
1240                    levels.resize(level + 1, Vec::new());
1241                }
1242                levels[level].push(paths.len() - 1);
1243                // Process children
1244                for (i, child) in node.children.iter().enumerate() {
1245                    let mut child_path = path.to_owned();
1246                    child_path.push(i);
1247                    collect_paths(child, &child_path, paths, levels);
1248                }
1249            }
1250        }
1251
1252        // Collect nodes by level
1253        let mut levels: Vec<Vec<usize>> = Vec::new();
1254
1255        // Start by collecting all paths to nodes
1256        let mut paths: Vec<Vec<usize>> = Vec::new();
1257        collect_paths(self, &[], &mut paths, &mut levels);
1258
1259        BfsPaths { levels, paths }
1260    }
1261
1262    #[must_use]
1263    pub fn bfs_visit(&self, mut visitor: impl FnMut(&Self)) -> BfsPaths {
1264        // Collect nodes in pre-order, recording their path
1265        fn collect_paths(
1266            node: &Container,
1267            path: &[usize],
1268            paths: &mut Vec<Vec<usize>>,
1269            levels: &mut Vec<Vec<usize>>,
1270            visitor: &mut impl FnMut(&Container),
1271        ) {
1272            if !node.children.is_empty() {
1273                // Store the path to this node
1274                paths.push(path.to_owned());
1275
1276                // Add this node's index to the appropriate level
1277                let level = path.len(); // Path length = level + 1 (root is at index 0)
1278                if levels.len() <= level {
1279                    levels.resize(level + 1, Vec::new());
1280                }
1281                levels[level].push(paths.len() - 1);
1282                // Process children
1283                for (i, child) in node.children.iter().enumerate() {
1284                    visitor(child);
1285                    let mut child_path = path.to_owned();
1286                    child_path.push(i);
1287                    collect_paths(child, &child_path, paths, levels, visitor);
1288                }
1289            }
1290        }
1291
1292        // Collect nodes by level
1293        let mut levels: Vec<Vec<usize>> = Vec::new();
1294
1295        // Start by collecting all paths to nodes
1296        let mut paths: Vec<Vec<usize>> = Vec::new();
1297
1298        visitor(self);
1299        collect_paths(self, &[], &mut paths, &mut levels, &mut visitor);
1300
1301        BfsPaths { levels, paths }
1302    }
1303
1304    #[must_use]
1305    pub fn bfs_visit_mut(&mut self, mut visitor: impl FnMut(&mut Self)) -> BfsPaths {
1306        // Collect nodes in pre-order, recording their path
1307        fn collect_paths(
1308            node: &mut Container,
1309            path: &[usize],
1310            paths: &mut Vec<Vec<usize>>,
1311            levels: &mut Vec<Vec<usize>>,
1312            visitor: &mut impl FnMut(&mut Container),
1313        ) {
1314            if !node.children.is_empty() {
1315                // Store the path to this node
1316                paths.push(path.to_owned());
1317
1318                // Add this node's index to the appropriate level
1319                let level = path.len(); // Path length = level + 1 (root is at index 0)
1320                if levels.len() <= level {
1321                    levels.resize(level + 1, Vec::new());
1322                }
1323                levels[level].push(paths.len() - 1);
1324                // Process children
1325                for (i, child) in node.children.iter_mut().enumerate() {
1326                    visitor(child);
1327                    let mut child_path = path.to_owned();
1328                    child_path.push(i);
1329                    collect_paths(child, &child_path, paths, levels, visitor);
1330                }
1331            }
1332        }
1333
1334        // Collect nodes by level
1335        let mut levels: Vec<Vec<usize>> = Vec::new();
1336
1337        // Start by collecting all paths to nodes
1338        let mut paths: Vec<Vec<usize>> = Vec::new();
1339
1340        visitor(self);
1341        collect_paths(self, &[], &mut paths, &mut levels, &mut visitor);
1342
1343        BfsPaths { levels, paths }
1344    }
1345}
1346
1347impl From<&Container> for BfsPaths {
1348    fn from(root: &Container) -> Self {
1349        root.bfs()
1350    }
1351}
1352
1353pub struct BfsPaths {
1354    levels: Vec<Vec<usize>>,
1355    paths: Vec<Vec<usize>>,
1356}
1357
1358impl BfsPaths {
1359    pub fn traverse(&self, root: &Container, mut visitor: impl FnMut(&Container)) {
1360        // Follow paths to apply visitor to each node
1361        for level_nodes in &self.levels {
1362            for &node_idx in level_nodes {
1363                let path = &self.paths[node_idx];
1364
1365                // Follow the path to find the node
1366                let mut current = root;
1367
1368                for &child_idx in path {
1369                    current = &current.children[child_idx];
1370                }
1371
1372                visitor(current);
1373            }
1374        }
1375    }
1376
1377    pub fn traverse_mut(&self, root: &mut Container, mut visitor: impl FnMut(&mut Container)) {
1378        // Follow paths to apply visitor to each node
1379        for level_nodes in &self.levels {
1380            for &node_idx in level_nodes {
1381                let path = &self.paths[node_idx];
1382
1383                // Follow the path to find the node
1384                let mut current = &mut *root;
1385
1386                for &child_idx in path {
1387                    current = &mut current.children[child_idx];
1388                }
1389
1390                visitor(current);
1391            }
1392        }
1393    }
1394
1395    pub fn traverse_with_parents<R: Clone>(
1396        &self,
1397        inclusive: bool,
1398        initial: R,
1399        root: &Container,
1400        mut parent: impl FnMut(&Container, R) -> R,
1401        mut visitor: impl FnMut(&Container, R),
1402    ) {
1403        // Follow paths to apply visitor to each node
1404        for level_nodes in &self.levels {
1405            for &node_idx in level_nodes {
1406                let path = &self.paths[node_idx];
1407
1408                // Follow the path to find the node
1409                let mut current = root;
1410                let mut data = initial.clone();
1411
1412                for &child_idx in path {
1413                    data = parent(current, data);
1414                    current = &current.children[child_idx];
1415                }
1416
1417                if inclusive {
1418                    data = parent(current, data);
1419                }
1420
1421                visitor(current, data);
1422            }
1423        }
1424    }
1425
1426    pub fn traverse_with_parents_mut<R: Clone>(
1427        &self,
1428        inclusive: bool,
1429        initial: R,
1430        root: &mut Container,
1431        mut parent: impl FnMut(&mut Container, R) -> R,
1432        mut visitor: impl FnMut(&mut Container, R),
1433    ) {
1434        // Follow paths to apply visitor to each node
1435        for level_nodes in &self.levels {
1436            for &node_idx in level_nodes {
1437                let path = &self.paths[node_idx];
1438
1439                // Follow the path to find the node
1440                let mut current = &mut *root;
1441                let mut data = initial.clone();
1442
1443                for &child_idx in path {
1444                    data = parent(current, data);
1445                    current = &mut current.children[child_idx];
1446                }
1447
1448                if inclusive {
1449                    data = parent(current, data);
1450                }
1451
1452                visitor(current, data);
1453            }
1454        }
1455    }
1456
1457    pub fn traverse_with_parents_ref<R>(
1458        &self,
1459        inclusive: bool,
1460        data: &mut R,
1461        root: &Container,
1462        mut parent: impl FnMut(&Container, &mut R),
1463        mut visitor: impl FnMut(&Container, &mut R),
1464    ) {
1465        // Follow paths to apply visitor to each node
1466        for level_nodes in &self.levels {
1467            for &node_idx in level_nodes {
1468                let path = &self.paths[node_idx];
1469
1470                // Follow the path to find the node
1471                let mut current = root;
1472
1473                for &child_idx in path {
1474                    parent(current, data);
1475                    current = &current.children[child_idx];
1476                }
1477
1478                if inclusive {
1479                    parent(current, data);
1480                }
1481
1482                visitor(current, data);
1483            }
1484        }
1485    }
1486
1487    pub fn traverse_with_parents_ref_mut<R>(
1488        &self,
1489        inclusive: bool,
1490        data: &mut R,
1491        root: &mut Container,
1492        mut parent: impl FnMut(&mut Container, &mut R),
1493        mut visitor: impl FnMut(&mut Container, &mut R),
1494    ) {
1495        // Follow paths to apply visitor to each node
1496        for level_nodes in &self.levels {
1497            for &node_idx in level_nodes {
1498                let path = &self.paths[node_idx];
1499
1500                // Follow the path to find the node
1501                let mut current = &mut *root;
1502
1503                for &child_idx in path {
1504                    parent(current, data);
1505                    current = &mut current.children[child_idx];
1506                }
1507
1508                if inclusive {
1509                    parent(current, data);
1510                }
1511
1512                visitor(current, data);
1513            }
1514        }
1515    }
1516
1517    pub fn traverse_rev(&self, root: &Container, mut visitor: impl FnMut(&Container)) {
1518        // Follow paths to apply visitor to each node
1519        for level_nodes in self.levels.iter().rev() {
1520            for &node_idx in level_nodes {
1521                let path = &self.paths[node_idx];
1522
1523                // Follow the path to find the node
1524                let mut current = root;
1525
1526                for &child_idx in path {
1527                    current = &current.children[child_idx];
1528                }
1529
1530                visitor(current);
1531            }
1532        }
1533    }
1534
1535    pub fn traverse_rev_mut(&self, root: &mut Container, mut visitor: impl FnMut(&mut Container)) {
1536        // Follow paths to apply visitor to each node
1537        for level_nodes in self.levels.iter().rev() {
1538            for &node_idx in level_nodes {
1539                let path = &self.paths[node_idx];
1540
1541                // Follow the path to find the node
1542                let mut current = &mut *root;
1543
1544                for &child_idx in path {
1545                    current = &mut current.children[child_idx];
1546                }
1547
1548                visitor(current);
1549            }
1550        }
1551    }
1552
1553    pub fn traverse_rev_with_parents<R: Clone>(
1554        &self,
1555        inclusive: bool,
1556        initial: R,
1557        root: &Container,
1558        mut parent: impl FnMut(&Container, R) -> R,
1559        mut visitor: impl FnMut(&Container, R),
1560    ) {
1561        // Follow paths to apply visitor to each node
1562        for level_nodes in self.levels.iter().rev() {
1563            for &node_idx in level_nodes {
1564                let path = &self.paths[node_idx];
1565
1566                // Follow the path to find the node
1567                let mut current = root;
1568                let mut data = initial.clone();
1569
1570                for &child_idx in path {
1571                    data = parent(current, data);
1572                    current = &current.children[child_idx];
1573                }
1574
1575                if inclusive {
1576                    data = parent(current, data);
1577                }
1578
1579                visitor(current, data);
1580            }
1581        }
1582    }
1583
1584    pub fn traverse_rev_with_parents_mut<R: Clone>(
1585        &self,
1586        inclusive: bool,
1587        initial: R,
1588        root: &mut Container,
1589        mut parent: impl FnMut(&mut Container, R) -> R,
1590        mut visitor: impl FnMut(&mut Container, R),
1591    ) {
1592        // Follow paths to apply visitor to each node
1593        for level_nodes in self.levels.iter().rev() {
1594            for &node_idx in level_nodes {
1595                let path = &self.paths[node_idx];
1596
1597                // Follow the path to find the node
1598                let mut current = &mut *root;
1599                let mut data = initial.clone();
1600
1601                for &child_idx in path {
1602                    data = parent(current, data);
1603                    current = &mut current.children[child_idx];
1604                }
1605
1606                if inclusive {
1607                    data = parent(current, data);
1608                }
1609
1610                visitor(current, data);
1611            }
1612        }
1613    }
1614
1615    pub fn traverse_rev_with_parents_ref<R>(
1616        &self,
1617        inclusive: bool,
1618        initial: R,
1619        root: &Container,
1620        mut parent: impl FnMut(&Container, R) -> R,
1621        mut visitor: impl FnMut(&Container, &R),
1622    ) {
1623        let mut data = initial;
1624
1625        // Follow paths to apply visitor to each node
1626        for level_nodes in self.levels.iter().rev() {
1627            for &node_idx in level_nodes {
1628                let path = &self.paths[node_idx];
1629
1630                // Follow the path to find the node
1631                let mut current = root;
1632
1633                for &child_idx in path {
1634                    data = parent(current, data);
1635                    current = &current.children[child_idx];
1636                }
1637
1638                if inclusive {
1639                    data = parent(current, data);
1640                }
1641
1642                visitor(current, &data);
1643            }
1644        }
1645    }
1646
1647    pub fn traverse_rev_with_parents_ref_mut<R>(
1648        &self,
1649        inclusive: bool,
1650        initial: R,
1651        root: &mut Container,
1652        mut parent: impl FnMut(&mut Container, R) -> R,
1653        mut visitor: impl FnMut(&mut Container, &R),
1654    ) {
1655        let mut data = initial;
1656
1657        // Follow paths to apply visitor to each node
1658        for level_nodes in self.levels.iter().rev() {
1659            for &node_idx in level_nodes {
1660                let path = &self.paths[node_idx];
1661
1662                // Follow the path to find the node
1663                let mut current = &mut *root;
1664
1665                for &child_idx in path {
1666                    data = parent(current, data);
1667                    current = &mut current.children[child_idx];
1668                }
1669
1670                if inclusive {
1671                    data = parent(current, data);
1672                }
1673
1674                visitor(current, &data);
1675            }
1676        }
1677    }
1678}
1679
1680#[cfg(any(test, feature = "maud"))]
1681impl TryFrom<maud::Markup> for Container {
1682    type Error = tl::ParseError;
1683
1684    fn try_from(value: maud::Markup) -> Result<Self, Self::Error> {
1685        value.into_string().try_into()
1686    }
1687}
1688
1689fn visible_elements(elements: &[Container]) -> impl Iterator<Item = &Container> {
1690    elements.iter().filter(|x| x.hidden != Some(true))
1691}
1692
1693fn visible_elements_mut(elements: &mut [Container]) -> impl Iterator<Item = &mut Container> {
1694    elements.iter_mut().filter(|x| x.hidden != Some(true))
1695}
1696
1697fn relative_positioned_elements(elements: &[Container]) -> impl Iterator<Item = &Container> {
1698    visible_elements(elements).filter(|x| x.position.is_none_or(Position::is_relative))
1699}
1700
1701fn relative_positioned_elements_mut(
1702    elements: &mut [Container],
1703) -> impl Iterator<Item = &mut Container> {
1704    visible_elements_mut(elements).filter(|x| x.position.is_none_or(Position::is_relative))
1705}
1706
1707fn absolute_positioned_elements(elements: &[Container]) -> impl Iterator<Item = &Container> {
1708    visible_elements(elements).filter(|x| x.position == Some(Position::Absolute))
1709}
1710
1711fn absolute_positioned_elements_mut(
1712    elements: &mut [Container],
1713) -> impl Iterator<Item = &mut Container> {
1714    visible_elements_mut(elements).filter(|x| x.position == Some(Position::Absolute))
1715}
1716
1717fn fixed_positioned_elements(elements: &[Container]) -> impl Iterator<Item = &Container> {
1718    visible_elements(elements).filter(|x| x.is_fixed())
1719}
1720
1721fn fixed_positioned_elements_mut(
1722    elements: &mut [Container],
1723) -> impl Iterator<Item = &mut Container> {
1724    visible_elements_mut(elements).filter(|x| x.is_fixed())
1725}
1726
1727impl Container {
1728    #[must_use]
1729    pub const fn is_fixed(&self) -> bool {
1730        matches!(self.position, Some(Position::Fixed | Position::Sticky))
1731    }
1732
1733    #[must_use]
1734    pub const fn is_raw(&self) -> bool {
1735        matches!(self.element, Element::Raw { .. })
1736    }
1737}
1738
1739#[cfg_attr(feature = "profiling", profiling::all_functions)]
1740impl Container {
1741    #[must_use]
1742    pub fn is_visible(&self) -> bool {
1743        self.hidden != Some(true)
1744    }
1745
1746    #[must_use]
1747    pub fn is_hidden(&self) -> bool {
1748        self.hidden == Some(true)
1749    }
1750
1751    #[must_use]
1752    pub fn is_span(&self) -> bool {
1753        matches!(
1754            self.element,
1755            Element::Raw { .. }
1756                | Element::Span
1757                | Element::Anchor { .. }
1758                | Element::Input { .. }
1759                | Element::Button
1760                | Element::Image { .. }
1761        ) && self.children.iter().all(Self::is_span)
1762    }
1763
1764    #[must_use]
1765    pub fn is_flex_container(&self) -> bool {
1766        self.direction == LayoutDirection::Row
1767            || self.justify_content.is_some()
1768            || self.align_items.is_some()
1769            || self.children.iter().any(|x| x.flex.is_some())
1770    }
1771
1772    pub fn visible_elements(&self) -> impl Iterator<Item = &Self> {
1773        visible_elements(&self.children)
1774    }
1775
1776    pub fn visible_elements_mut(&mut self) -> impl Iterator<Item = &mut Self> {
1777        visible_elements_mut(&mut self.children)
1778    }
1779
1780    pub fn relative_positioned_elements(&self) -> impl Iterator<Item = &Self> {
1781        relative_positioned_elements(&self.children)
1782    }
1783
1784    pub fn relative_positioned_elements_mut(&mut self) -> impl Iterator<Item = &mut Self> {
1785        relative_positioned_elements_mut(&mut self.children)
1786    }
1787
1788    pub fn absolute_positioned_elements(&self) -> impl Iterator<Item = &Self> {
1789        absolute_positioned_elements(&self.children)
1790    }
1791
1792    pub fn absolute_positioned_elements_mut(&mut self) -> impl Iterator<Item = &mut Self> {
1793        absolute_positioned_elements_mut(&mut self.children)
1794    }
1795
1796    pub fn fixed_positioned_elements(&self) -> impl Iterator<Item = &Self> {
1797        fixed_positioned_elements(&self.children)
1798    }
1799
1800    pub fn fixed_positioned_elements_mut(&mut self) -> impl Iterator<Item = &mut Self> {
1801        fixed_positioned_elements_mut(&mut self.children)
1802    }
1803
1804    #[must_use]
1805    pub fn find_element_by_id(&self, id: usize) -> Option<&Self> {
1806        if self.id == id {
1807            return Some(self);
1808        }
1809        self.children.iter().find_map(|x| x.find_element_by_id(id))
1810    }
1811
1812    #[must_use]
1813    pub fn find_element_by_id_mut(&mut self, id: usize) -> Option<&mut Self> {
1814        if self.id == id {
1815            return Some(self);
1816        }
1817        self.children
1818            .iter_mut()
1819            .find_map(|x| x.find_element_by_id_mut(id))
1820    }
1821
1822    #[must_use]
1823    pub fn find_element_by_str_id(&self, str_id: &str) -> Option<&Self> {
1824        if self.str_id.as_ref().is_some_and(|x| x == str_id) {
1825            return Some(self);
1826        }
1827        self.children
1828            .iter()
1829            .find_map(|x| x.find_element_by_str_id(str_id))
1830    }
1831
1832    #[must_use]
1833    pub fn find_element_by_class(&self, class: &str) -> Option<&Self> {
1834        if self.classes.iter().any(|x| x == class) {
1835            return Some(self);
1836        }
1837        self.children
1838            .iter()
1839            .find_map(|x| x.find_element_by_class(class))
1840    }
1841
1842    #[must_use]
1843    pub fn find_element_by_str_id_mut(&mut self, str_id: &str) -> Option<&mut Self> {
1844        if self.str_id.as_ref().is_some_and(|x| x == str_id) {
1845            return Some(self);
1846        }
1847        self.children
1848            .iter_mut()
1849            .find_map(|x| x.find_element_by_str_id_mut(str_id))
1850    }
1851
1852    #[must_use]
1853    pub fn find_parent<'a>(&self, root: &'a mut Self) -> Option<&'a Self> {
1854        if root.children.iter().any(|x| x.id == self.id) {
1855            Some(root)
1856        } else {
1857            root.children
1858                .iter()
1859                .find(|x| x.children.iter().any(|x| x.id == self.id))
1860        }
1861    }
1862
1863    #[must_use]
1864    pub fn find_parent_by_id(&self, id: usize) -> Option<&Self> {
1865        if self.children.iter().any(|x| x.id == id) {
1866            Some(self)
1867        } else {
1868            self.children.iter().find_map(|x| x.find_parent_by_id(id))
1869        }
1870    }
1871
1872    #[must_use]
1873    pub fn find_parent_by_id_mut(&mut self, id: usize) -> Option<&mut Self> {
1874        if self.children.iter().any(|x| x.id == id) {
1875            Some(self)
1876        } else {
1877            self.children
1878                .iter_mut()
1879                .find_map(|x| x.find_parent_by_id_mut(id))
1880        }
1881    }
1882
1883    #[must_use]
1884    pub fn find_parent_by_str_id_mut(&mut self, id: &str) -> Option<&mut Self> {
1885        if self
1886            .children
1887            .iter()
1888            .filter_map(|x| x.str_id.as_ref())
1889            .map(String::as_str)
1890            .any(|x| x == id)
1891        {
1892            Some(self)
1893        } else {
1894            self.children
1895                .iter_mut()
1896                .find_map(|x| x.find_parent_by_str_id_mut(id))
1897        }
1898    }
1899
1900    /// # Panics
1901    ///
1902    /// * If the `Container` is the root node
1903    /// * If the `Container` is not properly attached to the tree
1904    #[must_use]
1905    pub fn replace_with_elements(&mut self, replacement: Vec<Self>, root: &mut Self) -> Self {
1906        let Some(parent) = root.find_parent_by_id_mut(self.id) else {
1907            panic!("Cannot replace the root node with multiple elements");
1908        };
1909
1910        let index = parent
1911            .children
1912            .iter()
1913            .enumerate()
1914            .find_map(|(i, x)| if x.id == self.id { Some(i) } else { None })
1915            .unwrap_or_else(|| panic!("Container is not attached properly to tree"));
1916
1917        let original = parent.children.remove(index);
1918
1919        for (i, element) in replacement.into_iter().enumerate() {
1920            parent.children.insert(index + i, element);
1921        }
1922
1923        original
1924    }
1925
1926    /// # Panics
1927    ///
1928    /// * If the `Container` is not properly attached to the tree
1929    pub fn replace_id_children_with_elements(
1930        &mut self,
1931        replacement: Vec<Self>,
1932        id: usize,
1933    ) -> Option<Vec<Self>> {
1934        let parent = self.find_element_by_id_mut(id)?;
1935
1936        let original = parent.children.drain(..).collect::<Vec<_>>();
1937
1938        for element in replacement {
1939            parent.children.push(element);
1940        }
1941
1942        Some(original)
1943    }
1944
1945    /// # Panics
1946    ///
1947    /// * If the `Container` is not properly attached to the tree
1948    #[cfg(feature = "layout")]
1949    pub fn replace_id_children_with_elements_calc(
1950        &mut self,
1951        calculator: &impl layout::Calc,
1952        replacement: Vec<Self>,
1953        id: usize,
1954    ) -> bool {
1955        let Some(parent_id) = self.find_element_by_id(id).map(|x| x.id) else {
1956            return false;
1957        };
1958
1959        self.replace_id_children_with_elements(replacement, id);
1960
1961        self.partial_calc(calculator, parent_id);
1962
1963        true
1964    }
1965
1966    /// # Panics
1967    ///
1968    /// * If the `Container` is not properly attached to the tree
1969    pub fn replace_id_with_elements(&mut self, replacement: Vec<Self>, id: usize) -> Option<Self> {
1970        let parent = self.find_parent_by_id_mut(id)?;
1971
1972        let index = parent
1973            .children
1974            .iter()
1975            .enumerate()
1976            .find_map(|(i, x)| if x.id == id { Some(i) } else { None })?;
1977
1978        let original = parent.children.remove(index);
1979
1980        for (i, element) in replacement.into_iter().enumerate() {
1981            parent.children.insert(index + i, element);
1982        }
1983
1984        Some(original)
1985    }
1986
1987    /// # Panics
1988    ///
1989    /// * If the `Container` is not properly attached to the tree
1990    #[cfg(feature = "layout")]
1991    pub fn replace_id_with_elements_calc(
1992        &mut self,
1993        calculator: &impl layout::Calc,
1994        replacement: Vec<Self>,
1995        id: usize,
1996    ) -> bool {
1997        let Some(parent_id) = self.find_parent_by_id_mut(id).map(|x| x.id) else {
1998            return false;
1999        };
2000
2001        self.replace_id_with_elements(replacement, id);
2002
2003        self.partial_calc(calculator, parent_id);
2004
2005        true
2006    }
2007
2008    /// # Panics
2009    ///
2010    /// * If the `Container` is not properly attached to the tree
2011    pub fn replace_str_id_with_elements(
2012        &mut self,
2013        replacement: Vec<Self>,
2014        id: &str,
2015    ) -> Option<Self> {
2016        let parent = self.find_parent_by_str_id_mut(id)?;
2017
2018        let index = parent
2019            .children
2020            .iter()
2021            .enumerate()
2022            .find_map(|(i, x)| {
2023                if x.str_id.as_ref().is_some_and(|x| x.as_str() == id) {
2024                    Some(i)
2025                } else {
2026                    None
2027                }
2028            })
2029            .unwrap_or_else(|| panic!("Container is not attached properly to tree"));
2030
2031        let original = parent.children.remove(index);
2032
2033        for (i, element) in replacement.into_iter().enumerate() {
2034            parent.children.insert(index + i, element);
2035        }
2036
2037        Some(original)
2038    }
2039
2040    /// # Panics
2041    ///
2042    /// * If the `Container` is not properly attached to the tree
2043    #[cfg(feature = "layout")]
2044    pub fn replace_str_id_with_elements_calc(
2045        &mut self,
2046        calculator: &impl layout::Calc,
2047        replacement: Vec<Self>,
2048        id: &str,
2049    ) -> Option<Self> {
2050        let parent_id = self.find_parent_by_str_id_mut(id)?.id;
2051
2052        let element = self.replace_str_id_with_elements(replacement, id);
2053
2054        self.partial_calc(calculator, parent_id);
2055
2056        element
2057    }
2058
2059    #[cfg(feature = "layout")]
2060    pub fn partial_calc(&mut self, calculator: &impl layout::Calc, id: usize) {
2061        let Some(parent) = self.find_parent_by_id_mut(id) else {
2062            return;
2063        };
2064
2065        if calculator.calc(parent) {
2066            calculator.calc(self);
2067        }
2068    }
2069}
2070
2071#[derive(Default, Clone, Debug, PartialEq)]
2072pub enum Element {
2073    #[default]
2074    Div,
2075    Raw {
2076        value: String,
2077    },
2078    Aside,
2079    Main,
2080    Header,
2081    Footer,
2082    Section,
2083    Form,
2084    Span,
2085    Input {
2086        input: Input,
2087    },
2088    Button,
2089    Image {
2090        source: Option<String>,
2091        alt: Option<String>,
2092        fit: Option<ImageFit>,
2093        source_set: Option<String>,
2094        sizes: Option<Number>,
2095        loading: Option<ImageLoading>,
2096    },
2097    Anchor {
2098        target: Option<LinkTarget>,
2099        href: Option<String>,
2100    },
2101    Heading {
2102        size: HeaderSize,
2103    },
2104    UnorderedList,
2105    OrderedList,
2106    ListItem,
2107    Table,
2108    THead,
2109    TH,
2110    TBody,
2111    TR,
2112    TD,
2113    #[cfg(feature = "canvas")]
2114    Canvas,
2115}
2116
2117#[derive(Default)]
2118struct Attrs {
2119    values: Vec<(String, String)>,
2120}
2121
2122#[derive(Debug)]
2123pub enum MaybeReplaced<T: std::fmt::Display> {
2124    NotReplaced(T),
2125    Replaced(String),
2126}
2127
2128#[cfg_attr(feature = "profiling", profiling::all_functions)]
2129impl Attrs {
2130    fn new() -> Self {
2131        Self::default()
2132    }
2133
2134    #[allow(unused)]
2135    fn with_attr<K: Into<String>, V: std::fmt::Display + 'static>(
2136        mut self,
2137        name: K,
2138        value: V,
2139    ) -> Self {
2140        self.add(name, value);
2141        self
2142    }
2143
2144    fn with_attr_opt<K: Into<String>, V: std::fmt::Display + 'static>(
2145        mut self,
2146        name: K,
2147        value: Option<V>,
2148    ) -> Self {
2149        self.add_opt(name, value);
2150        self
2151    }
2152
2153    fn to_string_pad_left(&self) -> String {
2154        if self.values.is_empty() {
2155            String::new()
2156        } else {
2157            format!(
2158                " {}",
2159                self.values
2160                    .iter()
2161                    .map(|(name, value)| format!("{name}=\"{value}\""))
2162                    .collect::<Vec<_>>()
2163                    .join(" ")
2164            )
2165        }
2166    }
2167
2168    #[allow(unused)]
2169    fn replace_or_add<V: std::fmt::Display>(&mut self, name: &str, new_value: V) -> Option<String> {
2170        match self.replace(name, new_value) {
2171            MaybeReplaced::NotReplaced(x) => {
2172                self.add(name, x);
2173                None
2174            }
2175            MaybeReplaced::Replaced(x) => Some(x),
2176        }
2177    }
2178
2179    #[allow(unused)]
2180    fn replace<V: std::fmt::Display>(&mut self, name: &str, new_value: V) -> MaybeReplaced<V> {
2181        for (key, value) in &mut self.values {
2182            if key == name {
2183                let mut encoded =
2184                    html_escape::encode_double_quoted_attribute(new_value.to_string().as_str())
2185                        .to_string()
2186                        .replace('\n', "&#10;");
2187
2188                std::mem::swap(value, &mut encoded);
2189
2190                let old_value = encoded;
2191
2192                return MaybeReplaced::Replaced(old_value);
2193            }
2194        }
2195
2196        MaybeReplaced::NotReplaced(new_value)
2197    }
2198
2199    fn add<K: Into<String>, V: std::fmt::Display>(&mut self, name: K, value: V) {
2200        self.values.push((
2201            name.into(),
2202            html_escape::encode_double_quoted_attribute(value.to_string().as_str())
2203                .to_string()
2204                .replace('\n', "&#10;"),
2205        ));
2206    }
2207
2208    fn add_opt<K: Into<String>, V: std::fmt::Display>(&mut self, name: K, value: Option<V>) {
2209        if let Some(value) = value {
2210            self.values.push((
2211                name.into(),
2212                html_escape::encode_double_quoted_attribute(value.to_string().as_str())
2213                    .to_string()
2214                    .replace('\n', "&#10;"),
2215            ));
2216        }
2217    }
2218
2219    #[cfg(feature = "layout")]
2220    fn add_opt_skip_default<K: Into<String>, V: Default + PartialEq + std::fmt::Display>(
2221        &mut self,
2222        name: K,
2223        value: Option<V>,
2224        skip_default: bool,
2225    ) {
2226        if let Some(value) = value {
2227            if skip_default && value == Default::default() {
2228                return;
2229            }
2230            self.values.push((
2231                name.into(),
2232                html_escape::encode_double_quoted_attribute(value.to_string().as_str())
2233                    .to_string()
2234                    .replace('\n', "&#10;"),
2235            ));
2236        }
2237    }
2238}
2239
2240#[cfg_attr(feature = "profiling", profiling::all_functions)]
2241impl Container {
2242    #[allow(clippy::too_many_lines)]
2243    fn attrs(&self, #[allow(unused)] with_debug_attrs: bool) -> Attrs {
2244        let mut attrs = Attrs { values: vec![] };
2245
2246        attrs.add("dbg-id", self.id);
2247
2248        attrs.add_opt("id", self.str_id.as_ref());
2249
2250        match &self.element {
2251            Element::Image {
2252                fit,
2253                source_set,
2254                sizes,
2255                alt,
2256                loading,
2257                ..
2258            } => {
2259                attrs.add_opt("sx-fit", *fit);
2260                attrs.add_opt("loading", *loading);
2261                attrs.add_opt("srcset", source_set.as_ref());
2262                attrs.add_opt("sizes", sizes.as_ref());
2263                attrs.add_opt("alt", alt.as_ref());
2264            }
2265            Element::Anchor { target, .. } => {
2266                attrs.add_opt("target", target.as_ref());
2267            }
2268            Element::Div
2269            | Element::Raw { .. }
2270            | Element::Aside
2271            | Element::Main
2272            | Element::Header
2273            | Element::Footer
2274            | Element::Section
2275            | Element::Form
2276            | Element::Span
2277            | Element::Input { .. }
2278            | Element::Button
2279            | Element::Heading { .. }
2280            | Element::UnorderedList
2281            | Element::OrderedList
2282            | Element::ListItem
2283            | Element::Table
2284            | Element::THead
2285            | Element::TH
2286            | Element::TBody
2287            | Element::TR
2288            | Element::TD => {}
2289            #[cfg(feature = "canvas")]
2290            Element::Canvas => {}
2291        }
2292
2293        let mut data = self.data.iter().collect::<Vec<_>>();
2294        data.sort_by(|(a, _), (b, _)| (*a).cmp(b));
2295
2296        if !self.classes.is_empty() {
2297            attrs.add("class", self.classes.join(" "));
2298        }
2299
2300        for (name, value) in data {
2301            attrs.add(format!("data-{name}"), value);
2302        }
2303
2304        if let Some(route) = &self.route {
2305            match route {
2306                Route::Get {
2307                    route,
2308                    trigger,
2309                    swap,
2310                } => {
2311                    attrs.add("hx-get", route);
2312                    attrs.add_opt("hx-trigger", trigger.clone());
2313                    attrs.add("hx-swap", swap);
2314                }
2315                Route::Post {
2316                    route,
2317                    trigger,
2318                    swap,
2319                } => {
2320                    attrs.add("hx-post", route);
2321                    attrs.add_opt("hx-trigger", trigger.clone());
2322                    attrs.add("hx-swap", swap);
2323                }
2324            }
2325        }
2326
2327        attrs.add_opt("sx-justify-content", self.justify_content.as_ref());
2328        attrs.add_opt("sx-align-items", self.align_items.as_ref());
2329
2330        attrs.add_opt("sx-text-align", self.text_align.as_ref());
2331
2332        if let Some(text_decoration) = &self.text_decoration {
2333            attrs.add_opt("sx-text-decoration-color", text_decoration.color);
2334            attrs.add(
2335                "sx-text-decoration-line",
2336                text_decoration
2337                    .line
2338                    .iter()
2339                    .map(ToString::to_string)
2340                    .collect::<Vec<_>>()
2341                    .join(" "),
2342            );
2343            attrs.add_opt("sx-text-decoration-style", text_decoration.style);
2344            attrs.add_opt(
2345                "sx-text-decoration-thickness",
2346                text_decoration.thickness.as_ref(),
2347            );
2348        }
2349
2350        if let Some(font_family) = &self.font_family {
2351            attrs.add("sx-font-family", font_family.join(","));
2352        }
2353
2354        match self.element {
2355            Element::TR => {
2356                if self.direction != LayoutDirection::Row {
2357                    attrs.add("sx-dir", self.direction);
2358                }
2359            }
2360            _ => {
2361                if self.direction != LayoutDirection::default() {
2362                    attrs.add("sx-dir", self.direction);
2363                }
2364            }
2365        }
2366
2367        attrs.add_opt("sx-position", self.position);
2368
2369        attrs.add_opt("sx-background", self.background);
2370
2371        attrs.add_opt("sx-width", self.width.as_ref());
2372        attrs.add_opt("sx-min-width", self.min_width.as_ref());
2373        attrs.add_opt("sx-max-width", self.max_width.as_ref());
2374        attrs.add_opt("sx-height", self.height.as_ref());
2375        attrs.add_opt("sx-min-height", self.min_height.as_ref());
2376        attrs.add_opt("sx-max-height", self.max_height.as_ref());
2377
2378        if let Some(flex) = &self.flex {
2379            attrs.add("sx-flex-grow", &flex.grow);
2380            attrs.add("sx-flex-shrink", &flex.shrink);
2381            attrs.add("sx-flex-basis", &flex.basis);
2382        }
2383
2384        attrs.add_opt("sx-col-gap", self.column_gap.as_ref());
2385        attrs.add_opt("sx-row-gap", self.row_gap.as_ref());
2386        attrs.add_opt("sx-grid-cell-size", self.grid_cell_size.as_ref());
2387
2388        attrs.add_opt("sx-opacity", self.opacity.as_ref());
2389
2390        attrs.add_opt("sx-left", self.left.as_ref());
2391        attrs.add_opt("sx-right", self.right.as_ref());
2392        attrs.add_opt("sx-top", self.top.as_ref());
2393        attrs.add_opt("sx-bottom", self.bottom.as_ref());
2394
2395        attrs.add_opt("sx-translate-x", self.translate_x.as_ref());
2396        attrs.add_opt("sx-translate-y", self.translate_y.as_ref());
2397
2398        attrs.add_opt("sx-cursor", self.cursor.as_ref());
2399
2400        attrs.add_opt("sx-padding-left", self.padding_left.as_ref());
2401        attrs.add_opt("sx-padding-right", self.padding_right.as_ref());
2402        attrs.add_opt("sx-padding-top", self.padding_top.as_ref());
2403        attrs.add_opt("sx-padding-bottom", self.padding_bottom.as_ref());
2404
2405        attrs.add_opt("sx-margin-left", self.margin_left.as_ref());
2406        attrs.add_opt("sx-margin-right", self.margin_right.as_ref());
2407        attrs.add_opt("sx-margin-top", self.margin_top.as_ref());
2408        attrs.add_opt("sx-margin-bottom", self.margin_bottom.as_ref());
2409
2410        attrs.add_opt("sx-hidden", self.hidden.as_ref());
2411        attrs.add_opt("sx-visibility", self.visibility.as_ref());
2412
2413        attrs.add_opt("sx-font-size", self.font_size.as_ref());
2414        attrs.add_opt("sx-color", self.color.as_ref());
2415
2416        attrs.add_opt("debug", self.debug.as_ref());
2417
2418        attrs.add_opt(
2419            "sx-border-left",
2420            self.border_left
2421                .as_ref()
2422                .map(|(color, size)| format!("{size}, {color}")),
2423        );
2424        attrs.add_opt(
2425            "sx-border-right",
2426            self.border_right
2427                .as_ref()
2428                .map(|(color, size)| format!("{size}, {color}")),
2429        );
2430        attrs.add_opt(
2431            "sx-border-top",
2432            self.border_top
2433                .as_ref()
2434                .map(|(color, size)| format!("{size}, {color}")),
2435        );
2436        attrs.add_opt(
2437            "sx-border-bottom",
2438            self.border_bottom
2439                .as_ref()
2440                .map(|(color, size)| format!("{size}, {color}")),
2441        );
2442        attrs.add_opt(
2443            "sx-border-top-left-radius",
2444            self.border_top_left_radius.as_ref(),
2445        );
2446        attrs.add_opt(
2447            "sx-border-top-right-radius",
2448            self.border_top_right_radius.as_ref(),
2449        );
2450        attrs.add_opt(
2451            "sx-border-bottom-left-radius",
2452            self.border_bottom_left_radius.as_ref(),
2453        );
2454        attrs.add_opt(
2455            "sx-border-bottom-right-radius",
2456            self.border_bottom_right_radius.as_ref(),
2457        );
2458
2459        attrs.add_opt("state", self.state.as_ref());
2460
2461        for action in &self.actions {
2462            match &action.trigger {
2463                hyperchad_actions::ActionTrigger::Click => {
2464                    attrs.add("fx-click", action.action.to_string());
2465                }
2466                hyperchad_actions::ActionTrigger::ClickOutside => {
2467                    attrs.add("fx-click-outside", action.action.to_string());
2468                }
2469                hyperchad_actions::ActionTrigger::MouseDown => {
2470                    attrs.add("fx-mouse-down", action.action.to_string());
2471                }
2472                hyperchad_actions::ActionTrigger::Hover => {
2473                    attrs.add("fx-hover", action.action.to_string());
2474                }
2475                hyperchad_actions::ActionTrigger::Change => {
2476                    attrs.add("fx-change", action.action.to_string());
2477                }
2478                hyperchad_actions::ActionTrigger::Resize => {
2479                    attrs.add("fx-resize", action.action.to_string());
2480                }
2481                hyperchad_actions::ActionTrigger::Immediate => {
2482                    attrs.add("fx-immediate", action.action.to_string());
2483                }
2484                hyperchad_actions::ActionTrigger::Event(..) => {
2485                    attrs.add("fx-event", action.action.to_string());
2486                }
2487            }
2488        }
2489
2490        match self.overflow_x {
2491            LayoutOverflow::Auto => {
2492                attrs.add("sx-overflow-x", "auto");
2493            }
2494            LayoutOverflow::Scroll => {
2495                attrs.add("sx-overflow-x", "scroll");
2496            }
2497            LayoutOverflow::Expand => {}
2498            LayoutOverflow::Squash => {
2499                attrs.add("sx-overflow-x", "squash");
2500            }
2501            LayoutOverflow::Wrap { grid } => {
2502                attrs.add("sx-overflow-x", if grid { "wrap-grid" } else { "wrap" });
2503            }
2504            LayoutOverflow::Hidden => {
2505                attrs.add("sx-overflow-x", "hidden");
2506            }
2507        }
2508        match self.overflow_y {
2509            LayoutOverflow::Auto => {
2510                attrs.add("sx-overflow-y", "auto");
2511            }
2512            LayoutOverflow::Scroll => {
2513                attrs.add("sx-overflow-y", "scroll");
2514            }
2515            LayoutOverflow::Expand => {}
2516            LayoutOverflow::Squash => {
2517                attrs.add("sx-overflow-y", "squash");
2518            }
2519            LayoutOverflow::Wrap { grid } => {
2520                attrs.add("sx-overflow-y", if grid { "wrap-grid" } else { "wrap" });
2521            }
2522            LayoutOverflow::Hidden => {
2523                attrs.add("sx-overflow-y", "hidden");
2524            }
2525        }
2526
2527        #[cfg(feature = "layout")]
2528        if with_debug_attrs {
2529            let skip_default = std::env::var("SKIP_DEFAULT_DEBUG_ATTRS")
2530                .is_ok_and(|x| ["1", "true"].contains(&x.to_lowercase().as_str()));
2531
2532            attrs.add_opt_skip_default("calc-x", self.calculated_x, skip_default);
2533            attrs.add_opt_skip_default("calc-y", self.calculated_y, skip_default);
2534            attrs.add_opt_skip_default("calc-min-width", self.calculated_min_width, skip_default);
2535            attrs.add_opt_skip_default(
2536                "calc-child-min-width",
2537                self.calculated_child_min_width,
2538                skip_default,
2539            );
2540            attrs.add_opt_skip_default("calc-max-width", self.calculated_max_width, skip_default);
2541            attrs.add_opt_skip_default(
2542                "calc-preferred-width",
2543                self.calculated_preferred_width,
2544                skip_default,
2545            );
2546            attrs.add_opt_skip_default("calc-width", self.calculated_width, skip_default);
2547            attrs.add_opt_skip_default("calc-min-height", self.calculated_min_height, skip_default);
2548            attrs.add_opt_skip_default(
2549                "calc-child-min-height",
2550                self.calculated_child_min_height,
2551                skip_default,
2552            );
2553            attrs.add_opt_skip_default("calc-max-height", self.calculated_max_height, skip_default);
2554            attrs.add_opt_skip_default(
2555                "calc-preferred-height",
2556                self.calculated_preferred_height,
2557                skip_default,
2558            );
2559            attrs.add_opt_skip_default("calc-height", self.calculated_height, skip_default);
2560            attrs.add_opt_skip_default(
2561                "calc-margin-left",
2562                self.calculated_margin_left,
2563                skip_default,
2564            );
2565            attrs.add_opt_skip_default(
2566                "calc-margin-right",
2567                self.calculated_margin_right,
2568                skip_default,
2569            );
2570            attrs.add_opt_skip_default("calc-margin-top", self.calculated_margin_top, skip_default);
2571            attrs.add_opt_skip_default(
2572                "calc-margin-bottom",
2573                self.calculated_margin_bottom,
2574                skip_default,
2575            );
2576            attrs.add_opt_skip_default(
2577                "calc-padding-left",
2578                self.calculated_padding_left,
2579                skip_default,
2580            );
2581            attrs.add_opt_skip_default(
2582                "calc-padding-right",
2583                self.calculated_padding_right,
2584                skip_default,
2585            );
2586            attrs.add_opt_skip_default(
2587                "calc-padding-top",
2588                self.calculated_padding_top,
2589                skip_default,
2590            );
2591            attrs.add_opt_skip_default(
2592                "calc-padding-bottom",
2593                self.calculated_padding_bottom,
2594                skip_default,
2595            );
2596            attrs.add_opt_skip_default(
2597                "calc-border-left",
2598                self.calculated_border_left
2599                    .map(|(color, size)| format!("{size}, {color}")),
2600                skip_default,
2601            );
2602            attrs.add_opt_skip_default(
2603                "calc-border-right",
2604                self.calculated_border_right
2605                    .map(|(color, size)| format!("{size}, {color}")),
2606                skip_default,
2607            );
2608            attrs.add_opt_skip_default(
2609                "calc-border-top",
2610                self.calculated_border_top
2611                    .map(|(color, size)| format!("{size}, {color}")),
2612                skip_default,
2613            );
2614            attrs.add_opt_skip_default(
2615                "calc-border-bottom",
2616                self.calculated_border_bottom
2617                    .map(|(color, size)| format!("{size}, {color}")),
2618                skip_default,
2619            );
2620            attrs.add_opt_skip_default(
2621                "calc-border-top-left-radius",
2622                self.calculated_border_top_left_radius,
2623                skip_default,
2624            );
2625            attrs.add_opt_skip_default(
2626                "calc-border-top-right-radius",
2627                self.calculated_border_top_right_radius,
2628                skip_default,
2629            );
2630            attrs.add_opt_skip_default(
2631                "calc-border-bottom-left-radius",
2632                self.calculated_border_bottom_left_radius,
2633                skip_default,
2634            );
2635            attrs.add_opt_skip_default(
2636                "calc-border-bottom-right-radius",
2637                self.calculated_border_bottom_right_radius,
2638                skip_default,
2639            );
2640            attrs.add_opt_skip_default("calc-col-gap", self.calculated_column_gap, skip_default);
2641            attrs.add_opt_skip_default("calc-row-gap", self.calculated_row_gap, skip_default);
2642            attrs.add_opt_skip_default("calc-opacity", self.calculated_opacity, skip_default);
2643            attrs.add_opt_skip_default("calc-font-size", self.calculated_font_size, skip_default);
2644            attrs.add_opt_skip_default("calc-scrollbar-right", self.scrollbar_right, skip_default);
2645            attrs.add_opt_skip_default(
2646                "calc-scrollbar-bottom",
2647                self.scrollbar_bottom,
2648                skip_default,
2649            );
2650
2651            if let Some(hyperchad_transformer_models::LayoutPosition::Wrap { row, col }) =
2652                &self.calculated_position
2653            {
2654                attrs.add("calc-row", *row);
2655                attrs.add("calc-col", *col);
2656            }
2657            #[cfg(feature = "layout-offset")]
2658            {
2659                attrs.add_opt_skip_default("calc-offset-x", self.calculated_offset_x, skip_default);
2660                attrs.add_opt_skip_default("calc-offset-y", self.calculated_offset_y, skip_default);
2661            }
2662        }
2663
2664        #[cfg(feature = "logic")]
2665        for config in &self.overrides {
2666            for item in &config.overrides {
2667                let name = override_item_to_attr_name(item);
2668
2669                match &config.condition {
2670                    OverrideCondition::ResponsiveTarget { name: target } => {
2671                        match item {
2672                            OverrideItem::Flex(..) => {
2673                                attrs.values.retain(|(x, _)| {
2674                                    !matches!(
2675                                        x.as_str(),
2676                                        "sx-flex-grow" | "sx-flex-basis" | "sx-flex-shrink",
2677                                    )
2678                                });
2679                            }
2680                            OverrideItem::TextDecoration(..) => {
2681                                attrs.values.retain(|(x, _)| {
2682                                    !matches!(
2683                                        x.as_str(),
2684                                        "sx-text-decoration-line"
2685                                            | "sx-text-decoration-style"
2686                                            | "sx-text-decoration-color"
2687                                            | "sx-text-decoration-thickness",
2688                                    )
2689                                });
2690                            }
2691                            _ => {}
2692                        }
2693
2694                        attrs.replace_or_add(
2695                            name,
2696                            item.as_json_if_expression_string(
2697                                hyperchad_actions::logic::Responsive::Target(target.clone()),
2698                                config.default.as_ref(),
2699                            )
2700                            .unwrap(),
2701                        );
2702                    }
2703                }
2704            }
2705        }
2706
2707        attrs.values.sort_by(|(a, _), (b, _)| a.cmp(b));
2708
2709        attrs
2710    }
2711
2712    fn attrs_to_string_pad_left(&self, with_debug_attrs: bool) -> String {
2713        self.attrs(with_debug_attrs).to_string_pad_left()
2714    }
2715
2716    #[cfg_attr(feature = "profiling", profiling::function)]
2717    #[allow(clippy::too_many_lines)]
2718    fn display(
2719        &self,
2720        f: &mut dyn Write,
2721        with_debug_attrs: bool,
2722        wrap_raw_in_element: bool,
2723    ) -> Result<(), std::io::Error> {
2724        match &self.element {
2725            Element::Raw { value } => {
2726                if wrap_raw_in_element {
2727                    f.write_fmt(format_args!(
2728                        "<raw{attrs}>",
2729                        attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2730                    ))?;
2731                    f.write_fmt(format_args!("{value}</raw>"))?;
2732                } else {
2733                    f.write_fmt(format_args!("{value}"))?;
2734                }
2735            }
2736            Element::Div => {
2737                f.write_fmt(format_args!(
2738                    "<div{attrs}>",
2739                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2740                ))?;
2741                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2742                f.write_fmt(format_args!("</div>"))?;
2743            }
2744            Element::Aside => {
2745                f.write_fmt(format_args!(
2746                    "<aside{attrs}>",
2747                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2748                ))?;
2749                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2750                f.write_fmt(format_args!("</aside>"))?;
2751            }
2752
2753            Element::Main => {
2754                f.write_fmt(format_args!(
2755                    "<main{attrs}>",
2756                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2757                ))?;
2758                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2759                f.write_fmt(format_args!("</main>"))?;
2760            }
2761            Element::Header => {
2762                f.write_fmt(format_args!(
2763                    "<header{attrs}>",
2764                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2765                ))?;
2766                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2767                f.write_fmt(format_args!("</header>"))?;
2768            }
2769            Element::Footer => {
2770                f.write_fmt(format_args!(
2771                    "<footer{attrs}>",
2772                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2773                ))?;
2774                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2775                f.write_fmt(format_args!("</footer>"))?;
2776            }
2777            Element::Section => {
2778                f.write_fmt(format_args!(
2779                    "<section{attrs}>",
2780                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2781                ))?;
2782                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2783                f.write_fmt(format_args!("</section>"))?;
2784            }
2785            Element::Form => {
2786                f.write_fmt(format_args!(
2787                    "<form{attrs}>",
2788                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2789                ))?;
2790                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2791                f.write_fmt(format_args!("</form>"))?;
2792            }
2793            Element::Span => {
2794                f.write_fmt(format_args!(
2795                    "<span{attrs}>",
2796                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2797                ))?;
2798                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2799                f.write_fmt(format_args!("</span>"))?;
2800            }
2801            Element::Input { input, .. } => {
2802                input.display(f, self.attrs(with_debug_attrs))?;
2803            }
2804            Element::Button => {
2805                f.write_fmt(format_args!(
2806                    "<button{attrs}>",
2807                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2808                ))?;
2809                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2810                f.write_fmt(format_args!("</button>"))?;
2811            }
2812            Element::Image { source, .. } => {
2813                f.write_fmt(format_args!(
2814                    "<img{src_attr}{attrs} />",
2815                    attrs = self.attrs_to_string_pad_left(with_debug_attrs),
2816                    src_attr = Attrs::new()
2817                        .with_attr_opt("src", source.to_owned())
2818                        .to_string_pad_left()
2819                ))?;
2820            }
2821            Element::Anchor { href, .. } => {
2822                f.write_fmt(format_args!(
2823                    "<a{href_attr}{attrs}>",
2824                    attrs = self.attrs_to_string_pad_left(with_debug_attrs),
2825                    href_attr = Attrs::new()
2826                        .with_attr_opt("href", href.to_owned())
2827                        .to_string_pad_left(),
2828                ))?;
2829                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2830                f.write_fmt(format_args!("</a>"))?;
2831            }
2832            Element::Heading { size } => {
2833                f.write_fmt(format_args!(
2834                    "<{size}{attrs}>",
2835                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2836                ))?;
2837                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2838                f.write_fmt(format_args!("</{size}>"))?;
2839            }
2840            Element::UnorderedList => {
2841                f.write_fmt(format_args!(
2842                    "<ul{attrs}>",
2843                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2844                ))?;
2845                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2846                f.write_fmt(format_args!("</ul>"))?;
2847            }
2848            Element::OrderedList => {
2849                f.write_fmt(format_args!(
2850                    "<ol{attrs}>",
2851                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2852                ))?;
2853                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2854                f.write_fmt(format_args!("</ol>"))?;
2855            }
2856            Element::ListItem => {
2857                f.write_fmt(format_args!(
2858                    "<li{attrs}>",
2859                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2860                ))?;
2861                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2862                f.write_fmt(format_args!("</li>"))?;
2863            }
2864            Element::Table => {
2865                f.write_fmt(format_args!(
2866                    "<table{attrs}>",
2867                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2868                ))?;
2869                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2870                f.write_fmt(format_args!("</table>"))?;
2871            }
2872            Element::THead => {
2873                f.write_fmt(format_args!(
2874                    "<thead{attrs}>",
2875                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2876                ))?;
2877                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2878                f.write_fmt(format_args!("</thead>"))?;
2879            }
2880            Element::TH => {
2881                f.write_fmt(format_args!(
2882                    "<th{attrs}>",
2883                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2884                ))?;
2885                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2886                f.write_fmt(format_args!("</th>"))?;
2887            }
2888            Element::TBody => {
2889                f.write_fmt(format_args!(
2890                    "<tbody{attrs}>",
2891                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2892                ))?;
2893                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2894                f.write_fmt(format_args!("</tbody>"))?;
2895            }
2896            Element::TR => {
2897                f.write_fmt(format_args!(
2898                    "<tr{attrs}>",
2899                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2900                ))?;
2901                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2902                f.write_fmt(format_args!("</tr>"))?;
2903            }
2904            Element::TD => {
2905                f.write_fmt(format_args!(
2906                    "<td{attrs}>",
2907                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2908                ))?;
2909                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2910                f.write_fmt(format_args!("</td>"))?;
2911            }
2912            #[cfg(feature = "canvas")]
2913            Element::Canvas => {
2914                f.write_fmt(format_args!(
2915                    "<canvas{attrs}>",
2916                    attrs = self.attrs_to_string_pad_left(with_debug_attrs)
2917                ))?;
2918                display_elements(&self.children, f, with_debug_attrs, wrap_raw_in_element)?;
2919                f.write_fmt(format_args!("</canvas>"))?;
2920            }
2921        }
2922
2923        Ok(())
2924    }
2925
2926    #[cfg_attr(feature = "profiling", profiling::function)]
2927    #[allow(clippy::fn_params_excessive_bools)]
2928    fn display_to_string(
2929        &self,
2930        with_debug_attrs: bool,
2931        wrap_raw_in_element: bool,
2932        #[cfg(feature = "format")] format: bool,
2933        #[cfg(feature = "syntax-highlighting")] highlight: bool,
2934    ) -> Result<String, Box<dyn std::error::Error>> {
2935        let mut data = Vec::new();
2936
2937        let _ = self.display(&mut data, with_debug_attrs, wrap_raw_in_element);
2938
2939        #[cfg(feature = "format")]
2940        let data = if format {
2941            if data[0] == b'<' {
2942                use xml::{reader::ParserConfig, writer::EmitterConfig};
2943                let data: &[u8] = &data;
2944
2945                let reader = ParserConfig::new()
2946                    .trim_whitespace(true)
2947                    .ignore_comments(false)
2948                    .create_reader(data);
2949
2950                let mut dest = Vec::new();
2951
2952                let mut writer = EmitterConfig::new()
2953                    .perform_indent(true)
2954                    .normalize_empty_elements(false)
2955                    .autopad_comments(false)
2956                    .write_document_declaration(false)
2957                    .create_writer(&mut dest);
2958
2959                for event in reader {
2960                    if let Some(event) = event?.as_writer_event() {
2961                        writer.write(event)?;
2962                    }
2963                }
2964
2965                dest
2966            } else {
2967                data
2968            }
2969        } else {
2970            data
2971        };
2972
2973        let xml = String::from_utf8(data)?;
2974
2975        // Remove doctype header thing
2976        let xml = if let Some((_, xml)) = xml.split_once('\n') {
2977            xml.to_string()
2978        } else {
2979            xml
2980        };
2981
2982        #[cfg(feature = "syntax-highlighting")]
2983        if highlight {
2984            use std::sync::LazyLock;
2985
2986            use syntect::highlighting::ThemeSet;
2987            use syntect::parsing::{SyntaxReference, SyntaxSet};
2988
2989            static PS: LazyLock<SyntaxSet> = LazyLock::new(SyntaxSet::load_defaults_newlines);
2990            static TS: LazyLock<ThemeSet> = LazyLock::new(ThemeSet::load_defaults);
2991            static SYNTAX: LazyLock<SyntaxReference> =
2992                LazyLock::new(|| PS.find_syntax_by_extension("xml").unwrap().clone());
2993
2994            let mut h =
2995                syntect::easy::HighlightLines::new(&SYNTAX, &TS.themes["base16-ocean.dark"]);
2996            let highlighted = syntect::util::LinesWithEndings::from(&xml)
2997                .map(|line| {
2998                    let ranges: Vec<(syntect::highlighting::Style, &str)> =
2999                        h.highlight_line(line, &PS).unwrap();
3000                    syntect::util::as_24_bit_terminal_escaped(&ranges[..], false)
3001                })
3002                .collect::<String>();
3003
3004            return Ok(highlighted);
3005        }
3006
3007        Ok(xml)
3008    }
3009}
3010
3011#[cfg(feature = "logic")]
3012const fn override_item_to_attr_name(item: &OverrideItem) -> &'static str {
3013    match item {
3014        OverrideItem::StrId(..) => "id",
3015        OverrideItem::Classes(..) => "class",
3016        OverrideItem::Direction(..) => "sx-dir",
3017        OverrideItem::OverflowX(..) => "sx-overflow-x",
3018        OverrideItem::OverflowY(..) => "sx-overflow-y",
3019        OverrideItem::GridCellSize(..) => "sx-grid-cell-size",
3020        OverrideItem::JustifyContent(..) => "sx-justify-content",
3021        OverrideItem::AlignItems(..) => "sx-align-items",
3022        OverrideItem::TextAlign(..) => "sx-text-align",
3023        OverrideItem::TextDecoration(..) => "sx-text-decoration",
3024        OverrideItem::FontFamily(..) => "sx-font-family",
3025        OverrideItem::Width(..) => "sx-width",
3026        OverrideItem::MinWidth(..) => "sx-min-width",
3027        OverrideItem::MaxWidth(..) => "sx-max-width",
3028        OverrideItem::Height(..) => "sx-height",
3029        OverrideItem::MinHeight(..) => "sx-min-height",
3030        OverrideItem::MaxHeight(..) => "sx-max-height",
3031        OverrideItem::Flex(..) => "sx-flex",
3032        OverrideItem::ColumnGap(..) => "sx-column-gap",
3033        OverrideItem::RowGap(..) => "sx-row-gap",
3034        OverrideItem::Opacity(..) => "sx-opacity",
3035        OverrideItem::Left(..) => "sx-left",
3036        OverrideItem::Right(..) => "sx-right",
3037        OverrideItem::Top(..) => "sx-top",
3038        OverrideItem::Bottom(..) => "sx-bottom",
3039        OverrideItem::TranslateX(..) => "sx-translate-x",
3040        OverrideItem::TranslateY(..) => "sx-translate-y",
3041        OverrideItem::Cursor(..) => "sx-cursor",
3042        OverrideItem::Position(..) => "sx-position",
3043        OverrideItem::Background(..) => "sx-background",
3044        OverrideItem::BorderTop(..) => "sx-border-top",
3045        OverrideItem::BorderRight(..) => "sx-border-right",
3046        OverrideItem::BorderBottom(..) => "sx-border-bottom",
3047        OverrideItem::BorderLeft(..) => "sx-border-left",
3048        OverrideItem::BorderTopLeftRadius(..) => "sx-border-top-left-radius",
3049        OverrideItem::BorderTopRightRadius(..) => "sx-border-top-right-radius",
3050        OverrideItem::BorderBottomLeftRadius(..) => "sx-border-bottom-left-radius",
3051        OverrideItem::BorderBottomRightRadius(..) => "sx-border-bottom-right-radius",
3052        OverrideItem::MarginLeft(..) => "sx-margin-left",
3053        OverrideItem::MarginRight(..) => "sx-margin-right",
3054        OverrideItem::MarginTop(..) => "sx-margin-top",
3055        OverrideItem::MarginBottom(..) => "sx-margin-bottom",
3056        OverrideItem::PaddingLeft(..) => "sx-padding-left",
3057        OverrideItem::PaddingRight(..) => "sx-padding-right",
3058        OverrideItem::PaddingTop(..) => "sx-padding-top",
3059        OverrideItem::PaddingBottom(..) => "sx-padding-bottom",
3060        OverrideItem::FontSize(..) => "sx-font-size",
3061        OverrideItem::Color(..) => "sx-color",
3062        OverrideItem::Hidden(..) => "sx-hidden",
3063        OverrideItem::Visibility(..) => "sx-visibility",
3064    }
3065}
3066
3067#[cfg_attr(feature = "profiling", profiling::all_functions)]
3068impl std::fmt::Display for Container {
3069    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3070        f.write_str(
3071            &self
3072                .display_to_string(
3073                    if cfg!(test) {
3074                        true
3075                    } else {
3076                        std::env::var("DEBUG_ATTRS")
3077                            .is_ok_and(|x| ["1", "true"].contains(&x.to_lowercase().as_str()))
3078                    },
3079                    if cfg!(test) {
3080                        true
3081                    } else {
3082                        std::env::var("DEBUG_RAW_ATTRS")
3083                            .is_ok_and(|x| ["1", "true"].contains(&x.to_lowercase().as_str()))
3084                    },
3085                    #[cfg(feature = "format")]
3086                    true,
3087                    #[cfg(feature = "syntax-highlighting")]
3088                    true,
3089                )
3090                .unwrap_or_else(|e| panic!("Failed to display container: {e:?} ({self:?})")),
3091        )?;
3092
3093        Ok(())
3094    }
3095}
3096
3097fn display_elements(
3098    elements: &[Container],
3099    f: &mut dyn Write,
3100    with_debug_attrs: bool,
3101    wrap_raw_in_element: bool,
3102) -> Result<(), std::io::Error> {
3103    for element in elements {
3104        element.display(f, with_debug_attrs, wrap_raw_in_element)?;
3105    }
3106
3107    Ok(())
3108}
3109
3110impl Element {
3111    #[must_use]
3112    pub const fn allows_children(&self) -> bool {
3113        match self {
3114            Self::Div
3115            | Self::Aside
3116            | Self::Main
3117            | Self::Header
3118            | Self::Footer
3119            | Self::Section
3120            | Self::Form
3121            | Self::Span
3122            | Self::Button
3123            | Self::Anchor { .. }
3124            | Self::Heading { .. }
3125            | Self::UnorderedList
3126            | Self::OrderedList
3127            | Self::ListItem
3128            | Self::Table
3129            | Self::THead
3130            | Self::TH
3131            | Self::TBody
3132            | Self::TR
3133            | Self::TD => true,
3134            Self::Input { .. } | Self::Raw { .. } | Self::Image { .. } => false,
3135            #[cfg(feature = "canvas")]
3136            Self::Canvas => false,
3137        }
3138    }
3139
3140    #[must_use]
3141    pub const fn tag_display_str(&self) -> &'static str {
3142        match self {
3143            Self::Raw { .. } => "Raw",
3144            Self::Div { .. } => "Div",
3145            Self::Aside { .. } => "Aside",
3146            Self::Main { .. } => "Main",
3147            Self::Header { .. } => "Header",
3148            Self::Footer { .. } => "Footer",
3149            Self::Section { .. } => "Section",
3150            Self::Form { .. } => "Form",
3151            Self::Span { .. } => "Span",
3152            Self::Input { .. } => "Input",
3153            Self::Button { .. } => "Button",
3154            Self::Image { .. } => "Image",
3155            Self::Anchor { .. } => "Anchor",
3156            Self::Heading { .. } => "Heading",
3157            Self::UnorderedList { .. } => "UnorderedList",
3158            Self::OrderedList { .. } => "OrderedList",
3159            Self::ListItem { .. } => "ListItem",
3160            Self::Table { .. } => "Table",
3161            Self::THead { .. } => "THead",
3162            Self::TH { .. } => "TH",
3163            Self::TBody { .. } => "TBody",
3164            Self::TR { .. } => "TR",
3165            Self::TD { .. } => "TD",
3166            #[cfg(feature = "canvas")]
3167            Self::Canvas { .. } => "Canvas",
3168        }
3169    }
3170}
3171
3172pub struct TableIter<'a> {
3173    pub headings:
3174        Option<Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'a Container> + 'a>> + 'a>>,
3175    pub rows: Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'a Container> + 'a>> + 'a>,
3176}
3177
3178pub struct TableIterMut<'a> {
3179    pub headings:
3180        Option<Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'a mut Container> + 'a>> + 'a>>,
3181    pub rows: Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'a mut Container> + 'a>> + 'a>,
3182}
3183
3184#[cfg_attr(feature = "profiling", profiling::all_functions)]
3185impl Container {
3186    /// # Panics
3187    ///
3188    /// Will panic if `Element` is not a table
3189    #[must_use]
3190    pub fn table_iter<'a, 'b>(&'a self) -> TableIter<'b>
3191    where
3192        'a: 'b,
3193    {
3194        moosicbox_assert::assert_or_panic!(self.element == Element::Table, "Not a table");
3195
3196        let mut rows_builder: Option<Vec<Box<dyn Iterator<Item = &'b Self>>>> = None;
3197        let mut headings: Option<Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b Self>>>>> =
3198            None;
3199        let mut rows: Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b Self>>> + 'b> =
3200            Box::new(std::iter::empty());
3201
3202        for element in &self.children {
3203            match &element.element {
3204                Element::THead => {
3205                    headings =
3206                        Some(Box::new(element.children.iter().map(|x| {
3207                            Box::new(x.children.iter()) as Box<dyn Iterator<Item = &Self>>
3208                        }))
3209                            as Box<
3210                                dyn Iterator<Item = Box<dyn Iterator<Item = &'b Self>>> + 'b,
3211                            >);
3212                }
3213                Element::TBody => {
3214                    rows =
3215                        Box::new(element.children.iter().map(|x| {
3216                            Box::new(x.children.iter()) as Box<dyn Iterator<Item = &Self>>
3217                        }))
3218                            as Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b Self>>>>;
3219                }
3220                Element::TR => {
3221                    if let Some(builder) = &mut rows_builder {
3222                        builder
3223                            .push(Box::new(element.children.iter())
3224                                as Box<dyn Iterator<Item = &'b Self>>);
3225                    } else {
3226                        rows_builder
3227                            .replace(vec![Box::new(element.children.iter())
3228                                as Box<dyn Iterator<Item = &'b Self>>]);
3229                    }
3230                }
3231                _ => {
3232                    panic!("Invalid table element: {element}");
3233                }
3234            }
3235        }
3236
3237        if let Some(rows_builder) = rows_builder {
3238            rows = Box::new(rows_builder.into_iter());
3239        }
3240
3241        TableIter { headings, rows }
3242    }
3243
3244    /// # Panics
3245    ///
3246    /// Will panic if `Element` is not a table
3247    #[must_use]
3248    pub fn table_iter_mut<'a, 'b>(&'a mut self) -> TableIterMut<'b>
3249    where
3250        'a: 'b,
3251    {
3252        self.table_iter_mut_with_observer(None::<fn(&mut Self)>)
3253    }
3254
3255    /// # Panics
3256    ///
3257    /// Will panic if `Element` is not a table
3258    #[must_use]
3259    pub fn table_iter_mut_with_observer<'a, 'b>(
3260        &'a mut self,
3261        mut observer: Option<impl FnMut(&mut Self)>,
3262    ) -> TableIterMut<'b>
3263    where
3264        'a: 'b,
3265    {
3266        moosicbox_assert::assert_or_panic!(self.element == Element::Table, "Not a table");
3267
3268        let mut rows_builder: Option<Vec<Box<dyn Iterator<Item = &'b mut Self>>>> = None;
3269        let mut headings: Option<
3270            Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b mut Self>>> + 'b>,
3271        > = None;
3272        let mut rows: Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b mut Self>>> + 'b> =
3273            Box::new(std::iter::empty());
3274
3275        for container in &mut self.children {
3276            if let Some(observer) = &mut observer {
3277                match container.element {
3278                    Element::THead | Element::TBody | Element::TR => {
3279                        observer(container);
3280                    }
3281                    _ => {}
3282                }
3283            }
3284            match container.element {
3285                Element::THead => {
3286                    headings = Some(Box::new(container.children.iter_mut().map(|x| {
3287                        Box::new(x.children.iter_mut()) as Box<dyn Iterator<Item = &mut Self>>
3288                    }))
3289                        as Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b mut Self>>> + 'b>);
3290                }
3291                Element::TBody => {
3292                    rows = Box::new(container.children.iter_mut().map(|x| {
3293                        Box::new(x.children.iter_mut()) as Box<dyn Iterator<Item = &mut Self>>
3294                    }))
3295                        as Box<dyn Iterator<Item = Box<dyn Iterator<Item = &'b mut Self>>>>;
3296                }
3297                Element::TR => {
3298                    if let Some(builder) = &mut rows_builder {
3299                        builder.push(Box::new(container.children.iter_mut())
3300                            as Box<dyn Iterator<Item = &'b mut Self>>);
3301                    } else {
3302                        rows_builder.replace(vec![Box::new(container.children.iter_mut())
3303                            as Box<dyn Iterator<Item = &'b mut Self>>]);
3304                    }
3305                }
3306                _ => {
3307                    panic!("Invalid table container: {container}");
3308                }
3309            }
3310        }
3311
3312        if let Some(rows_builder) = rows_builder {
3313            rows = Box::new(rows_builder.into_iter());
3314        }
3315
3316        TableIterMut { headings, rows }
3317    }
3318}
3319
3320#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3321pub enum HeaderSize {
3322    H1,
3323    H2,
3324    H3,
3325    H4,
3326    H5,
3327    H6,
3328}
3329
3330impl std::fmt::Display for HeaderSize {
3331    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3332        match self {
3333            Self::H1 => f.write_str("h1"),
3334            Self::H2 => f.write_str("h2"),
3335            Self::H3 => f.write_str("h3"),
3336            Self::H4 => f.write_str("h4"),
3337            Self::H5 => f.write_str("h5"),
3338            Self::H6 => f.write_str("h6"),
3339        }
3340    }
3341}
3342
3343impl From<HeaderSize> for u8 {
3344    fn from(value: HeaderSize) -> Self {
3345        match value {
3346            HeaderSize::H1 => 1,
3347            HeaderSize::H2 => 2,
3348            HeaderSize::H3 => 3,
3349            HeaderSize::H4 => 4,
3350            HeaderSize::H5 => 5,
3351            HeaderSize::H6 => 6,
3352        }
3353    }
3354}
3355
3356#[derive(Clone, Debug, PartialEq, Eq)]
3357pub enum Input {
3358    Checkbox {
3359        checked: Option<bool>,
3360    },
3361    Text {
3362        value: Option<String>,
3363        placeholder: Option<String>,
3364    },
3365    Password {
3366        value: Option<String>,
3367        placeholder: Option<String>,
3368    },
3369}
3370
3371#[cfg_attr(feature = "profiling", profiling::all_functions)]
3372impl Input {
3373    fn display(&self, f: &mut dyn Write, attrs: Attrs) -> Result<(), std::io::Error> {
3374        match self {
3375            Self::Checkbox { checked } => {
3376                let attrs = attrs.with_attr_opt("checked", checked.map(|x| x.to_string()));
3377                f.write_fmt(format_args!(
3378                    "<input type=\"checkbox\"{attrs} />",
3379                    attrs = attrs.to_string_pad_left(),
3380                ))?;
3381            }
3382            Self::Text { value, placeholder } => {
3383                let attrs = attrs
3384                    .with_attr_opt("value", value.to_owned())
3385                    .with_attr_opt("placeholder", placeholder.to_owned());
3386                f.write_fmt(format_args!(
3387                    "<input type=\"text\"{attrs} />",
3388                    attrs = attrs.to_string_pad_left(),
3389                ))?;
3390            }
3391            Self::Password { value, placeholder } => {
3392                let attrs = attrs
3393                    .with_attr_opt("value", value.to_owned())
3394                    .with_attr_opt("placeholder", placeholder.to_owned());
3395                f.write_fmt(format_args!(
3396                    "<input type=\"password\"{attrs} />",
3397                    attrs = attrs.to_string_pad_left(),
3398                ))?;
3399            }
3400        }
3401
3402        Ok(())
3403    }
3404}
3405
3406#[cfg_attr(feature = "profiling", profiling::all_functions)]
3407impl std::fmt::Display for Input {
3408    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3409        match self {
3410            Self::Checkbox { checked } => {
3411                let attrs = Attrs::new().with_attr_opt("checked", checked.map(|x| x.to_string()));
3412                f.write_fmt(format_args!(
3413                    "<input type=\"checkbox\"{attrs} />",
3414                    attrs = attrs.to_string_pad_left(),
3415                ))
3416            }
3417            Self::Text { value, placeholder } => {
3418                let attrs = Attrs::new()
3419                    .with_attr_opt("value", value.to_owned())
3420                    .with_attr_opt("placeholder", placeholder.to_owned());
3421                f.write_fmt(format_args!(
3422                    "<input type=\"text\"{attrs} />",
3423                    attrs = attrs.to_string_pad_left(),
3424                ))
3425            }
3426            Self::Password { value, placeholder } => {
3427                let attrs = Attrs::new()
3428                    .with_attr_opt("value", value.to_owned())
3429                    .with_attr_opt("placeholder", placeholder.to_owned());
3430                f.write_fmt(format_args!(
3431                    "<input type=\"password\"{attrs} />",
3432                    attrs = attrs.to_string_pad_left(),
3433                ))
3434            }
3435        }
3436    }
3437}