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