avalanche_web/
components.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::fmt::Display;
4use std::rc::Rc;
5
6use wasm_bindgen::JsCast;
7
8use crate::events::*;
9use avalanche::renderer::{HasChildrenMarker, NativeType};
10use avalanche::{Component, Context, View};
11
12/// Represents a text node.
13#[derive(Clone, PartialEq)]
14pub struct Text {
15    pub(crate) text: String,
16    updated: bool,
17    location: (u32, u32),
18    key: Option<String>,
19}
20#[derive(Default)]
21pub struct TextBuilder {
22    text: Option<String>,
23    updated: bool,
24    key: Option<String>,
25}
26
27impl TextBuilder {
28    pub fn new() -> Self {
29        Default::default()
30    }
31
32    pub fn text<T: ToString>(mut self, text: T, updated: bool) -> Self {
33        self.text = Some(text.to_string());
34        self.updated = updated;
35        self
36    }
37
38    pub fn __last<T: ToString>(mut self, text: T, updated: bool) -> Self {
39        self.text = Some(text.to_string());
40        self.updated = updated;
41        self
42    }
43
44    pub fn key<T: ToString>(mut self, key: T, _updated: bool) -> Self {
45        self.key = Some(key.to_string());
46        self
47    }
48
49    pub fn build(self, location: (u32, u32)) -> Text {
50        Text {
51            text: self.text.unwrap(),
52            updated: self.updated,
53            key: self.key,
54            location,
55        }
56    }
57}
58
59impl Component for Text {
60    type Builder = TextBuilder;
61
62    fn render(&self, _: Context) -> View {
63        ().into()
64    }
65    fn native_type(&self) -> Option<NativeType> {
66        let action = NativeType {
67            handler: "avalanche_web_text",
68            name: "",
69        };
70
71        Some(action)
72    }
73
74    fn updated(&self) -> bool {
75        self.updated
76    }
77
78    fn location(&self) -> Option<(u32, u32)> {
79        Some(self.location)
80    }
81
82    fn key(&self) -> Option<&str> {
83        self.key.as_deref()
84    }
85}
86
87pub(crate) enum Attr {
88    Prop(Option<Cow<'static, str>>),
89    Handler(Rc<dyn Fn(Event)>),
90}
91#[derive(Default)]
92#[doc(hidden)]
93pub struct RawElement {
94    /// The `bool` represents whether the attr was updated.
95    pub(crate) attrs: HashMap<&'static str, (Attr, bool)>,
96    pub(crate) attrs_updated: bool,
97    pub(crate) children: Vec<View>,
98    pub(crate) children_updated: bool,
99    pub(crate) value_controlled: bool,
100    pub(crate) checked_controlled: bool,
101    pub(crate) key: Option<String>,
102    pub(crate) location: (u32, u32),
103    pub(crate) tag: &'static str,
104}
105
106impl RawElement {
107    fn attr(&mut self, name: &'static str, attr: Attr, updated: bool) {
108        self.attrs.insert(name, (attr, updated));
109        self.attrs_updated |= updated;
110    }
111
112    fn children(&mut self, children: Vec<View>, updated: bool) {
113        self.children = children;
114        self.children_updated = updated;
115    }
116}
117
118impl Component for RawElement {
119    type Builder = ();
120
121    fn render(&self, _: Context) -> View {
122        HasChildrenMarker {
123            children: self.children.clone(),
124        }
125        .into()
126    }
127
128    fn updated(&self) -> bool {
129        self.attrs_updated || self.children_updated
130    }
131
132    fn native_type(&self) -> Option<NativeType> {
133        Some(NativeType {
134            handler: "avalanche_web",
135            name: self.tag,
136        })
137    }
138
139    fn location(&self) -> Option<(u32, u32)> {
140        Some(self.location)
141    }
142
143    fn key(&self) -> Option<&str> {
144        self.key.as_deref()
145    }
146}
147
148#[derive(Debug, Copy, Clone)]
149pub enum Dir {
150    Ltr,
151    Rtl,
152    Auto,
153}
154
155impl std::fmt::Display for Dir {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        write!(
158            f,
159            "{}",
160            match self {
161                Dir::Ltr => "ltr",
162                Dir::Rtl => "rtl",
163                Dir::Auto => "auto",
164            }
165        )
166    }
167}
168
169#[derive(Debug, Copy, Clone)]
170#[non_exhaustive]
171pub enum Translate {
172    Yes,
173    No,
174}
175
176impl std::fmt::Display for Translate {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        write!(
179            f,
180            "{}",
181            match self {
182                Translate::Yes => "yes",
183                Translate::No => "no",
184            }
185        )
186    }
187}
188
189// TODO: is there a way to avoid making this public?
190/// An internal trait used as a workaround for the fact that inherent types cannot have associated types.
191/// `NativeElement` is used for typing on event handlers.
192#[doc(hidden)]
193pub trait AssociatedNativeElement {
194    type NativeElement: JsCast;
195}
196
197macro_rules! def_component {
198    (
199        $native_tag:expr;
200        $native_element:path;
201        $tag:ident;
202        $tag_builder:ident;
203    ) => {
204        pub struct $tag;
205
206        // Dummy implenentation of Component for $tag
207        // Used only for Builder; all tags create RawElements
208        impl ::avalanche::Component for $tag {
209            type Builder = $tag_builder;
210
211            fn render(&self, _: Context) -> View {
212                unreachable!()
213            }
214
215            fn updated(&self) -> bool {
216                unreachable!()
217            }
218        }
219
220        pub struct $tag_builder {
221            raw: RawElement,
222        }
223
224        impl $tag_builder {
225            pub fn new() -> Self {
226                Default::default()
227            }
228
229            pub fn build(mut self, location: (u32, u32)) -> RawElement {
230                self.raw.location = location;
231                self.raw.tag = $native_tag;
232                self.raw
233            }
234
235            pub fn key<S: ToString>(mut self, key: S, _updated: bool) -> Self {
236                self.raw.key = Some(key.to_string());
237                self
238            }
239
240            pub fn child(mut self, child: View, updated: bool) -> Self {
241                self.raw.children(vec![child], updated);
242                self
243            }
244
245            pub fn children<T: Into<Vec<View>>>(mut self, children: T, updated: bool) -> Self {
246                self.raw.children(children.into(), updated);
247                self
248            }
249
250            pub fn __last<T: Into<Vec<View>>>(mut self, children: T, updated: bool) -> Self {
251                self.raw.children(children.into(), updated);
252                self
253            }
254        }
255
256        impl Default for $tag_builder {
257            fn default() -> Self {
258                Self {
259                    raw: std::default::Default::default(),
260                }
261            }
262        }
263
264        impl AssociatedNativeElement for $tag_builder {
265            type NativeElement = $native_element;
266        }
267
268        add_global_attrs! {$tag_builder}
269    };
270}
271
272macro_rules! def_component_attrs {
273    (
274        $mac:ident;
275        props: $($propnative:expr => $propident:ident : $proptype:ty),*;
276        $(bool_props: $($boolpropnative: expr => $boolpropident:ident),*;)?
277        $(listeners: $($listennative:expr => $listenident:ident : $listentype:ty),*;)?
278    ) => {
279        macro_rules! $mac {
280            ($builder:ty) => {
281                impl $builder {
282                    $(
283                        pub fn $propident<T>(mut self, val: T, updated: bool) -> Self where T : Into<$proptype> {
284                            self.raw.attr(
285                                $propnative,
286                                Attr::Prop(Some(Cow::Owned(Into::<$proptype>::into(val).to_string()))),
287                                updated
288                            );
289                            self
290                        }
291                    )*
292
293                    $(
294                        $(
295                            pub fn $boolpropident(mut self, val: bool, updated: bool) -> Self {
296                                self.raw.attr(
297                                    $boolpropnative,
298                                    Attr::Prop(val.then(|| Cow::Borrowed($boolpropnative))),
299                                    updated
300                                );
301                                self
302                            }
303                        )*
304                    )?
305
306                    $(
307                        $(
308                            pub fn $listenident(mut self, f: impl Fn(TypedEvent::<$listentype, <$builder as AssociatedNativeElement>::NativeElement>) + 'static, updated: bool) -> Self {
309                                self.raw.attr(
310                                    $listennative,
311                                    Attr::Handler(std::rc::Rc::new(move |e: Event| f(
312                                        TypedEvent::<$listentype, <$builder as AssociatedNativeElement>::NativeElement>::new(e.dyn_into::<$listentype>().unwrap())
313                                    ))),
314                                    updated
315                                );
316                                self
317                            }
318                        )*
319                    )?
320                }
321            }
322        }
323    }
324}
325
326def_component_attrs! {
327    add_global_attrs;
328    props:
329        "accesskey" => access_key: String,
330        "class" => class:  String,
331        // TODO: this is enumerable
332        // for forwards-compatability, make enum?
333        "contenteditable" => content_editable: bool,
334        "dir" => dir: Dir,
335        "draggable" => draggable: bool,
336        "id" => id: String,
337        "lang" => lang: String,
338        "placeholder" => placeholder: String,
339        "slot" => slot: String,
340        "spellcheck" => spell_check: bool,
341        "style" => style: String,
342        "tabindex" => tab_index: i16,
343        "title" => title: String,
344        "translate" => translate: Translate;
345    bool_props:
346        "hidden" => hidden;
347    listeners:
348        // Focus events
349        "blur" => on_blur: FocusEvent,
350        "focus" => on_focus: FocusEvent,
351        //focusin, focusout?
352
353        // Clipboard events
354        // TODO: these are unstable web_sys apis
355        // cut, copy, and paste
356
357        // Composition events
358        "compositionstart" => on_composition_start: CompositionEvent,
359        "compositionupdate" => on_composition_update: CompositionEvent,
360        "compositionend" => on_composition_end: CompositionEvent,
361
362        // Form events
363        "change" => on_change: Event,
364        "input" => on_input: Event,
365        // TODO: for form only?
366        "reset" => on_reset: Event,
367        "submit" => on_submit: Event,
368        "invalid" => on_invalid: Event,
369
370        // Image events
371        "load" => on_load: Event,
372        "error" => on_error: Event,
373
374        // Keyboard events
375        "keydown" => on_key_down: KeyboardEvent,
376        "keyup" => on_key_up: KeyboardEvent,
377
378        // Media events
379        "canplay" => on_can_play: Event,
380        "canplaythrough" => on_can_play_through: Event,
381        "durationchange" => on_duration_change: Event,
382        "emptied" => on_emptied: Event,
383        "ended" => on_ended: Event,
384        "loadeddata" => on_loaded_data: Event,
385        "loadedmetadata" => on_loaded_metadata: Event,
386        "pause" => on_pause: Event,
387        "play" => on_play: Event,
388        "playing" => on_playing: Event,
389        "ratechange" => on_rate_change: Event,
390        "seeked" => on_seeked: Event,
391        "seeking" => on_seeking: Event,
392        "stalled" => on_stalled: Event,
393        "suspend" => on_suspend: Event,
394        "timeupdate" => on_time_update: Event,
395        "volumechange" => on_volume_change: Event,
396        "waiting" => on_waiting: Event,
397
398        // Mouse events
399        "auxclick" => on_aux_click: MouseEvent,
400        "click" => on_click: MouseEvent,
401        "contextmenu" => on_context_menu: MouseEvent,
402        "dblclick" => on_double_click: MouseEvent,
403        "mousedown" => on_mouse_down: MouseEvent,
404        "mouseenter" => on_mouse_enter: MouseEvent,
405        "mouseleave" => on_mouse_leave: MouseEvent,
406        "mousemove" => on_mouse_move: MouseEvent,
407        "mouseover" => on_mouse_over: MouseEvent,
408        "mouseout" => on_mouse_out: MouseEvent,
409        "mouseup" => on_mouse_up: MouseEvent,
410        "pointerlockchange" => on_pointer_lock_change: Event,
411        "pointerlockerror" => on_pointer_lock_error: Event,
412        "select" => on_select: Event,
413
414        // Wheel event
415        "wheel" => on_wheel: WheelEvent,
416
417        // Drag and drop events
418        "drag" => on_drag: DragEvent,
419        "dragend" => on_drag_end: DragEvent,
420        "dragenter" => on_drag_enter: DragEvent,
421        "dragstart" => on_drag_start: DragEvent,
422        "dragleave" => on_drag_leave: DragEvent,
423        "dragover" => on_drag_over: DragEvent,
424        "drop" => on_drop: DragEvent,
425
426        // Touch events
427        "touchcancel" => on_touch_cancel: TouchEvent,
428        "touchend" => on_touch_end: TouchEvent,
429        "touchmove" => on_touch_move: TouchEvent,
430        "touchstart" => on_touch_start: TouchEvent,
431
432        // Pointer events
433        "pointerover" => on_pointer_over: PointerEvent,
434        "pointerenter" => on_pointer_enter: PointerEvent,
435        "pointerdown" => on_pointer_down: PointerEvent,
436        "pointermove" => on_pointer_move: PointerEvent,
437        "pointerup" => on_pointer_up: PointerEvent,
438        "pointercancel" => on_pointer_cancel: PointerEvent,
439        "pointerout" => on_pointer_out: PointerEvent,
440        "pointerleave" => on_pointer_leave: PointerEvent,
441        "gotpointercapture" => on_got_pointer_capture: PointerEvent,
442        "lostpointercapture" => on_lost_pointer_capture: PointerEvent,
443
444        // Scroll event
445        "scroll" => on_scroll: Event,
446
447        // Animation events
448        "animationstart" => on_animation_start: AnimationEvent,
449        "animationcancel" => on_animation_cancel: AnimationEvent,
450        "animationend" => on_animation_end: AnimationEvent,
451        "animationinteraction" => on_animation_interaction: AnimationEvent,
452
453        // Transition events
454        "transitionstart" => on_transition_start: TransitionEvent,
455        "transitioncancel" => on_transition_cancel: TransitionEvent,
456        "transitionend" => on_transition_end: TransitionEvent,
457        "transitionrun" => on_transition_run: TransitionEvent,
458
459        // Progress events
460        "abort" => on_abort: Event,
461        "loadstart" => on_load_start: ProgressEvent,
462        "progress" => on_progress: ProgressEvent;
463}
464
465def_component_attrs! {
466    add_cite_attr;
467    props:
468        "cite" => cite: String;
469}
470
471def_component_attrs! {
472    add_datetime_attr;
473    props:
474        "datetime" => date_time: String;
475}
476
477def_component_attrs! {
478    add_string_value_attr;
479    props:
480        "value" => value: String;
481}
482
483def_component_attrs! {
484    add_name_attr;
485    props:
486        "name" => name: String;
487}
488
489def_component! {
490    "div";
491    web_sys::HtmlDivElement;
492    Div;
493    DivBuilder;
494}
495
496def_component! {
497    "h1";
498    web_sys::HtmlHeadingElement;
499    H1;
500    H1Builder;
501}
502
503def_component! {
504    "h2";
505    web_sys::HtmlHeadingElement;
506    H2;
507    H2Builder;
508}
509
510def_component! {
511    "h3";
512    web_sys::HtmlHeadingElement;
513    H3;
514    H3Builder;
515}
516
517def_component! {
518    "h4";
519    web_sys::HtmlHeadingElement;
520    H4;
521    H4Builder;
522}
523
524def_component! {
525    "h5";
526    web_sys::HtmlHeadingElement;
527    H5;
528    H5Builder;
529}
530
531def_component! {
532    "h6";
533    web_sys::HtmlHeadingElement;
534    H6;
535    H6Builder;
536}
537
538// TODO: should meta-type tags be implemented?
539
540def_component! {
541    "body";
542    web_sys::HtmlBodyElement;
543    Body;
544    BodyBuilder;
545}
546
547def_component! {
548    "address";
549    web_sys::HtmlSpanElement;
550    Address;
551    AddressBuilder;
552}
553
554def_component! {
555    "article";
556    web_sys::HtmlElement;
557    Article;
558    ArticleBuilder;
559}
560
561def_component! {
562    "aside";
563    web_sys::HtmlElement;
564    Aside;
565    AsideBuilder;
566}
567
568def_component! {
569    "footer";
570    web_sys::HtmlElement;
571    Footer;
572    FooterBuilder;
573}
574
575def_component! {
576    "header";
577    web_sys::HtmlElement;
578    Header;
579    HeaderBuilder;
580}
581
582def_component! {
583    "hgroup";
584    web_sys::HtmlElement;
585    HGroup;
586    HGroupBuilder;
587}
588
589def_component! {
590    "main";
591    web_sys::HtmlElement;
592    Main;
593    MainBuilder;
594}
595
596def_component! {
597    "nav";
598    web_sys::HtmlElement;
599    Nav;
600    NavBuilder;
601}
602
603def_component! {
604    "section";
605    web_sys::HtmlElement;
606    Section;
607    SectionBuilder;
608}
609
610def_component! {
611    "blockquote";
612    web_sys::HtmlQuoteElement;
613    BlockQuote;
614    BlockQuoteBuilder;
615}
616
617add_cite_attr! {BlockQuoteBuilder}
618
619def_component! {
620    "dd";
621    web_sys::HtmlElement;
622    Dd;
623    DdBuilder;
624}
625
626def_component! {
627    "dl";
628    web_sys::HtmlElement;
629    Dl;
630    DlBuilder;
631}
632
633def_component! {
634    "dt";
635    web_sys::HtmlElement;
636    Dt;
637    DtBuilder;
638}
639
640def_component! {
641    "figcaption";
642    web_sys::HtmlElement;
643    FigCaption;
644    FigCaptionBuilder;
645}
646
647def_component! {
648    "figure";
649    web_sys::HtmlElement;
650    Figure;
651    FigureBuilder;
652}
653
654def_component! {
655    "hr";
656    web_sys::HtmlHrElement;
657    Hr;
658    HrBuilder;
659}
660
661def_component! {
662    "li";
663    web_sys::HtmlLiElement;
664    Li;
665    LiBuilder;
666}
667
668def_component_attrs! {
669    add_li_attrs;
670    props:
671        "value" => value: u32;
672}
673add_li_attrs! {LiBuilder}
674
675def_component! {
676    "ol";
677    web_sys::HtmlOListElement;
678    Ol;
679    OlBuilder;
680}
681
682def_component_attrs! {
683    add_ol_attrs;
684    props:
685        "start" => start: i32,
686        "type" => type_: String;
687    bool_props:
688        "reversed" => reversed;
689}
690add_ol_attrs! {OlBuilder}
691
692def_component! {
693    "p";
694    web_sys::HtmlParagraphElement;
695    P;
696    PBuilder;
697}
698
699def_component! {
700    "pre";
701    web_sys::HtmlPreElement;
702    Pre;
703    PreBuilder;
704}
705
706def_component! {
707    "ul";
708    web_sys::HtmlUListElement;
709    Ul;
710    UlBuilder;
711}
712
713def_component! {
714    "a";
715    web_sys::HtmlAnchorElement;
716    A;
717    ABuilder;
718}
719
720def_component_attrs! {
721    add_a_attrs;
722    props:
723        "download" => download: String,
724        "href" => href: String,
725        "hreflanf" => href_lang: String,
726        "ping" => ping: String,
727        "referrerpolicy" => referrer_policy: String,
728        "rel" => rel: String,
729        "target" => target: String,
730        "type" => type_: String;
731}
732add_a_attrs! {ABuilder}
733
734def_component! {
735    "abbr";
736    web_sys::HtmlElement;
737    Abbr;
738    AbbrBuilder;
739}
740
741def_component! {
742    "b";
743    web_sys::HtmlElement;
744    B;
745    BBuilder;
746}
747
748def_component! {
749    "bdi";
750    web_sys::HtmlElement;
751    Bdi;
752    BdiBuilder;
753}
754
755def_component! {
756    "bdo";
757    web_sys::HtmlElement;
758    Bdo;
759    BdoBuilder;
760}
761
762def_component! {
763    "br";
764    web_sys::HtmlBrElement;
765    Br;
766    BrBuilder;
767}
768
769def_component! {
770    "cite";
771    web_sys::HtmlSpanElement;
772    Cite;
773    CiteBuilder;
774}
775
776def_component! {
777    "code";
778    web_sys::HtmlSpanElement;
779    Code;
780    CodeBuilder;
781}
782
783def_component! {
784    "data";
785    web_sys::HtmlDataElement;
786    Data;
787    DataBuilder;
788}
789add_string_value_attr! {DataBuilder}
790
791def_component! {
792    "dfn";
793    web_sys::HtmlElement;
794    Dfn;
795    DfnBuilder;
796}
797
798def_component! {
799    "em";
800    web_sys::HtmlSpanElement;
801    Em;
802    EmBuilder;
803}
804
805def_component! {
806    "i";
807    web_sys::HtmlElement;
808    I;
809    IBuilder;
810}
811
812def_component! {
813    "kbd";
814    web_sys::HtmlElement;
815    Kbd;
816    KbdBuilder;
817}
818
819def_component! {
820    "mark";
821    web_sys::HtmlElement;
822    Mark;
823    MarkBuilder;
824}
825
826def_component! {
827    "q";
828    web_sys::HtmlQuoteElement;
829    Q;
830    QBuilder;
831}
832add_cite_attr! {QBuilder}
833
834def_component! {
835    "rp";
836    web_sys::HtmlElement;
837    Rp;
838    RpBuilder;
839}
840
841def_component! {
842    "rt";
843    web_sys::HtmlElement;
844    Rt;
845    RtBuilder;
846}
847
848def_component! {
849    "rtc";
850    web_sys::HtmlElement;
851    Rtc;
852    RtcBuilder;
853}
854
855def_component! {
856    "ruby";
857    web_sys::HtmlElement;
858    Ruby;
859    RubyBuilder;
860}
861
862def_component! {
863    "s";
864    web_sys::HtmlElement;
865    S;
866    SBuilder;
867}
868
869def_component! {
870    "samp";
871    web_sys::HtmlElement;
872    Samp;
873    SampBuilder;
874}
875
876def_component! {
877    "small";
878    web_sys::HtmlElement;
879    Small;
880    SmallBuilder;
881}
882
883def_component! {
884    "span";
885    web_sys::HtmlSpanElement;
886    Span;
887    SpanBuilder;
888}
889
890def_component! {
891    "strong";
892    web_sys::HtmlElement;
893    Strong;
894    StrongBuilder;
895}
896
897def_component! {
898    "sub";
899    web_sys::HtmlElement;
900    Sub;
901    SubBuilder;
902}
903
904def_component! {
905    "sup";
906    web_sys::HtmlElement;
907    Sup;
908    SupBuilder;
909}
910
911def_component! {
912    "time";
913    web_sys::HtmlTimeElement;
914    Time;
915    TimeBuilder;
916}
917
918add_datetime_attr! {TimeBuilder}
919
920def_component! {
921    "u";
922    web_sys::HtmlElement;
923    U;
924    UBuilder;
925}
926
927def_component! {
928    "var";
929    web_sys::HtmlElement;
930    Var;
931    VarBuilder;
932}
933
934def_component! {
935    "wbr";
936    web_sys::HtmlElement;
937    Wbr;
938    WbrBuilder;
939}
940
941def_component! {
942    "area";
943    web_sys::HtmlAreaElement;
944    Area;
945    AreaBuilder;
946}
947add_a_attrs! {AreaBuilder}
948
949def_component_attrs! {
950    add_area_attrs;
951    props:
952        "coords" => coords: String,
953        "shape" => shape: String;
954}
955add_area_attrs! {AreaBuilder}
956
957pub enum CrossOrigin {
958    Anonymous,
959    UseCredentials,
960}
961
962impl Display for CrossOrigin {
963    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
964        write!(
965            f,
966            "{}",
967            match self {
968                CrossOrigin::Anonymous => "anonymous",
969                CrossOrigin::UseCredentials => "use-credentials",
970            }
971        )
972    }
973}
974
975pub enum Preload {
976    None,
977    Metadata,
978    Auto,
979}
980
981impl Display for Preload {
982    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
983        write!(
984            f,
985            "{}",
986            match self {
987                Preload::None => "none",
988                Preload::Metadata => "metadata",
989                Preload::Auto => "auto",
990            }
991        )
992    }
993}
994
995def_component_attrs! {
996    add_media_attrs;
997    props:
998        "crossorigin" => cross_origin: CrossOrigin,
999        "preload" => preload: Preload,
1000        "src" => src: String;
1001    bool_props:
1002        "autoplay" => autoplay,
1003        "controls" => controls,
1004        "disableRemotePlayback" => disable_remote_playback,
1005        "loop" => loop_,
1006        "muted" => muted;
1007}
1008
1009def_component! {
1010    "audio";
1011    web_sys::HtmlAudioElement;
1012    Audio;
1013    AudioBuilder;
1014}
1015add_media_attrs! {AudioBuilder}
1016
1017def_component_attrs! {
1018    add_width_height_attrs;
1019    props:
1020        "width" => width: f64,
1021        "height" => height: f64;
1022}
1023
1024def_component! {
1025    "video";
1026    web_sys::HtmlVideoElement;
1027    Video;
1028    VideoBuilder;
1029}
1030add_media_attrs! {VideoBuilder}
1031add_width_height_attrs! {VideoBuilder}
1032
1033def_component_attrs! {
1034    add_video_attrs;
1035    props:
1036        "poster" => poster: String;
1037    bool_props:
1038        "autoPictureInPicture" => auto_picture_in_picture,
1039        "disablePictureInPicture" => disable_picture_in_picture,
1040        "playsinline" => plays_inline;
1041}
1042add_video_attrs! {VideoBuilder}
1043
1044def_component! {
1045    "img";
1046    web_sys::HtmlImageElement;
1047    Img;
1048    ImgBuilder;
1049}
1050add_width_height_attrs! {ImgBuilder}
1051
1052pub enum Decoding {
1053    Sync,
1054    Async,
1055    Auto,
1056}
1057
1058impl Display for Decoding {
1059    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1060        write!(
1061            f,
1062            "{}",
1063            match self {
1064                Decoding::Sync => "sync",
1065                Decoding::Async => "async",
1066                Decoding::Auto => "auto",
1067            }
1068        )
1069    }
1070}
1071
1072pub enum Loading {
1073    Eager,
1074    Lazy,
1075}
1076
1077impl Display for Loading {
1078    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1079        write!(
1080            f,
1081            "{}",
1082            match self {
1083                Loading::Eager => "eager",
1084                Loading::Lazy => "lazy",
1085            }
1086        )
1087    }
1088}
1089
1090def_component_attrs! {
1091    add_img_attrs;
1092    props:
1093        "alt" => alt: String,
1094        "crossorigin" => cross_origin: CrossOrigin,
1095        "decoding" => decoding: Decoding,
1096        "loading" => loading: Loading,
1097        "referrerpolicy" => referrer_policy: String,
1098        "sizes" => sizes: String,
1099        "src" => src: String,
1100        "srcset" => src_set: String,
1101        "usemap" => use_map: String;
1102    bool_props:
1103        "ismap" => is_map;
1104}
1105add_img_attrs! {ImgBuilder}
1106
1107def_component! {
1108    "map";
1109    web_sys::HtmlMapElement;
1110    Map;
1111    MapBuilder;
1112}
1113add_name_attr! {MapBuilder}
1114
1115def_component! {
1116    "track";
1117    web_sys::HtmlTrackElement;
1118    Track;
1119    TrackBuilder;
1120}
1121
1122pub enum TrackKind {
1123    Subtitles,
1124    Captions,
1125    Descriptions,
1126    Chapters,
1127    Metadata,
1128}
1129
1130impl Display for TrackKind {
1131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1132        write!(
1133            f,
1134            "{}",
1135            match self {
1136                TrackKind::Subtitles => "subtitles",
1137                TrackKind::Captions => "captions",
1138                TrackKind::Descriptions => "descriptions",
1139                TrackKind::Chapters => "chapters",
1140                TrackKind::Metadata => "metadata",
1141            }
1142        )
1143    }
1144}
1145
1146def_component_attrs! {
1147    add_track_attrs;
1148    props:
1149        "kind" => kind: TrackKind,
1150        "label" => label: String,
1151        "src" => src: String,
1152        "srclang" => src_lang: String;
1153    bool_props:
1154        "default" => default;
1155}
1156add_track_attrs! {TrackBuilder}
1157
1158def_component_attrs! {
1159    add_base_img_attrs;
1160    props:
1161        "alt" => alt: String,
1162        "height" => height: f64,
1163        "src" => src: String,
1164        "width" => width: f64;
1165}
1166
1167def_component! {
1168    "embed";
1169    web_sys::HtmlEmbedElement;
1170    Embed;
1171    EmbedBuilder;
1172}
1173add_width_height_attrs! {EmbedBuilder}
1174
1175def_component_attrs! {
1176    add_embed_attrs;
1177    props:
1178        "src" => src: String,
1179        "type" => type_: String;
1180}
1181add_embed_attrs! {EmbedBuilder}
1182
1183def_component! {
1184    "iframe";
1185    web_sys::HtmlIFrameElement;
1186    IFrame;
1187    IFrameBuilder;
1188}
1189add_width_height_attrs! {IFrameBuilder}
1190
1191def_component_attrs! {
1192    add_iframe_attrs;
1193    props:
1194        "allow" => allow: String,
1195        "csp" => csp: String,
1196        "loading" => loading: Loading,
1197        "name" => name: String,
1198        "referrerpolicy" => referrer_policy: String,
1199        "sandbox" => sandbox: String,
1200        "src" => src: String,
1201        "srcdoc" => src_doc: String;
1202    bool_props:
1203        "allowfullscreen" => allow_full_screen,
1204        "allowpaymentrequest" => allow_payment_request;
1205}
1206add_iframe_attrs! {IFrameBuilder}
1207
1208def_component! {
1209    "object";
1210    web_sys::HtmlObjectElement;
1211    Object;
1212    ObjectBuilder;
1213}
1214add_width_height_attrs! {ObjectBuilder}
1215add_name_attr! {ObjectBuilder}
1216
1217def_component_attrs! {
1218    add_object_attrs;
1219    props:
1220        "data" => data: String,
1221        "form" => form: String,
1222        "type" => type_: String,
1223        "usemap" => use_map: String;
1224    bool_props:
1225        "typemustmatch" => type_must_match;
1226}
1227add_object_attrs! {ObjectBuilder}
1228
1229def_component! {
1230    "param";
1231    web_sys::HtmlParamElement;
1232    Param;
1233    ParamBuilder;
1234}
1235add_string_value_attr! {ParamBuilder}
1236add_name_attr! {ParamBuilder}
1237
1238def_component! {
1239    "picture";
1240    web_sys::HtmlPictureElement;
1241    Picture;
1242    PictureBuilder;
1243}
1244
1245def_component! {
1246    "ins";
1247    web_sys::HtmlModElement;
1248    Ins;
1249    InsBuilder;
1250}
1251add_cite_attr! {InsBuilder}
1252add_datetime_attr! {InsBuilder}
1253
1254def_component! {
1255    "del";
1256    web_sys::HtmlModElement;
1257    Del;
1258    DelBuilder;
1259}
1260add_cite_attr! {DelBuilder}
1261add_datetime_attr! {DelBuilder}
1262
1263def_component! {
1264   "caption";
1265   web_sys::HtmlTableCaptionElement;
1266   Caption;
1267   CaptionBuilder;
1268}
1269
1270def_component! {
1271    "col";
1272    web_sys::HtmlTableColElement;
1273    Col;
1274    ColBuilder;
1275}
1276
1277def_component_attrs! {
1278    add_col_attrs;
1279    props:
1280        "span" => span: u32;
1281}
1282add_col_attrs! {ColBuilder}
1283
1284// same as above
1285def_component! {
1286    "colgroup";
1287    web_sys::HtmlTableColElement;
1288    ColGroup;
1289    ColGroupBuilder;
1290}
1291
1292def_component! {
1293    "table";
1294    web_sys::HtmlTableElement;
1295    Table;
1296    TableBuilder;
1297}
1298
1299def_component! {
1300    "tbody";
1301    web_sys::HtmlTableSectionElement;
1302    TBody;
1303    TBodyBuilder;
1304}
1305
1306def_component! {
1307    "td";
1308    web_sys::HtmlTableCellElement;
1309    Td;
1310    TdBuilder;
1311}
1312
1313def_component_attrs! {
1314    add_td_th_attrs;
1315    props:
1316        "colspan" => col_span: u32,
1317        "rowspan" => row_span: u32,
1318        "headers" => headers: String;
1319}
1320add_td_th_attrs! {TdBuilder}
1321
1322def_component! {
1323    "tfoot";
1324    web_sys::HtmlTableSectionElement;
1325    TFoot;
1326    TFootBuilder;
1327}
1328
1329// TODO: attrs
1330def_component! {
1331    "th";
1332    web_sys::HtmlTableCellElement;
1333    Th;
1334    ThBuilder;
1335}
1336add_td_th_attrs! {ThBuilder}
1337
1338pub enum Scope {
1339    Row,
1340    Col,
1341    RowGroup,
1342    ColGroup,
1343    Auto,
1344}
1345
1346impl Display for Scope {
1347    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1348        write!(
1349            f,
1350            "{}",
1351            match self {
1352                Scope::Row => "row",
1353                Scope::Col => "col",
1354                Scope::RowGroup => "rowgroup",
1355                Scope::ColGroup => "colgroup",
1356                Scope::Auto => "auto",
1357            }
1358        )
1359    }
1360}
1361
1362def_component_attrs! {
1363    add_th_attrs;
1364    props:
1365        "abbr" => abbr: String,
1366        "scope" => scope: Scope;
1367}
1368add_th_attrs! {ThBuilder}
1369
1370def_component! {
1371    "thead";
1372    web_sys::HtmlTableSectionElement;
1373    THead;
1374    THeadBuilder;
1375}
1376
1377def_component! {
1378    "tr";
1379    web_sys::HtmlTableRowElement;
1380    Tr;
1381    TrBuilder;
1382}
1383
1384def_component_attrs! {
1385    add_form_field_attrs;
1386    props:
1387        "form" => form: String,
1388        "name" => name: String;
1389    bool_props:
1390        "autofocus" => auto_focus,
1391        "disabled" => disabled;
1392}
1393
1394def_component_attrs! {
1395    add_form_submit_attrs;
1396    props:
1397        "formaction" => form_ation: String,
1398        "formenctype" => form_enc_type: String,
1399        "formmethod" => form_method: String,
1400        "formtarget" => form_target: String;
1401    bool_props:
1402        "formnovalidate" => form_no_validate;
1403}
1404
1405def_component_attrs! {
1406    add_type_attr;
1407    props:
1408        "type" => type_: String;
1409}
1410
1411def_component! {
1412    "button";
1413    web_sys::HtmlButtonElement;
1414    Button;
1415    ButtonBuilder;
1416}
1417add_type_attr! {ButtonBuilder}
1418add_form_field_attrs! {ButtonBuilder}
1419add_form_submit_attrs! {ButtonBuilder}
1420add_string_value_attr! {ButtonBuilder}
1421
1422def_component! {
1423    "datalist";
1424    web_sys::HtmlDataListElement;
1425    DataList;
1426    DataListBuilder;
1427}
1428
1429def_component! {
1430    "fieldset";
1431    web_sys::HtmlFieldSetElement;
1432    FieldSet;
1433    FieldSetBuilder;
1434}
1435
1436def_component_attrs! {
1437    add_field_set_attrs;
1438    props:
1439        "form" => form: String;
1440    bool_props:
1441        "disabled" => disabled;
1442}
1443add_field_set_attrs! {FieldSetBuilder}
1444add_name_attr! {FieldSetBuilder}
1445
1446def_component! {
1447    "form";
1448    web_sys::HtmlFormElement;
1449    Form;
1450    FormBuilder;
1451}
1452add_name_attr! {FormBuilder}
1453
1454def_component_attrs! {
1455    add_form_attrs;
1456    props:
1457        "accept-charset" => accept_charset: String,
1458        "autocomplete" => auto_complete: String,
1459        "rel" => rel: String,
1460        "action" => action: String,
1461        "enctype" => enc_type: String,
1462        "method" => method: String,
1463        "target" => target: String;
1464    bool_props:
1465        "novalidate" => no_validate;
1466}
1467add_form_attrs! {FormBuilder}
1468
1469def_component_attrs! {
1470    add_textinput_attrs;
1471    props:
1472        "autocomplete" => auto_complete: String,
1473        "maxlength" => max_length: u32,
1474        "minlength" => min_length: u32;
1475    bool_props:
1476        "readonly" => read_only,
1477        "required" => required;
1478}
1479
1480def_component! {
1481    "input";
1482    web_sys::HtmlInputElement;
1483    Input;
1484    InputBuilder;
1485}
1486add_form_field_attrs! {InputBuilder}
1487add_textinput_attrs! {InputBuilder}
1488add_form_submit_attrs! {InputBuilder}
1489add_base_img_attrs! {InputBuilder}
1490add_type_attr! {InputBuilder}
1491
1492def_component_attrs! {
1493    add_input_attrs;
1494    props:
1495        "capture" => capture: String,
1496        "dirname" => dir_name: String,
1497        "inputmode" => input_mode: String,
1498        "list" => list: String,
1499        "min" => min: String,
1500        "max" => max: String;
1501    bool_props:
1502        "multiple" => multiple;
1503}
1504add_input_attrs! {InputBuilder}
1505
1506impl InputBuilder {
1507    pub fn value<S: Into<String>>(mut self, val: S, updated: bool) -> Self {
1508        self.raw.value_controlled = true;
1509        self.raw
1510            .attr("value", Attr::Prop(Some(Cow::Owned(val.into()))), updated);
1511        self
1512    }
1513
1514    pub fn checked(mut self, val: bool, updated: bool) -> Self {
1515        self.raw.checked_controlled = true;
1516        self.raw.attr(
1517            "checked",
1518            Attr::Prop(val.then(|| Cow::Borrowed("checked"))),
1519            updated,
1520        );
1521        self
1522    }
1523}
1524
1525def_component! {
1526    "textarea";
1527    web_sys::HtmlTextAreaElement;
1528    TextArea;
1529    TextAreaBuilder;
1530}
1531add_form_field_attrs! {TextAreaBuilder}
1532add_textinput_attrs! {TextAreaBuilder}
1533
1534impl TextAreaBuilder {
1535    pub fn value<S: ToString>(mut self, val: S, updated: bool) -> Self {
1536        self.raw.value_controlled = true;
1537        self.raw
1538            .attr("value", Attr::Prop(Some(Cow::Owned(val.to_string()))), updated);
1539        self
1540    }
1541}
1542
1543pub enum Wrap {
1544    Soft,
1545    Hard,
1546    Off,
1547}
1548
1549impl Display for Wrap {
1550    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1551        write!(
1552            f,
1553            "{}",
1554            match self {
1555                Wrap::Soft => "soft",
1556                Wrap::Hard => "hard",
1557                Wrap::Off => "off",
1558            }
1559        )
1560    }
1561}
1562
1563def_component_attrs! {
1564    add_textarea_attrs;
1565    props:
1566        "cols" => cols: u32,
1567        "rows" => rows: u32,
1568        "wrap" => wrap: Wrap;
1569}
1570add_textarea_attrs! {TextAreaBuilder}
1571
1572def_component_attrs! {
1573    add_label_attrs;
1574    props:
1575        "for" => for_: String;
1576}
1577
1578def_component! {
1579    "label";
1580    web_sys::HtmlLabelElement;
1581    Label;
1582    LabelBuilder;
1583}
1584add_label_attrs! {LabelBuilder}
1585
1586def_component! {
1587    "legend";
1588    web_sys::HtmlLegendElement;
1589    Legend;
1590    LegendBuilder;
1591}
1592
1593def_component_attrs! {
1594    add_meter_attrs;
1595    props:
1596        "min" => min: f64,
1597        "max" => max: f64,
1598        "low" => low: f64,
1599        "high" => high: f64,
1600        "optimum" => optimum: f64,
1601        "form" => form: String,
1602        "value" => value: String;
1603}
1604
1605def_component! {
1606    "meter";
1607    web_sys::HtmlMeterElement;
1608    Meter;
1609    MeterBuilder;
1610}
1611add_meter_attrs! {MeterBuilder}
1612
1613def_component! {
1614    "optgroup";
1615    web_sys::HtmlOptGroupElement;
1616    OptGroup;
1617    OptGroupBuilder;
1618}
1619
1620def_component_attrs! {
1621    add_label_value_attrs;
1622    props:
1623        "value" => value: String,
1624        "label" => label: String;
1625}
1626add_label_value_attrs! {OptGroupBuilder}
1627
1628// TODO: doc alias for Option
1629def_component! {
1630    "option";
1631    web_sys::HtmlOptionElement;
1632    Opt;
1633    OptBuilder;
1634}
1635add_label_value_attrs! {OptBuilder}
1636
1637def_component_attrs! {
1638    add_opt_attrs;
1639    props:
1640        ;
1641    bool_props:
1642        "disabled" => disabled,
1643        "selected" => selected;
1644}
1645add_opt_attrs! {OptBuilder}
1646
1647def_component! {
1648    "output";
1649    web_sys::HtmlOutputElement;
1650    Output;
1651    OutputBuilder;
1652}
1653
1654def_component_attrs! {
1655    add_output_attrs;
1656    props:
1657        "for" => for_: String,
1658        "form" => form: String,
1659        "name" => name: String;
1660}
1661add_output_attrs! {OutputBuilder}
1662
1663def_component! {
1664    "progress";
1665    web_sys::HtmlProgressElement;
1666    Progress;
1667    ProgressBuilder;
1668}
1669
1670def_component_attrs! {
1671    add_progress_attrs;
1672    props:
1673        "max" => max: f64,
1674        "value" => value: f64;
1675}
1676
1677add_progress_attrs! {ProgressBuilder}
1678
1679// TODO: add controlled functionality
1680def_component! {
1681    "select";
1682    web_sys::HtmlSelectElement;
1683    Select;
1684    SelectBuilder;
1685}
1686
1687add_form_field_attrs! {SelectBuilder}
1688
1689def_component_attrs! {
1690    add_select_attrs;
1691    props:
1692        "autocomplete" => auto_complete: String,
1693        "size" => size: u32;
1694    bool_props:
1695        "multiple" => multiple,
1696        "required" => required;
1697}
1698add_select_attrs! {SelectBuilder}
1699
1700def_component_attrs! {
1701    add_open_attr;
1702    props:
1703        ;
1704    bool_props:
1705        "open" => open;
1706}
1707
1708def_component! {
1709    "details";
1710    web_sys::HtmlDetailsElement;
1711    Details;
1712    DetailsBuilder;
1713}
1714add_open_attr! {DetailsBuilder}
1715
1716def_component! {
1717    "dialog";
1718    web_sys::HtmlDialogElement;
1719    Dialog;
1720    DialogBuilder;
1721}
1722add_open_attr! {DialogBuilder}
1723
1724def_component! {
1725    "summary";
1726    web_sys::HtmlElement;
1727    Summary;
1728    SummaryBuilder;
1729}