maomi_dom/
base_element.rs

1//! Basic types for DOM elements.
2
3use maomi::{
4    backend::tree::*,
5    locale_string::LocaleString,
6    prop::{BindingValue, PropertyUpdate},
7};
8use std::{
9    borrow::Borrow,
10    cell::RefCell,
11    mem::{ManuallyDrop, MaybeUninit},
12    num::NonZeroUsize,
13    ops::Deref,
14    rc::Rc,
15};
16use wasm_bindgen::{prelude::*, JsCast};
17
18use crate::{
19    event::{ColdEventItem, ColdEventList, HotEventList},
20    DomGeneralElement, DomState, WriteHtmlState,
21};
22
23#[wasm_bindgen]
24extern "C" {
25    type MaomiDomElement;
26    #[wasm_bindgen(method, getter)]
27    fn maomi(this: &MaomiDomElement) -> Option<usize>;
28    #[wasm_bindgen(method, setter)]
29    fn set_maomi(this: &MaomiDomElement, ptr: usize);
30}
31
32#[cfg(feature = "prerendering")]
33#[derive(Debug, Clone)]
34pub(crate) struct PrerenderingElement {
35    tag_name: &'static str,
36    classes: Vec<&'static str>,
37    styles: Vec<(&'static str, String)>,
38    attrs: Vec<(&'static str, String)>,
39}
40
41#[cfg(feature = "prerendering")]
42impl PrerenderingElement {
43    pub(crate) fn new(tag_name: &'static str) -> Self {
44        Self {
45            tag_name,
46            classes: vec![],
47            styles: vec![],
48            attrs: vec![],
49        }
50    }
51
52    pub(crate) fn set_attribute(&mut self, name: &'static str, value: String) {
53        if let Some((_, v)) = self.attrs.iter_mut().find(|(n, _)| *n == name) {
54            *v = value;
55        } else {
56            self.attrs.push((name, value));
57        }
58    }
59
60    pub(crate) fn remove_attribute(&mut self, name: &'static str) {
61        if let Some(index) = self.attrs.iter_mut().position(|(n, _)| *n == name) {
62            self.attrs.swap_remove(index);
63        }
64    }
65
66    pub(crate) fn add_class(&mut self, class_name: &'static str) {
67        if !self.classes.contains(&class_name) {
68            self.classes.push(class_name);
69        }
70    }
71
72    pub(crate) fn remove_class(&mut self, class_name: &'static str) {
73        if let Some(index) = self.classes.iter().position(|x| *x == class_name) {
74            self.classes.swap_remove(index);
75        }
76    }
77
78    pub(crate) fn set_style(&mut self, style_name: &'static str, value: &str) {
79        let need_push = self
80            .styles
81            .iter_mut()
82            .find_map(|(n, v)| {
83                if *n == style_name {
84                    *v = value.to_string();
85                    return Some(());
86                }
87                None
88            })
89            .is_none();
90        if need_push {
91            self.styles.push((style_name, value.to_string()));
92        }
93    }
94
95    #[cfg(feature = "prerendering")]
96    pub(crate) fn write_children_html(
97        &self,
98        w: &mut impl std::io::Write,
99        this: &ForestNode<DomGeneralElement>,
100        state: &mut WriteHtmlState,
101    ) -> std::io::Result<()> {
102        let mut cur = this.first_child();
103        while let Some(c) = &cur {
104            DomGeneralElement::write_outer_html(&c, w, state)?;
105            cur = c.next_sibling();
106        }
107        Ok(())
108    }
109
110    #[cfg(feature = "prerendering")]
111    pub(crate) fn write_html(
112        &self,
113        w: &mut impl std::io::Write,
114        this: &ForestNode<DomGeneralElement>,
115        state: &mut WriteHtmlState,
116    ) -> std::io::Result<()> {
117        write!(w, "<{}", self.tag_name)?;
118        let mut has_class = false;
119        for c in &self.classes {
120            if c.len() == 0 {
121                continue;
122            };
123            if !has_class {
124                has_class = true;
125                write!(w, r#" class=""#)?;
126            } else {
127                write!(w, " ")?;
128            }
129            write!(w, "{}", c)?;
130        }
131        if has_class {
132            write!(w, r#"""#)?;
133        }
134        let mut has_style = false;
135        for (name, value) in &self.styles {
136            if !has_style {
137                has_style = true;
138                write!(w, r#" style=""#)?;
139            } else {
140                write!(w, ";")?;
141            }
142            write!(w, "{}:", name)?;
143            html_escape::encode_double_quoted_attribute_to_writer(&value, w)?;
144        }
145        if has_style {
146            write!(w, r#"""#)?;
147        }
148        for (name, value) in &self.attrs {
149            write!(w, r#" {}=""#, name)?;
150            html_escape::encode_double_quoted_attribute_to_writer(&value, w)?;
151            write!(w, r#"""#)?;
152        }
153        write!(w, ">")?;
154        state.prev_is_text_node = false;
155        self.write_children_html(w, this, state)?;
156        write!(w, "</{}>", self.tag_name)?;
157        state.prev_is_text_node = false;
158        Ok(())
159    }
160}
161
162#[cfg(feature = "prerendering-apply")]
163#[derive(Clone)]
164pub(crate) struct RematchedDomElem {
165    inner: std::rc::Rc<std::cell::Cell<Option<web_sys::Element>>>,
166}
167
168#[cfg(feature = "prerendering-apply")]
169impl RematchedDomElem {
170    pub(crate) fn new() -> Self {
171        Self {
172            inner: Default::default(),
173        }
174    }
175
176    pub(crate) fn set(&mut self, e: web_sys::Element) {
177        self.inner.set(Some(e));
178    }
179
180    pub(crate) fn take(&self) -> Option<web_sys::Element> {
181        self.inner.take()
182    }
183}
184
185#[doc(hidden)]
186pub struct DomElement {
187    pub(crate) elem: dom_state_ty!(web_sys::Element, PrerenderingElement, RematchedDomElem),
188    pub(crate) forest_token: ManuallyDrop<ForestToken>,
189    hot_event_list: Option<Box<HotEventList>>,
190    cold_event_list: Option<Box<ColdEventList>>,
191}
192
193impl Drop for DomElement {
194    #[inline]
195    fn drop(&mut self) {
196        if self.hot_event_list.is_some() || self.cold_event_list.is_some() {
197            match &self.elem {
198                DomState::Normal(x) => {
199                    x.unchecked_ref::<MaomiDomElement>().set_maomi(0);
200                }
201                #[cfg(feature = "prerendering")]
202                DomState::Prerendering(_) => {}
203                #[cfg(feature = "prerendering-apply")]
204                DomState::PrerenderingApply(_) => {}
205            }
206            crate::event::tap::remove_element_touch_state(&self.forest_token);
207        }
208        unsafe {
209            ManuallyDrop::drop(&mut self.forest_token);
210        }
211    }
212}
213
214impl std::fmt::Debug for DomElement {
215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216        match &self.elem {
217            DomState::Normal(x) => write!(f, "<{}>", x.tag_name()),
218            #[cfg(feature = "prerendering")]
219            DomState::Prerendering(_) => write!(f, "<(prerendering)>"),
220            #[cfg(feature = "prerendering-apply")]
221            DomState::PrerenderingApply(_) => write!(f, "<(prerendering)>"),
222        }
223    }
224}
225
226impl DomElement {
227    // Safety: must call `init` later (before dropped)
228    pub(crate) unsafe fn new(
229        elem: dom_state_ty!(web_sys::Element, PrerenderingElement, RematchedDomElem),
230    ) -> Self {
231        let x = MaybeUninit::uninit();
232        Self {
233            elem,
234            forest_token: ManuallyDrop::new(x.assume_init()),
235            hot_event_list: None,
236            cold_event_list: None,
237        }
238    }
239
240    pub(crate) fn init(&mut self, forest_token: ForestToken) {
241        self.forest_token = ManuallyDrop::new(forest_token);
242    }
243
244    pub(crate) fn is_prerendering(&self) -> dom_state_ty!((), (), ()) {
245        match &self.elem {
246            DomState::Normal(_) => DomState::Normal(()),
247            #[cfg(feature = "prerendering")]
248            DomState::Prerendering(_) => DomState::Prerendering(()),
249            #[cfg(feature = "prerendering-apply")]
250            DomState::PrerenderingApply(_) => DomState::PrerenderingApply(()),
251        }
252    }
253
254    pub(crate) fn composing_dom(&self) -> &web_sys::Node {
255        match &self.elem {
256            DomState::Normal(x) => &x,
257            #[cfg(feature = "prerendering")]
258            DomState::Prerendering(_) => unreachable!(),
259            #[cfg(feature = "prerendering-apply")]
260            DomState::PrerenderingApply(_) => unreachable!(),
261        }
262    }
263
264    #[cfg(feature = "prerendering-apply")]
265    pub(crate) fn rematch_dom(&mut self, e: web_sys::Node) {
266        if let DomState::PrerenderingApply(x) = &mut self.elem {
267            x.set(e.clone().unchecked_into());
268        }
269        for item in self.cold_event_list_mut() {
270            item.apply(e.unchecked_ref());
271        }
272        self.elem = DomState::Normal(e.unchecked_into());
273        if self.hot_event_list.is_some() || self.cold_event_list.is_some() {
274            self.init_event_token();
275        }
276    }
277
278    pub(crate) fn write_inner_html(
279        &self,
280        _this: &ForestNode<DomGeneralElement>,
281        w: &mut impl std::io::Write,
282        _state: &mut WriteHtmlState,
283    ) -> std::io::Result<()> {
284        match &self.elem {
285            DomState::Normal(x) => {
286                write!(w, "{}", x.inner_html())?;
287            }
288            #[cfg(feature = "prerendering")]
289            DomState::Prerendering(x) => {
290                x.write_children_html(w, _this, _state).unwrap();
291            }
292            #[cfg(feature = "prerendering-apply")]
293            DomState::PrerenderingApply(_) => {}
294        }
295        Ok(())
296    }
297
298    pub(crate) fn write_outer_html(
299        &self,
300        _this: &ForestNode<DomGeneralElement>,
301        w: &mut impl std::io::Write,
302        _state: &mut WriteHtmlState,
303    ) -> std::io::Result<()> {
304        match &self.elem {
305            DomState::Normal(x) => {
306                write!(w, "{}", x.outer_html())?;
307            }
308            #[cfg(feature = "prerendering")]
309            DomState::Prerendering(x) => {
310                x.write_html(w, _this, _state).unwrap();
311            }
312            #[cfg(feature = "prerendering-apply")]
313            DomState::PrerenderingApply(_) => {}
314        }
315        Ok(())
316    }
317
318    pub(crate) fn from_event_dom_elem(
319        dom_elem: &web_sys::Element,
320        bubbles: bool,
321    ) -> Option<ForestNodeRc<DomGeneralElement>> {
322        let ptr = dom_elem
323            .unchecked_ref::<MaomiDomElement>()
324            .maomi()
325            .and_then(|x| NonZeroUsize::new(x));
326        if let Some(ptr) = ptr {
327            return unsafe {
328                ForestTokenAddr::from_ptr(ptr.get() as *const ())
329                    .token()
330                    .unsafe_resolve_token()
331            };
332        }
333        if !bubbles {
334            return None;
335        }
336        let mut next = dom_elem.parent_element();
337        while let Some(cur) = next.as_ref() {
338            let ptr = cur
339                .unchecked_ref::<MaomiDomElement>()
340                .maomi()
341                .and_then(|x| NonZeroUsize::new(x));
342            if let Some(ptr) = ptr {
343                return unsafe {
344                    ForestTokenAddr::from_ptr(ptr.get() as *const ())
345                        .token()
346                        .unsafe_resolve_token()
347                };
348            }
349            next = cur.parent_element();
350        }
351        None
352    }
353
354    fn init_event_token(&mut self) {
355        let ptr = self.forest_token.stable_addr().ptr() as usize;
356        match &self.elem {
357            DomState::Normal(x) => {
358                x.unchecked_ref::<MaomiDomElement>().set_maomi(ptr as usize);
359            }
360            #[cfg(feature = "prerendering")]
361            DomState::Prerendering(_) => {}
362            #[cfg(feature = "prerendering-apply")]
363            DomState::PrerenderingApply(_) => {}
364        }
365    }
366
367    pub(crate) fn hot_event_list_mut(&mut self) -> &mut HotEventList {
368        if self.hot_event_list.is_none() {
369            self.hot_event_list = Some(Default::default());
370            if self.cold_event_list.is_none() {
371                self.init_event_token();
372            }
373        }
374        self.hot_event_list.as_mut().unwrap()
375    }
376
377    pub(crate) fn hot_event_list(&self) -> Option<&HotEventList> {
378        self.hot_event_list.as_ref().map(|x| &**x)
379    }
380
381    pub(crate) fn cold_event_list_mut(&mut self) -> &mut ColdEventList {
382        if self.cold_event_list.is_none() {
383            self.cold_event_list = Some(Default::default());
384            if self.hot_event_list.is_none() {
385                self.init_event_token();
386            }
387        }
388        self.cold_event_list.as_mut().unwrap()
389    }
390
391    pub(crate) fn cold_event_list(&self) -> Option<&ColdEventList> {
392        self.cold_event_list.as_ref().map(|x| &**x)
393    }
394}
395
396pub(crate) trait DomElementBase {
397    fn dom_element_lazy(
398        &self,
399    ) -> &std::cell::UnsafeCell<dom_state_ty!(web_sys::Element, (), RematchedDomElem)>;
400}
401
402/// Some helper functions for DOM elements.
403pub trait DomElementExt {
404    /// Get the underlying DOM element.
405    fn dom_element(&self) -> &web_sys::Element;
406}
407
408impl<T: DomElementBase> DomElementExt for T {
409    #[inline]
410    fn dom_element(&self) -> &web_sys::Element {
411        let ptr = self.dom_element_lazy().get();
412        #[cfg(feature = "prerendering-apply")]
413        if let DomState::PrerenderingApply(x) = unsafe { &*ptr } {
414            // it is safe
415            // because there cannot be another one that takes refs in DomState::PrerenderingApply state
416            if let Some(e) = x.take() {
417                unsafe {
418                    *ptr = DomState::Normal(e);
419                }
420            }
421        }
422        match unsafe { &*ptr } {
423            DomState::Normal(x) => x,
424            #[cfg(feature = "prerendering")]
425            DomState::Prerendering(_) => {
426                panic!("Cannot get DOM element in prerendering stage")
427            }
428            #[cfg(feature = "prerendering-apply")]
429            DomState::PrerenderingApply(_) => {
430                panic!("Cannot get DOM element in prerendering-apply stage")
431            }
432        }
433    }
434}
435
436/// The attributes that accepts a string.
437pub struct DomStrAttr {
438    pub(crate) inner: String,
439    pub(crate) f: fn(&web_sys::HtmlElement, &str),
440    #[cfg(feature = "prerendering")]
441    pub(crate) attr_name: &'static str,
442}
443
444impl Deref for DomStrAttr {
445    type Target = String;
446
447    #[inline]
448    fn deref(&self) -> &Self::Target {
449        &self.inner
450    }
451}
452
453impl<S: ?Sized + PartialEq + ToOwned<Owned = String>> PropertyUpdate<S> for DomStrAttr
454where
455    String: Borrow<S>,
456{
457    type UpdateContext = DomElement;
458
459    #[inline]
460    fn compare_and_set_ref(dest: &mut Self, src: &S, ctx: &mut DomElement) {
461        if dest.inner.borrow() == src {
462            return;
463        }
464        dest.inner = src.to_owned();
465        match &mut ctx.elem {
466            DomState::Normal(x) => {
467                (dest.f)(x.unchecked_ref(), &dest.inner);
468            }
469            #[cfg(feature = "prerendering")]
470            DomState::Prerendering(x) => {
471                x.set_attribute(dest.attr_name, dest.inner.clone());
472            }
473            #[cfg(feature = "prerendering-apply")]
474            DomState::PrerenderingApply(_) => {}
475        }
476    }
477}
478
479/// The attributes that accepts a locale string.
480pub struct DomLocaleStringAttr {
481    pub(crate) inner: LocaleString,
482    pub(crate) f: fn(&web_sys::HtmlElement, &LocaleString),
483    #[cfg(feature = "prerendering")]
484    pub(crate) attr_name: &'static str,
485}
486
487impl Deref for DomLocaleStringAttr {
488    type Target = LocaleString;
489
490    #[inline]
491    fn deref(&self) -> &Self::Target {
492        &self.inner
493    }
494}
495
496impl<S: ?Sized + PartialEq + ToOwned<Owned = LocaleString>> PropertyUpdate<S>
497    for DomLocaleStringAttr
498where
499    LocaleString: Borrow<S>,
500{
501    type UpdateContext = DomElement;
502
503    #[inline]
504    fn compare_and_set_ref(dest: &mut Self, src: &S, ctx: &mut DomElement) {
505        if dest.inner.borrow() == src {
506            return;
507        }
508        dest.inner = src.to_owned();
509        match &mut ctx.elem {
510            DomState::Normal(x) => {
511                (dest.f)(x.unchecked_ref(), &dest.inner);
512            }
513            #[cfg(feature = "prerendering")]
514            DomState::Prerendering(x) => {
515                x.set_attribute(dest.attr_name, dest.inner.to_string());
516            }
517            #[cfg(feature = "prerendering-apply")]
518            DomState::PrerenderingApply(_) => {}
519        }
520    }
521}
522
523/// The attributes that accepts a boolean value.
524///
525/// The boolean attributes are always default to `false` .
526/// In templates, they can be used without `=` like `<div hidden />` .
527pub struct DomBoolAttr {
528    pub(crate) inner: bool,
529    pub(crate) f: fn(&web_sys::HtmlElement, bool),
530    #[cfg(feature = "prerendering")]
531    pub(crate) attr_name: &'static str,
532}
533
534impl Deref for DomBoolAttr {
535    type Target = bool;
536
537    #[inline]
538    fn deref(&self) -> &Self::Target {
539        &self.inner
540    }
541}
542
543impl<S: ?Sized + PartialEq + ToOwned<Owned = bool>> PropertyUpdate<S> for DomBoolAttr
544where
545    bool: Borrow<S>,
546{
547    type UpdateContext = DomElement;
548
549    #[inline]
550    fn compare_and_set_ref(dest: &mut Self, src: &S, ctx: &mut DomElement) {
551        if dest.inner.borrow() == src {
552            return;
553        }
554        dest.inner = src.to_owned();
555        match &mut ctx.elem {
556            DomState::Normal(x) => {
557                (dest.f)(x.unchecked_ref(), dest.inner);
558            }
559            #[cfg(feature = "prerendering")]
560            DomState::Prerendering(x) => {
561                if dest.inner {
562                    x.set_attribute(dest.attr_name, String::with_capacity(0));
563                } else {
564                    x.remove_attribute(dest.attr_name);
565                }
566            }
567            #[cfg(feature = "prerendering-apply")]
568            DomState::PrerenderingApply(_) => {}
569        }
570    }
571}
572
573/// The attributes that accepts a non-negative integer.
574pub struct DomU32Attr {
575    pub(crate) inner: u32,
576    pub(crate) f: fn(&web_sys::HtmlElement, u32),
577    #[cfg(feature = "prerendering")]
578    pub(crate) attr_name: &'static str,
579}
580
581impl Deref for DomU32Attr {
582    type Target = u32;
583
584    #[inline]
585    fn deref(&self) -> &Self::Target {
586        &self.inner
587    }
588}
589
590impl<S: ?Sized + PartialEq + ToOwned<Owned = u32>> PropertyUpdate<S> for DomU32Attr
591where
592    u32: Borrow<S>,
593{
594    type UpdateContext = DomElement;
595
596    #[inline]
597    fn compare_and_set_ref(dest: &mut Self, src: &S, ctx: &mut DomElement) {
598        if dest.inner.borrow() == src {
599            return;
600        }
601        dest.inner = src.to_owned();
602        match &mut ctx.elem {
603            DomState::Normal(x) => {
604                (dest.f)(x.unchecked_ref(), dest.inner);
605            }
606            #[cfg(feature = "prerendering")]
607            DomState::Prerendering(x) => {
608                x.set_attribute(dest.attr_name, dest.inner.to_string());
609            }
610            #[cfg(feature = "prerendering-apply")]
611            DomState::PrerenderingApply(_) => {}
612        }
613    }
614}
615
616/// The attributes that accepts an integer.
617pub struct DomI32Attr {
618    pub(crate) inner: i32,
619    pub(crate) f: fn(&web_sys::HtmlElement, i32),
620    #[cfg(feature = "prerendering")]
621    pub(crate) attr_name: &'static str,
622}
623
624impl Deref for DomI32Attr {
625    type Target = i32;
626
627    #[inline]
628    fn deref(&self) -> &Self::Target {
629        &self.inner
630    }
631}
632
633impl<S: ?Sized + PartialEq + ToOwned<Owned = i32>> PropertyUpdate<S> for DomI32Attr
634where
635    i32: Borrow<S>,
636{
637    type UpdateContext = DomElement;
638
639    #[inline]
640    fn compare_and_set_ref(dest: &mut Self, src: &S, ctx: &mut DomElement) {
641        if dest.inner.borrow() == src {
642            return;
643        }
644        dest.inner = src.to_owned();
645        match &mut ctx.elem {
646            DomState::Normal(x) => {
647                (dest.f)(x.unchecked_ref(), dest.inner);
648            }
649            #[cfg(feature = "prerendering")]
650            DomState::Prerendering(x) => {
651                x.set_attribute(dest.attr_name, dest.inner.to_string());
652            }
653            #[cfg(feature = "prerendering-apply")]
654            DomState::PrerenderingApply(_) => {}
655        }
656    }
657}
658
659/// The attributes that accepts a floating number.
660pub struct DomF64Attr {
661    pub(crate) inner: f64,
662    pub(crate) f: fn(&web_sys::HtmlElement, f64),
663    #[cfg(feature = "prerendering")]
664    pub(crate) attr_name: &'static str,
665}
666
667impl Deref for DomF64Attr {
668    type Target = f64;
669
670    #[inline]
671    fn deref(&self) -> &Self::Target {
672        &self.inner
673    }
674}
675
676impl<S: ?Sized + PartialEq + ToOwned<Owned = f64>> PropertyUpdate<S> for DomF64Attr
677where
678    f64: Borrow<S>,
679{
680    type UpdateContext = DomElement;
681
682    #[inline]
683    fn compare_and_set_ref(dest: &mut Self, src: &S, ctx: &mut DomElement) {
684        if dest.inner.borrow() == src {
685            return;
686        }
687        dest.inner = src.to_owned();
688        match &mut ctx.elem {
689            DomState::Normal(x) => {
690                (dest.f)(x.unchecked_ref(), dest.inner);
691            }
692            #[cfg(feature = "prerendering")]
693            DomState::Prerendering(x) => {
694                x.set_attribute(dest.attr_name, dest.inner.to_string());
695            }
696            #[cfg(feature = "prerendering-apply")]
697            DomState::PrerenderingApply(_) => {}
698        }
699    }
700}
701
702/// The attributes that accepts a binding string.
703///
704/// It should be set with a `BindingValue` .
705pub struct DomBindingStrAttr {
706    pub(crate) inner: Rc<RefCell<BindingValue<String>>>,
707    pub(crate) f: fn(&web_sys::HtmlElement, &str),
708    #[cfg(feature = "prerendering")]
709    pub(crate) attr_name: &'static str,
710}
711
712impl DomBindingStrAttr {
713    /// Get a reference of the value.
714    #[inline]
715    pub fn with<R>(&self, f: impl FnOnce(&String) -> R) -> R {
716        (*self.inner).borrow().with(f)
717    }
718
719    /// Get the value.
720    #[inline]
721    pub fn get(&self) -> String {
722        (*self.inner).borrow().get()
723    }
724}
725
726impl PropertyUpdate<BindingValue<String>> for DomBindingStrAttr {
727    type UpdateContext = DomElement;
728
729    #[inline]
730    fn compare_and_set_ref(
731        dest: &mut Self,
732        src: &BindingValue<String>,
733        ctx: &mut Self::UpdateContext,
734    ) {
735        let inner = &mut dest.inner.borrow_mut();
736        if BindingValue::ptr_eq(inner, src) {
737            return;
738        }
739        let old = std::mem::replace(&mut **inner, src.clone_ref());
740        inner.with(|inner| {
741            if old.with(|x| inner == x) {
742                return;
743            }
744            match &mut ctx.elem {
745                DomState::Normal(x) => {
746                    (dest.f)(x.unchecked_ref(), inner);
747                }
748                #[cfg(feature = "prerendering")]
749                DomState::Prerendering(x) => {
750                    x.set_attribute(dest.attr_name, inner.to_string());
751                }
752                #[cfg(feature = "prerendering-apply")]
753                DomState::PrerenderingApply(_) => {}
754            }
755        });
756    }
757}
758
759/// The attributes that accepts a binding boolean value.
760///
761/// It should be set with a `BindingValue` .
762pub struct DomBindingBoolAttr {
763    pub(crate) inner: Rc<RefCell<BindingValue<bool>>>,
764    pub(crate) f: fn(&web_sys::HtmlElement, bool),
765    #[cfg(feature = "prerendering")]
766    pub(crate) attr_name: &'static str,
767}
768
769impl DomBindingBoolAttr {
770    /// Get a reference of the value.
771    #[inline]
772    pub fn with<R>(&self, f: impl FnOnce(&bool) -> R) -> R {
773        (*self.inner).borrow().with(f)
774    }
775
776    /// Get the value.
777    #[inline]
778    pub fn get(&self) -> bool {
779        (*self.inner).borrow().get()
780    }
781}
782
783impl PropertyUpdate<BindingValue<bool>> for DomBindingBoolAttr {
784    type UpdateContext = DomElement;
785
786    #[inline]
787    fn compare_and_set_ref(
788        dest: &mut Self,
789        src: &BindingValue<bool>,
790        ctx: &mut Self::UpdateContext,
791    ) {
792        let inner = &mut dest.inner.borrow_mut();
793        if BindingValue::ptr_eq(inner, src) {
794            return;
795        }
796        let old = std::mem::replace(&mut **inner, src.clone_ref());
797        inner.with(|inner| {
798            if old.with(|x| inner == x) {
799                return;
800            }
801            match &mut ctx.elem {
802                DomState::Normal(x) => {
803                    (dest.f)(x.unchecked_ref(), *inner);
804                }
805                #[cfg(feature = "prerendering")]
806                DomState::Prerendering(x) => {
807                    if *inner {
808                        x.set_attribute(dest.attr_name, String::with_capacity(0));
809                    } else {
810                        x.remove_attribute(dest.attr_name);
811                    }
812                }
813                #[cfg(feature = "prerendering-apply")]
814                DomState::PrerenderingApply(_) => {}
815            }
816        });
817    }
818}
819
820/// The attributes that accepts a floating number.
821///
822/// It should be set with a `BindingValue` .
823pub struct DomBindingF64Attr {
824    pub(crate) inner: Rc<RefCell<BindingValue<f64>>>,
825    pub(crate) f: fn(&web_sys::HtmlElement, f64),
826    #[cfg(feature = "prerendering")]
827    pub(crate) attr_name: &'static str,
828}
829
830impl DomBindingF64Attr {
831    /// Get a reference of the value.
832    #[inline]
833    pub fn with<R>(&self, f: impl FnOnce(&f64) -> R) -> R {
834        (*self.inner).borrow().with(f)
835    }
836
837    /// Get the value.
838    #[inline]
839    pub fn get(&self) -> f64 {
840        (*self.inner).borrow().get()
841    }
842}
843
844impl PropertyUpdate<BindingValue<f64>> for DomBindingF64Attr {
845    type UpdateContext = DomElement;
846
847    #[inline]
848    fn compare_and_set_ref(
849        dest: &mut Self,
850        src: &BindingValue<f64>,
851        ctx: &mut Self::UpdateContext,
852    ) {
853        let inner = &mut dest.inner.borrow_mut();
854        if BindingValue::ptr_eq(inner, src) {
855            return;
856        }
857        let old = std::mem::replace(&mut **inner, src.clone_ref());
858        inner.with(|inner| {
859            if old.with(|x| inner == x) {
860                return;
861            }
862            match &mut ctx.elem {
863                DomState::Normal(x) => {
864                    (dest.f)(x.unchecked_ref(), *inner);
865                }
866                #[cfg(feature = "prerendering")]
867                DomState::Prerendering(x) => {
868                    x.set_attribute(dest.attr_name, inner.to_string());
869                }
870                #[cfg(feature = "prerendering-apply")]
871                DomState::PrerenderingApply(_) => {}
872            }
873        });
874    }
875}
876
877pub(crate) fn init_binding_prop(
878    target: &mut DomElement,
879    name: &'static str,
880    f: impl 'static + Fn(web_sys::Event),
881) {
882    #[cfg(feature = "prerendering")]
883    if let crate::DomState::Prerendering(_) = &target.elem {
884        return;
885    }
886    let c = Closure::new(move |e| f(e));
887    let item = ColdEventItem::BindingEventListener(name, c);
888    match &target.elem {
889        crate::DomState::Normal(x) => item.apply(x),
890        #[cfg(feature = "prerendering")]
891        crate::DomState::Prerendering(_) => unreachable!(),
892        #[cfg(feature = "prerendering-apply")]
893        crate::DomState::PrerenderingApply(_) => {}
894    }
895    target.cold_event_list_mut().push(item);
896}