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 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 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 #[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 #[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 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 paths.push(path.to_owned());
1236
1237 let level = path.len(); if levels.len() <= level {
1240 levels.resize(level + 1, Vec::new());
1241 }
1242 levels[level].push(paths.len() - 1);
1243 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 let mut levels: Vec<Vec<usize>> = Vec::new();
1254
1255 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 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 paths.push(path.to_owned());
1275
1276 let level = path.len(); if levels.len() <= level {
1279 levels.resize(level + 1, Vec::new());
1280 }
1281 levels[level].push(paths.len() - 1);
1282 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 let mut levels: Vec<Vec<usize>> = Vec::new();
1294
1295 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 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 paths.push(path.to_owned());
1317
1318 let level = path.len(); if levels.len() <= level {
1321 levels.resize(level + 1, Vec::new());
1322 }
1323 levels[level].push(paths.len() - 1);
1324 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 let mut levels: Vec<Vec<usize>> = Vec::new();
1336
1337 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 for level_nodes in &self.levels {
1362 for &node_idx in level_nodes {
1363 let path = &self.paths[node_idx];
1364
1365 let mut current = root;
1367
1368 for &child_idx in path {
1369 current = ¤t.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 for level_nodes in &self.levels {
1380 for &node_idx in level_nodes {
1381 let path = &self.paths[node_idx];
1382
1383 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 for level_nodes in &self.levels {
1405 for &node_idx in level_nodes {
1406 let path = &self.paths[node_idx];
1407
1408 let mut current = root;
1410 let mut data = initial.clone();
1411
1412 for &child_idx in path {
1413 data = parent(current, data);
1414 current = ¤t.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 for level_nodes in &self.levels {
1436 for &node_idx in level_nodes {
1437 let path = &self.paths[node_idx];
1438
1439 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 for level_nodes in &self.levels {
1467 for &node_idx in level_nodes {
1468 let path = &self.paths[node_idx];
1469
1470 let mut current = root;
1472
1473 for &child_idx in path {
1474 parent(current, data);
1475 current = ¤t.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 for level_nodes in &self.levels {
1497 for &node_idx in level_nodes {
1498 let path = &self.paths[node_idx];
1499
1500 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 for level_nodes in self.levels.iter().rev() {
1520 for &node_idx in level_nodes {
1521 let path = &self.paths[node_idx];
1522
1523 let mut current = root;
1525
1526 for &child_idx in path {
1527 current = ¤t.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 for level_nodes in self.levels.iter().rev() {
1538 for &node_idx in level_nodes {
1539 let path = &self.paths[node_idx];
1540
1541 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 for level_nodes in self.levels.iter().rev() {
1563 for &node_idx in level_nodes {
1564 let path = &self.paths[node_idx];
1565
1566 let mut current = root;
1568 let mut data = initial.clone();
1569
1570 for &child_idx in path {
1571 data = parent(current, data);
1572 current = ¤t.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 for level_nodes in self.levels.iter().rev() {
1594 for &node_idx in level_nodes {
1595 let path = &self.paths[node_idx];
1596
1597 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 for level_nodes in self.levels.iter().rev() {
1627 for &node_idx in level_nodes {
1628 let path = &self.paths[node_idx];
1629
1630 let mut current = root;
1632
1633 for &child_idx in path {
1634 data = parent(current, data);
1635 current = ¤t.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 for level_nodes in self.levels.iter().rev() {
1659 for &node_idx in level_nodes {
1660 let path = &self.paths[node_idx];
1661
1662 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 #[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 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 #[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 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 #[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 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 #[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', " ");
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', " "),
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', " "),
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', " "),
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 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 #[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 #[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 #[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}