dominator/
dom.rs

1use std::pin::Pin;
2use std::borrow::BorrowMut;
3use std::convert::AsRef;
4use std::future::Future;
5use std::task::{Context, Poll};
6
7use once_cell::sync::Lazy;
8use futures_signals::signal::{Signal, Mutable, MutableSignal, not};
9use futures_signals::signal_vec::SignalVec;
10use futures_util::FutureExt;
11use futures_channel::oneshot;
12use discard::{Discard, DiscardOnDrop};
13use wasm_bindgen::{JsValue, UnwrapThrowExt, JsCast, intern};
14use web_sys::{HtmlElement, Node, EventTarget, Element, CssRule, CssStyleRule, CssStyleSheet, CssStyleDeclaration, ShadowRoot, ShadowRootMode, ShadowRootInit, Text};
15
16use crate::bindings;
17use crate::bindings::WINDOW;
18use crate::callbacks::Callbacks;
19use crate::traits::*;
20use crate::fragment::{Fragment, FragmentBuilder};
21use crate::operations;
22use crate::operations::{for_each, spawn_future};
23use crate::utils::{EventListener, on, RefCounter, MutableListener, UnwrapJsExt, ValueDiscard, FnDiscard};
24
25#[cfg(doc)]
26use crate::fragment;
27
28
29pub struct RefFn<A, B, C> where B: ?Sized, C: Fn(&A) -> &B {
30    value: A,
31    callback: C,
32}
33
34impl<A, B, C> RefFn<A, B, C> where B: ?Sized, C: Fn(&A) -> &B {
35    #[inline]
36    pub fn new(value: A, callback: C) -> Self {
37        Self {
38            value,
39            callback,
40        }
41    }
42
43    #[inline]
44    pub fn call_ref(&self) -> &B {
45        (self.callback)(&self.value)
46    }
47
48    /*pub fn map<D, E>(self, callback: E) -> RefFn<A, impl Fn(&A) -> &D>
49        where D: ?Sized,
50              E: Fn(&B) -> &D {
51
52        let old_callback = self.callback;
53
54        RefFn {
55            value: self.value,
56            callback: move |value| callback(old_callback(value)),
57        }
58    }*/
59}
60
61/*impl<A, B, C> Deref for RefFn<A, C> where B: ?Sized, C: Fn(&A) -> &B {
62    type Target = B;
63
64    #[inline]
65    fn deref(&self) -> &Self::Target {
66        self.call_ref()
67    }
68}*/
69
70/*impl<A, B, C> AsRef<B> for RefFn<A, C> where B: ?Sized, C: Fn(&A) -> &B {
71    #[inline]
72    fn as_ref(&self) -> &B {
73        self.call_ref()
74    }
75}*/
76
77
78// https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#Valid%20Namespace%20URIs
79const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
80
81// 32-bit signed int
82pub const HIGHEST_ZINDEX: &str = "2147483647";
83
84
85static HIDDEN_CLASS: Lazy<String> = Lazy::new(|| class! {
86    .style_important("display", "none")
87});
88
89
90// TODO should return HtmlBodyElement ?
91pub fn body() -> HtmlElement {
92    bindings::body()
93}
94
95
96pub fn get_id(id: &str) -> Element {
97    // TODO intern ?
98    bindings::get_element_by_id(id)
99}
100
101
102pub struct DomHandle {
103    parent: Node,
104    dom: Dom,
105}
106
107impl DomHandle {
108    /// Initializes the state after inserting a [`Dom`] into the real DOM.
109    ///
110    /// You shouldn't normally use this, use [`append_dom`] instead.
111    ///
112    /// But in some very rare situations you might want to manually
113    /// control where the [`Dom`] is inserted.
114    #[inline]
115    pub(crate) fn new(parent: &Node, mut dom: Dom) -> Self {
116        dom.callbacks.trigger_after_insert();
117
118        // This prevents it from triggering after_remove
119        dom.callbacks.leak();
120
121        Self {
122            parent: parent.clone(),
123            dom,
124        }
125    }
126}
127
128impl Discard for DomHandle {
129    #[inline]
130    #[track_caller]
131    fn discard(self) {
132        bindings::remove_child(&self.parent, &self.dom.element);
133        self.dom.callbacks.discard();
134    }
135}
136
137
138/// Appends a [`Dom`] into the real DOM.
139///
140/// When the [`DomHandle`] is discarded using `handle.discard()` it
141/// will remove the [`Dom`] from the DOM and it will clean up the
142/// internal [`Dom`] state.
143///
144/// If you never call `handle.discard()` then it will leak memory
145/// forever. This is normally what you want for a top-level [`Dom`].
146#[inline]
147#[track_caller]
148pub fn append_dom(parent: &Node, dom: Dom) -> DomHandle {
149    bindings::append_child(&parent, &dom.element);
150    DomHandle::new(parent, dom)
151}
152
153/// The same as [`append_dom`] except it replaces an existing DOM node.
154#[inline]
155#[track_caller]
156pub fn replace_dom(parent: &Node, old_node: &Node, dom: Dom) -> DomHandle {
157    bindings::replace_child(&parent, &dom.element, old_node);
158    DomHandle::new(parent, dom)
159}
160
161
162#[must_use = "Signals do nothing unless polled"]
163enum IsWindowLoaded {
164    Initial {},
165    Pending {
166        receiver: oneshot::Receiver<Option<bool>>,
167        _event: DiscardOnDrop<EventListener>,
168    },
169    Done {},
170}
171
172impl Signal for IsWindowLoaded {
173    type Item = bool;
174
175    fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
176        let result = match *self {
177            IsWindowLoaded::Initial {} => {
178                let is_ready = bindings::ready_state() == "complete";
179
180                if is_ready {
181                    Poll::Ready(Some(true))
182
183                } else {
184                    let (sender, receiver) = oneshot::channel();
185
186                    *self = IsWindowLoaded::Pending {
187                        receiver,
188                        _event: DiscardOnDrop::new(WINDOW.with(|window| {
189                            EventListener::once(window, "load", move |_| {
190                                // TODO test this
191                                crate::__unwrap!(
192                                    sender.send(Some(true)),
193                                    _e => panic!("Invalid is_window_loaded() state"),
194                                )
195                            })
196                        })),
197                    };
198
199                    Poll::Ready(Some(false))
200                }
201            },
202            IsWindowLoaded::Pending { ref mut receiver, .. } => {
203                receiver.poll_unpin(cx).map(|x| x.unwrap_throw())
204            },
205            IsWindowLoaded::Done {} => {
206                Poll::Ready(None)
207            },
208        };
209
210        if let Poll::Ready(Some(true)) = result {
211            *self = IsWindowLoaded::Done {};
212        }
213
214        result
215    }
216}
217
218// TODO this should be moved into gloo
219/// `Signal` which says whether the window is fully loaded or not.
220///
221/// This is the same as the [DOM `load` event](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event).
222#[inline]
223pub fn is_window_loaded() -> impl Signal<Item = bool> {
224    IsWindowLoaded::Initial {}
225}
226
227
228/// This is returned by the [`window_size`] function.
229#[derive(Debug, Clone, Copy, PartialEq)]
230pub struct WindowSize {
231    pub width: f64,
232    pub height: f64,
233}
234
235impl WindowSize {
236    fn new() -> Self {
237        WINDOW.with(|window| {
238            let width = window.inner_width().unwrap_throw().as_f64().unwrap_throw();
239            let height = window.inner_height().unwrap_throw().as_f64().unwrap_throw();
240
241            Self { width, height }
242        })
243    }
244}
245
246
247thread_local! {
248    static WINDOW_SIZE: RefCounter<MutableListener<WindowSize>> = RefCounter::new();
249}
250
251
252#[derive(Debug)]
253#[must_use = "Signals do nothing unless polled"]
254struct WindowSizeSignal {
255    signal: MutableSignal<WindowSize>,
256}
257
258impl Signal for WindowSizeSignal {
259    type Item = WindowSize;
260
261    #[inline]
262    fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
263        Pin::new(&mut self.signal).poll_change(cx)
264    }
265}
266
267impl Drop for WindowSizeSignal {
268    fn drop(&mut self) {
269        WINDOW_SIZE.with(|size| {
270            size.decrement();
271        });
272    }
273}
274
275
276/// `Signal` which gives the current width / height of the window.
277///
278/// When the window is resized, it will automatically update with the new size.
279pub fn window_size() -> impl Signal<Item = WindowSize> {
280    let signal = WINDOW_SIZE.with(|size| {
281        let size = size.increment(|| {
282            let size = Mutable::new(WindowSize::new());
283
284            let listener = {
285                let size = size.clone();
286
287                WINDOW.with(move |window| {
288                    on(window, &EventOptions::default(), move |_: crate::events::Resize| {
289                        size.set_neq(WindowSize::new());
290                    })
291                })
292            };
293
294            MutableListener::new(size, listener)
295        });
296
297        size.as_mutable().signal()
298    });
299
300    WindowSizeSignal { signal }
301}
302
303
304fn media_query_raw<A, F>(query: &str, mut f: F) -> (Mutable<A>, EventListener)
305    where A: PartialEq + 'static,
306          F: FnMut(bool) -> A + 'static {
307
308    let query = WINDOW.with(|window| window.match_media(query).unwrap().unwrap());
309
310    let mutable = Mutable::new(f(query.matches()));
311
312    let listener = on(&query, &EventOptions::default(), {
313        let mutable = mutable.clone();
314        let query = query.clone();
315
316        move |_: crate::events::Change| {
317            mutable.set_neq(f(query.matches()));
318        }
319    });
320
321    (mutable, listener)
322}
323
324
325#[derive(Debug)]
326#[must_use = "Signals do nothing unless polled"]
327struct MediaQuerySignal {
328    signal: MutableSignal<bool>,
329    _listener: EventListener,
330}
331
332impl Signal for MediaQuerySignal {
333    type Item = bool;
334
335    #[inline]
336    fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
337        Pin::new(&mut self.signal).poll_change(cx)
338    }
339}
340
341/// `Signal` which is `true` if the media query matches.
342///
343/// The Signal will automatically update when the situation changes.
344///
345/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries)
346///
347/// # Example
348///
349/// ```rust
350/// media_query("(max-width: 500px) and (max-height: 300px)").map(|small| {
351///     // The window is less than 500px wide AND the window is less than 300px tall
352///     if small {
353///         ...
354///
355///     } else {
356///         ...
357///     }
358/// })
359/// ```
360///
361/// ```rust
362/// media_query("(orientation: portrait)").map(|portrait| {
363///     if portrait {
364///         // The browser is in portrait mode
365///
366///     } else {
367///         // The browser is in landscape mode
368///     }
369/// })
370/// ```
371///
372/// ```rust
373/// media_query("(display-mode: fullscreen)").map(|fullscreen| {
374///     if fullscreen {
375///         // The browser is in fullscreen mode
376///
377///     } else {
378///         // The browser is not in fullscreen mode
379///     }
380/// })
381/// ```
382pub fn media_query(query: &str) -> impl Signal<Item = bool> {
383    let (mutable, listener) = media_query_raw(query, |value| value);
384
385    MediaQuerySignal {
386        signal: mutable.signal(),
387        _listener: listener,
388    }
389}
390
391
392/// Color scheme of the browser.
393///
394/// This is returned from the [`color_scheme`] function.
395#[derive(Debug, Clone, Copy, PartialEq, Eq)]
396pub enum ColorScheme {
397    Light,
398    Dark,
399}
400
401impl ColorScheme {
402    #[inline]
403    fn from_bool(matches: bool) -> Self {
404        if matches {
405            Self::Dark
406        } else {
407            Self::Light
408        }
409    }
410
411    /// Whether the color scheme is light.
412    #[inline]
413    pub fn is_light(self) -> bool {
414        matches!(self, Self::Light)
415    }
416
417    /// Whether the color scheme is dark.
418    #[inline]
419    pub fn is_dark(self) -> bool {
420        matches!(self, Self::Dark)
421    }
422
423    /// Chooses a value based on whether the color scheme is light or dark.
424    ///
425    /// # Example
426    ///
427    /// ```rust
428    /// // If the color scheme is light, then it returns "white"
429    /// // If the color scheme is dark, then it returns "black"
430    /// color_scheme().map(|x| x.choose("white", "black"))
431    /// ```
432    #[inline]
433    pub fn choose<A>(self, light: A, dark: A) -> A {
434        match self {
435            Self::Light => light,
436            Self::Dark => dark,
437        }
438    }
439}
440
441
442thread_local! {
443    static COLOR_SCHEME: RefCounter<MutableListener<ColorScheme>> = RefCounter::new();
444}
445
446
447#[derive(Debug)]
448#[must_use = "Signals do nothing unless polled"]
449struct ColorSchemeSignal {
450    signal: MutableSignal<ColorScheme>,
451}
452
453impl Signal for ColorSchemeSignal {
454    type Item = ColorScheme;
455
456    #[inline]
457    fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
458        Pin::new(&mut self.signal).poll_change(cx)
459    }
460}
461
462impl Drop for ColorSchemeSignal {
463    fn drop(&mut self) {
464        COLOR_SCHEME.with(|x| x.decrement());
465    }
466}
467
468
469/// The color scheme of the browser.
470///
471/// This can be used to automatically adjust your website's colors based on
472/// whether the user prefers dark mode or light mode.
473///
474/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)
475pub fn color_scheme() -> impl Signal<Item = ColorScheme> {
476    let signal = COLOR_SCHEME.with(|counter| {
477        let counter = counter.increment(|| {
478            let (mutable, listener) = media_query_raw("(prefers-color-scheme: dark)", ColorScheme::from_bool);
479
480            MutableListener::new(mutable, listener)
481        });
482
483        counter.as_mutable().signal()
484    });
485
486    ColorSchemeSignal { signal }
487}
488
489
490// TODO should this intern ?
491#[inline]
492pub fn text(value: &str) -> Dom {
493    Dom::new(bindings::create_text_node(value).into())
494}
495
496
497fn make_text_signal<A, B>(callbacks: &mut Callbacks, value: B) -> Text
498    where A: AsStr,
499          B: Signal<Item = A> + 'static {
500
501    let element = bindings::create_text_node(intern(""));
502
503    {
504        let element = element.clone();
505
506        callbacks.after_remove(for_each(value, move |value| {
507            value.with_str(|value| {
508                // TODO maybe this should intern ?
509                bindings::set_text(&element, value);
510            });
511        }));
512    }
513
514    element
515}
516
517// TODO should this inline ?
518pub fn text_signal<A, B>(value: B) -> Dom
519    where A: AsStr,
520          B: Signal<Item = A> + 'static {
521
522    let mut callbacks = Callbacks::new();
523
524    let element = make_text_signal(&mut callbacks, value);
525
526    Dom {
527        element: element.into(),
528        callbacks: callbacks,
529    }
530}
531
532
533// TODO better warning message for must_use
534#[must_use]
535#[derive(Debug)]
536pub struct Dom {
537    pub(crate) element: Node,
538    pub(crate) callbacks: Callbacks,
539}
540
541impl Dom {
542    #[inline]
543    pub fn new(element: Node) -> Self {
544        Self {
545            element,
546            callbacks: Callbacks::new(),
547        }
548    }
549
550    #[inline]
551    #[track_caller]
552    pub fn empty() -> Self {
553        Self::new(bindings::create_empty_node())
554    }
555
556    #[deprecated(since = "0.5.15", note = "Store the data explicitly in a component struct instead")]
557    #[inline]
558    pub fn with_state<A, F>(mut state: A, initializer: F) -> Dom
559        where A: 'static,
560              F: FnOnce(&mut A) -> Dom {
561
562        let mut dom = initializer(&mut state);
563
564        dom.callbacks.after_remove(ValueDiscard::new(state));
565
566        dom
567    }
568}
569
570
571#[inline]
572#[track_caller]
573fn create_element<A>(name: &str) -> A where A: JsCast {
574    // TODO use unchecked_into in release mode ?
575    crate::__unwrap!(
576        bindings::create_element(intern(name)).dyn_into(),
577        e => panic!("Invalid DOM type: \"{}\" => {:?}", name, JsValue::as_ref(&e)),
578    )
579}
580
581#[inline]
582#[track_caller]
583fn create_element_ns<A>(name: &str, namespace: &str) -> A where A: JsCast {
584    // TODO use unchecked_into in release mode ?
585    crate::__unwrap!(
586        bindings::create_element_ns(intern(namespace), intern(name)).dyn_into(),
587        e => panic!("Invalid DOM type: \"{}\" => {:?}", name, JsValue::as_ref(&e)),
588    )
589}
590
591
592// TODO should this inline ?
593fn set_option<A, B, C, D, F>(element: A, callbacks: &mut Callbacks, value: D, mut f: F)
594    where A: 'static,
595          C: OptionStr<Output = B>,
596          D: Signal<Item = C> + 'static,
597          F: FnMut(&A, Option<B>) + 'static {
598
599    let mut is_set = false;
600
601    callbacks.after_remove(for_each(value, move |value| {
602        let value = value.into_option();
603
604        if value.is_some() {
605            is_set = true;
606
607        } else if is_set {
608            is_set = false;
609
610        } else {
611            return;
612        }
613
614        f(&element, value);
615    }));
616}
617
618// TODO should this inline ?
619// TODO track_caller
620fn set_style<A, B>(style: &CssStyleDeclaration, name: &A, value: B, important: bool)
621    where A: MultiStr,
622          B: MultiStr {
623
624    let mut names = vec![];
625    let mut values = vec![];
626
627    // TODO track_caller
628    fn try_set_style(style: &CssStyleDeclaration, names: &mut Vec<String>, values: &mut Vec<String>, name: &str, value: &str, important: bool) -> Option<()> {
629        assert!(value != "");
630
631        // TODO handle browser prefixes ?
632        bindings::remove_style(style, name);
633
634        bindings::set_style(style, name, value, important);
635
636        let is_changed = bindings::get_style(style, name) != "";
637
638        if is_changed {
639            Some(())
640
641        } else {
642            names.push(String::from(name));
643            values.push(String::from(value));
644            None
645        }
646    }
647
648    let okay = name.find_map(|name| {
649        let name: &str = intern(name);
650
651        value.find_map(|value| {
652            // TODO should this intern ?
653            try_set_style(style, &mut names, &mut values, &name, &value, important)
654        })
655    });
656
657    if let None = okay {
658        if cfg!(debug_assertions) {
659            // TODO maybe make this configurable
660            panic!("style is incorrect:\n  names: {}\n  values: {}", names.join(", "), values.join(", "));
661        }
662    }
663}
664
665// TODO should this inline ?
666// TODO track_caller
667fn set_style_signal<A, B, C, D>(style: CssStyleDeclaration, callbacks: &mut Callbacks, name: A, value: D, important: bool)
668    where A: MultiStr + 'static,
669          B: MultiStr,
670          C: OptionStr<Output = B>,
671          D: Signal<Item = C> + 'static {
672
673    set_option(style, callbacks, value, move |style, value| {
674        match value {
675            Some(value) => {
676                // TODO should this intern or not ?
677                set_style(style, &name, value, important);
678            },
679            None => {
680                name.each(|name| {
681                    // TODO handle browser prefixes ?
682                    bindings::remove_style(style, intern(name));
683                });
684            },
685        }
686    });
687}
688
689// TODO should this inline ?
690// TODO track_caller
691fn set_style_unchecked_signal<A, B, C, D>(style: CssStyleDeclaration, callbacks: &mut Callbacks, name: A, value: D, important: bool)
692    where A: AsStr + 'static,
693          B: AsStr,
694          C: OptionStr<Output = B>,
695          D: Signal<Item = C> + 'static {
696
697    set_option(style, callbacks, value, move |style, value| {
698        match value {
699            Some(value) => {
700                name.with_str(|name| {
701                    let name: &str = intern(name);
702
703                    value.with_str(|value| {
704                        bindings::set_style(style, name, value, important);
705                    });
706                });
707            },
708            None => {
709                name.with_str(|name| {
710                    bindings::remove_style(style, intern(name));
711                });
712            },
713        }
714    });
715}
716
717// TODO check that the property *actually* was changed ?
718// TODO maybe use AsRef<Object> ?
719// TODO should this inline ?
720#[track_caller]
721fn set_property<A, B, C>(element: &A, name: &B, value: C) where A: AsRef<JsValue>, B: MultiStr, C: Into<JsValue> {
722    let element = element.as_ref();
723    let value = value.into();
724
725    name.each(|name| {
726        bindings::set_property(element, intern(name), &value);
727    });
728}
729
730
731#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
732pub struct EventOptions {
733    pub bubbles: bool,
734    pub preventable: bool,
735}
736
737impl EventOptions {
738    pub fn bubbles() -> Self {
739        Self {
740            bubbles: true,
741            preventable: false,
742        }
743    }
744
745    pub fn preventable() -> Self {
746        Self {
747            bubbles: false,
748            preventable: true,
749        }
750    }
751
752    pub(crate) fn into_gloo(self) -> gloo_events::EventListenerOptions {
753        gloo_events::EventListenerOptions {
754            phase: if self.bubbles {
755                gloo_events::EventListenerPhase::Bubble
756            } else {
757                gloo_events::EventListenerPhase::Capture
758            },
759            passive: !self.preventable,
760        }
761    }
762}
763
764impl Default for EventOptions {
765    fn default() -> Self {
766        Self {
767            bubbles: false,
768            preventable: false,
769        }
770    }
771}
772
773
774/// Scroll behavior for [`ScrollIntoView`].
775#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
776pub enum ScrollBehavior {
777    /// The behavior is determined by the [`scroll-behavior`](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior) CSS property.
778    ///
779    /// This is usually the same as [`ScrollBehavior::Instant`].
780    Auto,
781
782    /// Instantly scrolls to the final position.
783    Instant,
784
785    /// Smoothly scrolls from the current position to the final position.
786    Smooth,
787}
788
789impl ScrollBehavior {
790    fn into_js(self) -> web_sys::ScrollBehavior {
791        match self {
792            Self::Auto => web_sys::ScrollBehavior::Auto,
793            Self::Instant => web_sys::ScrollBehavior::Instant,
794            Self::Smooth => web_sys::ScrollBehavior::Smooth,
795        }
796    }
797}
798
799impl Default for ScrollBehavior {
800    fn default() -> Self {
801        Self::Auto
802    }
803}
804
805
806/// Scroll alignment for [`ScrollIntoView`].
807#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
808pub enum ScrollAlign {
809    /// Scrolls so that the element is in the top / left corner of the screen.
810    Start,
811
812    /// Scrolls so that the element is centered in the middle of the screen.
813    Center,
814
815    /// Scrolls so that the element is in the bottom / right corner of the screen.
816    End,
817
818    /// If the element is already in view, then do nothing.
819    ///
820    /// If the element is not in view, then scroll the smallest amount to make the element in view.
821    Nearest,
822}
823
824impl ScrollAlign {
825    fn into_js(self) -> web_sys::ScrollLogicalPosition {
826        match self {
827            Self::Start => web_sys::ScrollLogicalPosition::Start,
828            Self::Center => web_sys::ScrollLogicalPosition::Center,
829            Self::End => web_sys::ScrollLogicalPosition::End,
830            Self::Nearest => web_sys::ScrollLogicalPosition::Nearest,
831        }
832    }
833}
834
835
836/// Specifies the scroll behavior for [`DomBuilder::scroll_into_view_signal`].
837#[derive(Debug, Clone, Hash, PartialEq)]
838pub struct ScrollIntoView {
839    /// Behavior while scrolling.
840    pub behavior: ScrollBehavior,
841
842    /// Horizontal alignment.
843    pub align_x: ScrollAlign,
844
845    /// Vertical alignment.
846    pub align_y: ScrollAlign,
847}
848
849impl ScrollIntoView {
850    /// Smoothly scrolls until the element is in the middle of the screen.
851    pub fn smooth_center() -> Self {
852        Self {
853            behavior: ScrollBehavior::Smooth,
854            align_x: ScrollAlign::Center,
855            align_y: ScrollAlign::Center,
856        }
857    }
858
859    /// Smoothly scrolls the minimum amount until the element is in view.
860    pub fn smooth_nearest() -> Self {
861        Self {
862            behavior: ScrollBehavior::Smooth,
863            align_x: ScrollAlign::Nearest,
864            align_y: ScrollAlign::Nearest,
865        }
866    }
867
868    fn into_js(&self) -> web_sys::ScrollIntoViewOptions {
869        let output = web_sys::ScrollIntoViewOptions::new();
870        output.set_inline(self.align_x.into_js());
871        output.set_block(self.align_y.into_js());
872        output.set_behavior(self.behavior.into_js());
873        output
874    }
875}
876
877impl Default for ScrollIntoView {
878    fn default() -> Self {
879        Self {
880            behavior: ScrollBehavior::default(),
881            align_x: ScrollAlign::Start,
882            align_y: ScrollAlign::Nearest,
883        }
884    }
885}
886
887
888// TODO better warning message for must_use
889#[must_use]
890#[derive(Debug)]
891pub struct DomBuilder<A> {
892    element: A,
893    callbacks: Callbacks,
894}
895
896impl<A> DomBuilder<A> where A: JsCast {
897    #[track_caller]
898    #[inline]
899    pub fn new_html(name: &str) -> Self {
900        Self::new(create_element(name))
901    }
902
903    #[track_caller]
904    #[inline]
905    pub fn new_svg(name: &str) -> Self {
906        Self::new(create_element_ns(name, SVG_NAMESPACE))
907    }
908}
909
910impl<A> DomBuilder<A> {
911    #[inline]
912    #[doc(hidden)]
913    pub fn __internal_transfer_callbacks<B>(mut self, mut shadow: DomBuilder<B>) -> Self {
914        self.callbacks.after_insert.append(&mut shadow.callbacks.after_insert);
915        self.callbacks.after_remove.append(&mut shadow.callbacks.after_remove);
916        self
917    }
918
919    #[inline]
920    pub fn new(value: A) -> Self {
921        Self {
922            element: value,
923            callbacks: Callbacks::new(),
924        }
925    }
926
927    #[inline]
928    #[track_caller]
929    fn _event<T, F>(callbacks: &mut Callbacks, element: &EventTarget, options: &EventOptions, listener: F)
930        where T: StaticEvent,
931              F: FnMut(T) + 'static {
932        callbacks.after_remove(on(element, options, listener));
933    }
934
935    // TODO add this to the StylesheetBuilder and ClassBuilder too
936    #[inline]
937    #[track_caller]
938    pub fn global_event_with_options<T, F>(mut self, options: &EventOptions, listener: F) -> Self
939        where T: StaticEvent,
940              F: FnMut(T) + 'static {
941        WINDOW.with(|window| {
942            Self::_event(&mut self.callbacks, window, options, listener);
943        });
944        self
945    }
946
947    // TODO add this to the StylesheetBuilder and ClassBuilder too
948    #[inline]
949    #[track_caller]
950    pub fn global_event<T, F>(self, listener: F) -> Self
951        where T: StaticEvent,
952              F: FnMut(T) + 'static {
953        self.global_event_with_options(&T::default_options(false), listener)
954    }
955
956    #[deprecated(since = "0.5.21", note = "Use global_event_with_options instead")]
957    #[inline]
958    #[track_caller]
959    pub fn global_event_preventable<T, F>(self, listener: F) -> Self
960        where T: StaticEvent,
961              F: FnMut(T) + 'static {
962        self.global_event_with_options(&T::default_options(true), listener)
963    }
964
965    #[inline]
966    pub fn future<F>(mut self, future: F) -> Self where F: Future<Output = ()> + 'static {
967        self.callbacks.after_remove(DiscardOnDrop::leak(spawn_future(future)));
968        self
969    }
970
971
972    // TODO experiment with giving the closure &Self instead, to make it impossible to return a different element
973    #[inline]
974    pub fn apply<F>(self, f: F) -> Self where F: FnOnce(Self) -> Self {
975        f(self)
976    }
977
978    #[inline]
979    pub fn apply_if<F>(self, test: bool, f: F) -> Self where F: FnOnce(Self) -> Self {
980        if test {
981            f(self)
982
983        } else {
984            self
985        }
986    }
987}
988
989impl<A> DomBuilder<A> where A: Clone {
990    #[inline]
991    #[doc(hidden)]
992    pub fn __internal_element(&self) -> A {
993        self.element.clone()
994    }
995
996    #[deprecated(since = "0.5.1", note = "Use the with_node macro instead")]
997    #[inline]
998    pub fn with_element<B, F>(self, f: F) -> B where F: FnOnce(Self, A) -> B {
999        let element = self.element.clone();
1000        f(self, element)
1001    }
1002
1003    #[deprecated(since = "0.5.20", note = "Use the with_node macro instead")]
1004    #[inline]
1005    pub fn before_inserted<F>(self, f: F) -> Self where F: FnOnce(A) {
1006        let element = self.element.clone();
1007        f(element);
1008        self
1009    }
1010}
1011
1012impl<A> DomBuilder<A> where A: Clone + 'static {
1013    #[inline]
1014    pub fn after_inserted<F>(mut self, f: F) -> Self where F: FnOnce(A) + 'static {
1015        let element = self.element.clone();
1016        self.callbacks.after_insert(move |_| f(element));
1017        self
1018    }
1019
1020    #[inline]
1021    pub fn after_removed<F>(mut self, f: F) -> Self where F: FnOnce(A) + 'static {
1022        let element = self.element.clone();
1023        self.callbacks.after_remove(FnDiscard::new(move || f(element)));
1024        self
1025    }
1026}
1027
1028impl<A> DomBuilder<A> where A: Into<Node> {
1029    #[inline]
1030    pub fn into_dom(self) -> Dom {
1031        Dom {
1032            element: self.element.into(),
1033            callbacks: self.callbacks,
1034        }
1035    }
1036}
1037
1038impl<A> DomBuilder<A> where A: AsRef<JsValue> {
1039    #[inline]
1040    #[track_caller]
1041    pub fn prop<B, C>(self, name: B, value: C) -> Self where B: MultiStr, C: Into<JsValue> {
1042        set_property(&self.element, &name, value);
1043        self
1044    }
1045
1046    #[deprecated(since = "0.5.24", note = "Use the `prop` method instead")]
1047    #[inline]
1048    #[track_caller]
1049    pub fn property<B, C>(self, name: B, value: C) -> Self where B: MultiStr, C: Into<JsValue> {
1050        self.prop(name, value)
1051    }
1052}
1053
1054impl<A> DomBuilder<A> where A: AsRef<JsValue> {
1055    // TODO should this inline ?
1056    // TODO track_caller
1057    fn set_property_signal<B, C, D>(&mut self, name: B, value: D)
1058        where B: MultiStr + 'static,
1059              C: Into<JsValue>,
1060              D: Signal<Item = C> + 'static {
1061
1062        let element = self.element.as_ref().clone();
1063
1064        self.callbacks.after_remove(for_each(value, move |value| {
1065            set_property(&element, &name, value);
1066        }));
1067    }
1068
1069    #[inline]
1070    #[track_caller]
1071    pub fn prop_signal<B, C, D>(mut self, name: B, value: D) -> Self
1072        where B: MultiStr + 'static,
1073              C: Into<JsValue>,
1074              D: Signal<Item = C> + 'static {
1075
1076        self.set_property_signal(name, value);
1077        self
1078    }
1079
1080    #[deprecated(since = "0.5.24", note = "Use the `prop_signal` method instead")]
1081    #[inline]
1082    #[track_caller]
1083    pub fn property_signal<B, C, D>(self, name: B, value: D) -> Self
1084        where B: MultiStr + 'static,
1085              C: Into<JsValue>,
1086              D: Signal<Item = C> + 'static {
1087
1088        self.prop_signal(name, value)
1089    }
1090}
1091
1092impl<A> DomBuilder<A> where A: AsRef<EventTarget> {
1093    #[inline]
1094    #[track_caller]
1095    pub fn event_with_options<T, F>(mut self, options: &EventOptions, listener: F) -> Self
1096        where T: StaticEvent,
1097              F: FnMut(T) + 'static {
1098        Self::_event(&mut self.callbacks, &self.element.as_ref(), options, listener);
1099        self
1100    }
1101
1102    #[inline]
1103    #[track_caller]
1104    pub fn event<T, F>(self, listener: F) -> Self
1105        where T: StaticEvent,
1106              F: FnMut(T) + 'static {
1107        self.event_with_options(&T::default_options(false), listener)
1108    }
1109
1110    #[deprecated(since = "0.5.21", note = "Use event_with_options instead")]
1111    #[inline]
1112    #[track_caller]
1113    pub fn event_preventable<T, F>(self, listener: F) -> Self
1114        where T: StaticEvent,
1115              F: FnMut(T) + 'static {
1116        self.event_with_options(&T::default_options(true), listener)
1117    }
1118}
1119
1120impl<A> DomBuilder<A> where A: AsRef<Node> {
1121    /// Inserts the [`Fragment`] into this [`DomBuilder`].
1122    ///
1123    /// See the documentation for [`fragment!`] for more details.
1124    #[inline]
1125    #[track_caller]
1126    pub fn fragment<F>(self, fragment: &F) -> Self where F: Fragment {
1127        let FragmentBuilder(DomBuilder { callbacks, .. }) = {
1128            let element: &Node = self.element.as_ref();
1129
1130            fragment.apply(FragmentBuilder(DomBuilder {
1131                element,
1132                callbacks: self.callbacks,
1133            }))
1134        };
1135
1136        Self {
1137            element: self.element,
1138            callbacks,
1139        }
1140    }
1141
1142    #[inline]
1143    #[track_caller]
1144    pub fn text(self, value: &str) -> Self {
1145        // TODO should this intern ?
1146        bindings::append_child(self.element.as_ref(), &bindings::create_text_node(value));
1147        self
1148    }
1149
1150    #[inline]
1151    #[track_caller]
1152    pub fn text_signal<B, C>(mut self, value: C) -> Self
1153        where B: AsStr,
1154              C: Signal<Item = B> + 'static {
1155
1156        let element = make_text_signal(&mut self.callbacks, value);
1157        bindings::append_child(self.element.as_ref(), &element);
1158        self
1159    }
1160
1161    #[inline]
1162    #[track_caller]
1163    pub fn child<B: BorrowMut<Dom>>(mut self, mut child: B) -> Self {
1164        operations::insert_children_one(self.element.as_ref(), &mut self.callbacks, child.borrow_mut());
1165        self
1166    }
1167
1168    #[inline]
1169    #[track_caller]
1170    pub fn child_signal<B>(mut self, child: B) -> Self
1171        where B: Signal<Item = Option<Dom>> + 'static {
1172
1173        operations::insert_child_signal(self.element.as_ref().clone(), &mut self.callbacks, child);
1174        self
1175    }
1176
1177    // TODO figure out how to make this owned rather than &mut
1178    #[inline]
1179    #[track_caller]
1180    pub fn children<B: BorrowMut<Dom>, C: IntoIterator<Item = B>>(mut self, children: C) -> Self {
1181        operations::insert_children_iter(self.element.as_ref(), &mut self.callbacks, children);
1182        self
1183    }
1184
1185    #[inline]
1186    #[track_caller]
1187    pub fn children_signal_vec<B>(mut self, children: B) -> Self
1188        where B: SignalVec<Item = Dom> + 'static {
1189
1190        operations::insert_children_signal_vec(self.element.as_ref().clone(), &mut self.callbacks, children);
1191        self
1192    }
1193}
1194
1195impl<A> DomBuilder<A> where A: AsRef<Element> {
1196    #[inline]
1197    #[doc(hidden)]
1198    #[track_caller]
1199    pub fn __internal_shadow_root(&self, mode: ShadowRootMode) -> DomBuilder<ShadowRoot> {
1200        let shadow = self.element.as_ref().attach_shadow(&ShadowRootInit::new(mode)).unwrap_js();
1201        DomBuilder::new(shadow)
1202    }
1203
1204    #[inline]
1205    #[track_caller]
1206    pub fn attr<B>(self, name: B, value: &str) -> Self where B: MultiStr {
1207        let element = self.element.as_ref();
1208
1209        name.each(|name| {
1210            bindings::set_attribute(element, intern(name), &value);
1211        });
1212
1213        self
1214    }
1215
1216    #[deprecated(since = "0.5.24", note = "Use the `attr` method instead")]
1217    #[inline]
1218    #[track_caller]
1219    pub fn attribute<B>(self, name: B, value: &str) -> Self where B: MultiStr {
1220        self.attr(name, value)
1221    }
1222
1223    #[inline]
1224    #[track_caller]
1225    pub fn attr_ns<B>(self, namespace: &str, name: B, value: &str) -> Self where B: MultiStr {
1226        let element = self.element.as_ref();
1227        let namespace: &str = intern(namespace);
1228
1229        name.each(|name| {
1230            bindings::set_attribute_ns(element, &namespace, intern(name), &value);
1231        });
1232
1233        self
1234    }
1235
1236    #[deprecated(since = "0.5.24", note = "Use the `attr_ns` method instead")]
1237    #[inline]
1238    #[track_caller]
1239    pub fn attribute_namespace<B>(self, namespace: &str, name: B, value: &str) -> Self where B: MultiStr {
1240        self.attr_ns(namespace, name, value)
1241    }
1242
1243    #[inline]
1244    #[track_caller]
1245    pub fn class<B>(self, name: B) -> Self where B: MultiStr {
1246        let classes = self.element.as_ref().class_list();
1247
1248        name.each(|name| {
1249            bindings::add_class(&classes, intern(name));
1250        });
1251
1252        self
1253    }
1254
1255    // TODO make this more efficient ?
1256    #[inline]
1257    #[track_caller]
1258    pub fn visible(self, value: bool) -> Self {
1259        if value {
1260            // TODO remove the class somehow ?
1261            self
1262
1263        } else {
1264            self.class(&*HIDDEN_CLASS)
1265        }
1266    }
1267}
1268
1269impl<A> DomBuilder<A> where A: AsRef<Element> {
1270    // TODO should this inline ?
1271    // TODO track_caller
1272    fn set_attribute_signal<B, C, D, E>(&mut self, name: B, value: E)
1273        where B: MultiStr + 'static,
1274              C: AsStr,
1275              D: OptionStr<Output = C>,
1276              E: Signal<Item = D> + 'static {
1277
1278        set_option(self.element.as_ref().clone(), &mut self.callbacks, value, move |element, value| {
1279            match value {
1280                Some(value) => {
1281                    value.with_str(|value| {
1282                        name.each(|name| {
1283                            bindings::set_attribute(element, intern(name), &value);
1284                        });
1285                    });
1286                },
1287                None => {
1288                    name.each(|name| {
1289                        bindings::remove_attribute(element, intern(name));
1290                    });
1291                },
1292            }
1293        });
1294    }
1295
1296    #[inline]
1297    #[track_caller]
1298    pub fn attr_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1299        where B: MultiStr + 'static,
1300              C: AsStr,
1301              D: OptionStr<Output = C>,
1302              E: Signal<Item = D> + 'static {
1303
1304        self.set_attribute_signal(name, value);
1305        self
1306    }
1307
1308    #[deprecated(since = "0.5.24", note = "Use the `attr_signal` method instead")]
1309    #[inline]
1310    #[track_caller]
1311    pub fn attribute_signal<B, C, D, E>(self, name: B, value: E) -> Self
1312        where B: MultiStr + 'static,
1313              C: AsStr,
1314              D: OptionStr<Output = C>,
1315              E: Signal<Item = D> + 'static {
1316
1317        self.attr_signal(name, value)
1318    }
1319
1320
1321    // TODO should this inline ?
1322    // TODO track_caller
1323    fn set_attribute_namespace_signal<B, C, D, E>(&mut self, namespace: &str, name: B, value: E)
1324        where B: MultiStr + 'static,
1325              C: AsStr,
1326              D: OptionStr<Output = C>,
1327              E: Signal<Item = D> + 'static {
1328
1329        // TODO avoid this to_owned by using Into<Cow<'static str>>
1330        let namespace: String = intern(namespace).to_owned();
1331
1332        set_option(self.element.as_ref().clone(), &mut self.callbacks, value, move |element, value| {
1333            match value {
1334                Some(value) => {
1335                    value.with_str(|value| {
1336                        name.each(|name| {
1337                            // TODO should this intern the value ?
1338                            bindings::set_attribute_ns(element, &namespace, intern(name), &value);
1339                        });
1340                    });
1341                },
1342                None => {
1343                    name.each(|name| {
1344                        bindings::remove_attribute_ns(element, &namespace, intern(name));
1345                    });
1346                },
1347            }
1348        });
1349    }
1350
1351    #[inline]
1352    #[track_caller]
1353    pub fn attr_ns_signal<B, C, D, E>(mut self, namespace: &str, name: B, value: E) -> Self
1354        where B: MultiStr + 'static,
1355              C: AsStr,
1356              D: OptionStr<Output = C>,
1357              E: Signal<Item = D> + 'static {
1358
1359        self.set_attribute_namespace_signal(namespace, name, value);
1360        self
1361    }
1362
1363    #[deprecated(since = "0.5.24", note = "Use the `attr_ns_signal` method instead")]
1364    #[inline]
1365    #[track_caller]
1366    pub fn attribute_namespace_signal<B, C, D, E>(self, namespace: &str, name: B, value: E) -> Self
1367        where B: MultiStr + 'static,
1368              C: AsStr,
1369              D: OptionStr<Output = C>,
1370              E: Signal<Item = D> + 'static {
1371
1372        self.attr_ns_signal(namespace, name, value)
1373    }
1374
1375
1376    // TODO should this inline ?
1377    // TODO track_caller
1378    fn set_class_signal<B, C>(&mut self, name: B, value: C)
1379        where B: MultiStr + 'static,
1380              C: Signal<Item = bool> + 'static {
1381
1382        let element = self.element.as_ref().class_list();
1383
1384        let mut is_set = false;
1385
1386        self.callbacks.after_remove(for_each(value, move |value| {
1387            if value {
1388                if !is_set {
1389                    is_set = true;
1390
1391                    name.each(|name| {
1392                        bindings::add_class(&element, intern(name));
1393                    });
1394                }
1395
1396            } else {
1397                if is_set {
1398                    is_set = false;
1399
1400                    name.each(|name| {
1401                        bindings::remove_class(&element, intern(name));
1402                    });
1403                }
1404            }
1405        }));
1406    }
1407
1408    #[inline]
1409    #[track_caller]
1410    pub fn class_signal<B, C>(mut self, name: B, value: C) -> Self
1411        where B: MultiStr + 'static,
1412              C: Signal<Item = bool> + 'static {
1413
1414        self.set_class_signal(name, value);
1415        self
1416    }
1417
1418    // TODO make this more efficient ?
1419    #[inline]
1420    #[track_caller]
1421    pub fn visible_signal<B>(self, value: B) -> Self where B: Signal<Item = bool> + 'static {
1422        self.class_signal(&*HIDDEN_CLASS, not(value))
1423    }
1424
1425
1426    // TODO use OptionStr ?
1427    // TODO should this inline ?
1428    // TODO track_caller
1429    fn set_scroll_signal<B, F>(&mut self, signal: B, mut f: F)
1430        where B: Signal<Item = Option<i32>> + 'static,
1431              F: FnMut(&Element, i32) + 'static {
1432
1433        let element: Element = self.element.as_ref().clone();
1434
1435        // This needs to use `after_insert` because scrolling an element before it is in the DOM has no effect
1436        self.callbacks.after_insert(move |callbacks| {
1437            callbacks.after_remove(for_each(signal, move |value| {
1438                if let Some(value) = value {
1439                    f(&element, value);
1440                }
1441            }));
1442        });
1443    }
1444
1445    // TODO rename to scroll_x_signal ?
1446    #[inline]
1447    #[track_caller]
1448    pub fn scroll_left_signal<B>(mut self, signal: B) -> Self where B: Signal<Item = Option<i32>> + 'static {
1449        // TODO bindings function for this ?
1450        self.set_scroll_signal(signal, Element::set_scroll_left);
1451        self
1452    }
1453
1454    // TODO rename to scroll_y_signal ?
1455    #[inline]
1456    #[track_caller]
1457    pub fn scroll_top_signal<B>(mut self, signal: B) -> Self where B: Signal<Item = Option<i32>> + 'static {
1458        // TODO bindings function for this ?
1459        self.set_scroll_signal(signal, Element::set_scroll_top);
1460        self
1461    }
1462
1463
1464    // TODO should this inline ?
1465    // TODO track_caller
1466    fn set_scroll_into_view_signal<B>(&mut self, signal: B)
1467        where B: Signal<Item = Option<ScrollIntoView>> + 'static {
1468
1469        let element: Element = self.element.as_ref().clone();
1470
1471        // This needs to use `after_insert` because scrolling an element before it is in the DOM has no effect
1472        self.callbacks.after_insert(move |callbacks| {
1473            callbacks.after_remove(for_each(signal, move |options| {
1474                if let Some(options) = options {
1475                    element.scroll_into_view_with_scroll_into_view_options(&options.into_js());
1476                }
1477            }));
1478        });
1479    }
1480
1481    /// Scrolls the document to ensure that the element is in view.
1482    ///
1483    /// If the Signal returns `None` then it doesn't do any scrolling.
1484    ///
1485    /// If the Signal returns `Some` then the [`ScrollIntoView`] describes how the scrolling happens.
1486    #[inline]
1487    #[track_caller]
1488    pub fn scroll_into_view_signal<B>(mut self, signal: B) -> Self where B: Signal<Item = Option<ScrollIntoView>> + 'static {
1489        self.set_scroll_into_view_signal(signal);
1490        self
1491    }
1492}
1493
1494impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
1495    #[inline]
1496    #[track_caller]
1497    pub fn style<B, C>(self, name: B, value: C) -> Self
1498        where B: MultiStr,
1499              C: MultiStr {
1500        set_style(&self.element.as_ref().style(), &name, value, false);
1501        self
1502    }
1503
1504    #[inline]
1505    #[track_caller]
1506    pub fn style_important<B, C>(self, name: B, value: C) -> Self
1507        where B: MultiStr,
1508              C: MultiStr {
1509        set_style(&self.element.as_ref().style(), &name, value, true);
1510        self
1511    }
1512
1513    #[inline]
1514    #[track_caller]
1515    pub fn style_unchecked<B, C>(self, name: B, value: C) -> Self
1516        where B: AsStr,
1517              C: AsStr {
1518        name.with_str(|name| {
1519            value.with_str(|value| {
1520                bindings::set_style(&self.element.as_ref().style(), intern(name), value, false);
1521            });
1522        });
1523        self
1524    }
1525}
1526
1527impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
1528    #[inline]
1529    #[track_caller]
1530    pub fn style_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1531        where B: MultiStr + 'static,
1532              C: MultiStr,
1533              D: OptionStr<Output = C>,
1534              E: Signal<Item = D> + 'static {
1535
1536        set_style_signal(self.element.as_ref().style(), &mut self.callbacks, name, value, false);
1537        self
1538    }
1539
1540    #[inline]
1541    #[track_caller]
1542    pub fn style_important_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1543        where B: MultiStr + 'static,
1544              C: MultiStr,
1545              D: OptionStr<Output = C>,
1546              E: Signal<Item = D> + 'static {
1547
1548        set_style_signal(self.element.as_ref().style(), &mut self.callbacks, name, value, true);
1549        self
1550    }
1551
1552    #[inline]
1553    #[track_caller]
1554    pub fn style_unchecked_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1555        where B: AsStr + 'static,
1556              C: AsStr,
1557              D: OptionStr<Output = C>,
1558              E: Signal<Item = D> + 'static {
1559
1560        set_style_unchecked_signal(self.element.as_ref().style(), &mut self.callbacks, name, value, false);
1561        self
1562    }
1563
1564
1565    // TODO remove the `value` argument ?
1566    #[inline]
1567    #[track_caller]
1568    pub fn focused(mut self, value: bool) -> Self {
1569        let element = self.element.as_ref().clone();
1570
1571        // This needs to use `after_insert` because calling `.focus()` on an element before it is in the DOM has no effect
1572        self.callbacks.after_insert(move |_| {
1573            // TODO avoid updating if the focused state hasn't changed ?
1574            if value {
1575                bindings::focus(&element);
1576
1577            } else {
1578                bindings::blur(&element);
1579            }
1580        });
1581
1582        self
1583    }
1584
1585
1586    // TODO should this inline ?
1587    // TODO track_caller
1588    fn set_focused_signal<B>(&mut self, value: B)
1589        where B: Signal<Item = bool> + 'static {
1590
1591        let element = self.element.as_ref().clone();
1592
1593        // This needs to use `after_insert` because calling `.focus()` on an element before it is in the DOM has no effect
1594        self.callbacks.after_insert(move |callbacks| {
1595            // TODO verify that this is correct under all circumstances
1596            callbacks.after_remove(for_each(value, move |value| {
1597                // TODO avoid updating if the focused state hasn't changed ?
1598                if value {
1599                    bindings::focus(&element);
1600
1601                } else {
1602                    bindings::blur(&element);
1603                }
1604            }));
1605        });
1606    }
1607
1608    #[inline]
1609    #[track_caller]
1610    pub fn focused_signal<B>(mut self, value: B) -> Self
1611        where B: Signal<Item = bool> + 'static {
1612
1613        self.set_focused_signal(value);
1614        self
1615    }
1616}
1617
1618
1619/// Creates a raw global CSS stylesheet.
1620///
1621/// This accepts a single argument, which is a string containing CSS code.
1622///
1623/// The CSS is injected into the global scope, exactly the same as if you had
1624/// imported a `.css` file.
1625///
1626/// It is recommended to use [`stylesheet!`] instead, because it has some additional
1627/// features and safety checking.
1628///
1629/// But if you need to copy some existing CSS into dominator, then you can use
1630/// `stylesheet_raw`.
1631///
1632/// # Example
1633///
1634/// ```rust
1635/// stylesheet_raw(r#"
1636///     div.foo > span:nth-child(5):hover {
1637///         color: green;
1638///         background-color: blue;
1639///     }
1640///
1641///     @media only screen and (max-width: 500px) {
1642///         .some-class {
1643///             background-color: red;
1644///         }
1645///     }
1646/// "#);
1647/// ```
1648#[inline]
1649pub fn stylesheet_raw<A>(css: A) where A: AsStr {
1650    css.with_str(|css| {
1651        bindings::create_stylesheet(Some(css));
1652    });
1653}
1654
1655
1656// TODO better warning message for must_use
1657#[must_use]
1658pub struct StylesheetBuilder {
1659    element: CssStyleDeclaration,
1660    callbacks: Callbacks,
1661}
1662
1663// TODO remove the CssStyleRule when this is discarded
1664impl StylesheetBuilder {
1665    fn __internal_rules<A>(rules: &A) -> CssRule where A: MultiStr {
1666        // TODO can this be made faster ?
1667        // TODO somehow share this safely between threads ?
1668        thread_local! {
1669            static STYLESHEET: CssStyleSheet = bindings::create_stylesheet(None);
1670        }
1671
1672        STYLESHEET.with(move |stylesheet| {
1673            let mut failed = vec![];
1674
1675            let okay = rules.find_map(|rule| {
1676                // TODO maybe intern the rule ?
1677                if let Ok(declaration) = bindings::make_rule(stylesheet, rule) {
1678                    Some(declaration)
1679
1680                } else {
1681                    failed.push(String::from(rule));
1682                    None
1683                }
1684            });
1685
1686            if let Some(okay) = okay {
1687                okay
1688
1689            } else {
1690                // TODO maybe make this configurable
1691                panic!("selectors are incorrect:\n  {}", failed.join("\n  "));
1692            }
1693        })
1694    }
1695
1696    #[doc(hidden)]
1697    #[inline]
1698    pub fn __internal_stylesheet<A>(rules: A) -> Self where A: MultiStr {
1699        let element = Self::__internal_rules(&rules).unchecked_into::<CssStyleRule>();
1700
1701        Self {
1702            element: element.style(),
1703            callbacks: Callbacks::new(),
1704        }
1705    }
1706
1707    #[doc(hidden)]
1708    #[inline]
1709    pub fn __internal_new<A>(rules: A) -> Self where A: MultiStr {
1710        Self::__internal_stylesheet(MapMultiStr::new(rules, |rule| format!("{} {{}}", rule)))
1711    }
1712
1713    #[inline]
1714    #[track_caller]
1715    pub fn style<B, C>(self, name: B, value: C) -> Self
1716        where B: MultiStr,
1717              C: MultiStr {
1718        set_style(&self.element, &name, value, false);
1719        self
1720    }
1721
1722    #[inline]
1723    #[track_caller]
1724    pub fn style_important<B, C>(self, name: B, value: C) -> Self
1725        where B: MultiStr,
1726              C: MultiStr {
1727        set_style(&self.element, &name, value, true);
1728        self
1729    }
1730
1731    #[inline]
1732    #[track_caller]
1733    pub fn style_unchecked<B, C>(self, name: B, value: C) -> Self
1734        where B: AsStr,
1735              C: AsStr {
1736        name.with_str(|name| {
1737            value.with_str(|value| {
1738                bindings::set_style(&self.element, intern(name), value, false);
1739            });
1740        });
1741        self
1742    }
1743
1744    #[inline]
1745    #[track_caller]
1746    pub fn style_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1747        where B: MultiStr + 'static,
1748              C: MultiStr,
1749              D: OptionStr<Output = C>,
1750              E: Signal<Item = D> + 'static {
1751
1752        set_style_signal(self.element.clone(), &mut self.callbacks, name, value, false);
1753        self
1754    }
1755
1756    #[inline]
1757    #[track_caller]
1758    pub fn style_important_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1759        where B: MultiStr + 'static,
1760              C: MultiStr,
1761              D: OptionStr<Output = C>,
1762              E: Signal<Item = D> + 'static {
1763
1764        set_style_signal(self.element.clone(), &mut self.callbacks, name, value, true);
1765        self
1766    }
1767
1768    #[inline]
1769    #[track_caller]
1770    pub fn style_unchecked_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1771        where B: AsStr + 'static,
1772              C: AsStr,
1773              D: OptionStr<Output = C>,
1774              E: Signal<Item = D> + 'static {
1775
1776        set_style_unchecked_signal(self.element.clone(), &mut self.callbacks, name, value, false);
1777        self
1778    }
1779
1780    /// Appends raw CSS code into the stylesheet.
1781    ///
1782    /// It is recommended to use the various `.style` methods instead.
1783    ///
1784    /// However, `.raw` is useful if you need to copy some existing CSS
1785    /// into dominator.
1786    ///
1787    /// # Example
1788    ///
1789    /// ```rust
1790    /// stylesheet!(".foo", {
1791    ///     .raw(r#"
1792    ///         background-color: green;
1793    ///         width: 50px;
1794    ///         position: absolute;
1795    ///     "#)
1796    /// })
1797    /// ```
1798    #[inline]
1799    #[track_caller]
1800    pub fn raw<B>(self, css: B) -> Self where B: AsStr {
1801        css.with_str(|css| {
1802            bindings::append_raw(&self.element, css);
1803        });
1804
1805        self
1806    }
1807
1808    // TODO return a Handle
1809    #[inline]
1810    #[track_caller]
1811    #[doc(hidden)]
1812    pub fn __internal_done(mut self) {
1813        self.callbacks.trigger_after_insert();
1814
1815        // This prevents it from triggering after_remove
1816        self.callbacks.leak();
1817    }
1818}
1819
1820
1821// TODO better warning message for must_use
1822#[must_use]
1823pub struct ClassBuilder {
1824    stylesheet: StylesheetBuilder,
1825    class_name: String,
1826}
1827
1828impl ClassBuilder {
1829    #[doc(hidden)]
1830    #[inline]
1831    #[track_caller]
1832    pub fn __internal_new(name: Option<&str>) -> Self {
1833        let class_name = __internal::make_class_id(name);
1834
1835        Self {
1836            // TODO make this more efficient ?
1837            stylesheet: StylesheetBuilder::__internal_stylesheet(&format!(".{} {{}}", class_name)),
1838            class_name,
1839        }
1840    }
1841
1842    #[doc(hidden)]
1843    #[inline]
1844    #[track_caller]
1845    pub fn __internal_class_name(&self) -> &str {
1846        &self.class_name
1847    }
1848
1849    #[inline]
1850    #[track_caller]
1851    pub fn style<B, C>(mut self, name: B, value: C) -> Self
1852        where B: MultiStr,
1853              C: MultiStr {
1854        self.stylesheet = self.stylesheet.style(name, value);
1855        self
1856    }
1857
1858    #[inline]
1859    #[track_caller]
1860    pub fn style_important<B, C>(mut self, name: B, value: C) -> Self
1861        where B: MultiStr,
1862              C: MultiStr {
1863        self.stylesheet = self.stylesheet.style_important(name, value);
1864        self
1865    }
1866
1867    #[inline]
1868    #[track_caller]
1869    pub fn style_unchecked<B, C>(mut self, name: B, value: C) -> Self
1870        where B: AsStr,
1871              C: AsStr {
1872        self.stylesheet = self.stylesheet.style_unchecked(name, value);
1873        self
1874    }
1875
1876    #[inline]
1877    #[track_caller]
1878    pub fn style_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1879        where B: MultiStr + 'static,
1880              C: MultiStr,
1881              D: OptionStr<Output = C>,
1882              E: Signal<Item = D> + 'static {
1883
1884        self.stylesheet = self.stylesheet.style_signal(name, value);
1885        self
1886    }
1887
1888    #[inline]
1889    #[track_caller]
1890    pub fn style_important_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1891        where B: MultiStr + 'static,
1892              C: MultiStr,
1893              D: OptionStr<Output = C>,
1894              E: Signal<Item = D> + 'static {
1895
1896        self.stylesheet = self.stylesheet.style_important_signal(name, value);
1897        self
1898    }
1899
1900    #[inline]
1901    #[track_caller]
1902    pub fn style_unchecked_signal<B, C, D, E>(mut self, name: B, value: E) -> Self
1903        where B: AsStr + 'static,
1904              C: AsStr,
1905              D: OptionStr<Output = C>,
1906              E: Signal<Item = D> + 'static {
1907
1908        self.stylesheet = self.stylesheet.style_unchecked_signal(name, value);
1909        self
1910    }
1911
1912    /// Appends raw CSS code into the class.
1913    ///
1914    /// It is recommended to use the various `.style` methods instead.
1915    ///
1916    /// However, `.raw` is useful if you need to copy some existing CSS
1917    /// into dominator.
1918    ///
1919    /// # Example
1920    ///
1921    /// ```rust
1922    /// class! {
1923    ///     .raw(r#"
1924    ///         background-color: green;
1925    ///         width: 50px;
1926    ///         position: absolute;
1927    ///     "#)
1928    /// }
1929    /// ```
1930    #[inline]
1931    #[track_caller]
1932    pub fn raw<B>(mut self, css: B) -> Self where B: AsStr {
1933        self.stylesheet = self.stylesheet.raw(css);
1934        self
1935    }
1936
1937    // TODO return a Handle ?
1938    #[doc(hidden)]
1939    #[inline]
1940    #[track_caller]
1941    pub fn __internal_done(self) -> String {
1942        self.stylesheet.__internal_done();
1943        self.class_name
1944    }
1945}
1946
1947
1948#[doc(hidden)]
1949pub mod __internal {
1950    use std::sync::atomic::{AtomicU32, Ordering};
1951    use crate::fragment::{Fragment, FragmentBuilder, BoxFragment};
1952    use crate::traits::MultiStr;
1953
1954
1955    pub use web_sys::HtmlElement;
1956    pub use web_sys::SvgElement;
1957
1958
1959    pub fn make_class_id(name: Option<&str>) -> String {
1960        // TODO replace this with a global counter in JavaScript ?
1961        // TODO can this be made more efficient ?
1962        static CLASS_ID: AtomicU32 = AtomicU32::new(0);
1963
1964        // TODO check for overflow ?
1965        // TODO should this be SeqCst ?
1966        let id = CLASS_ID.fetch_add(1, Ordering::Relaxed);
1967
1968        let name = name.unwrap_or("__class_");
1969        // TODO make this more efficient ?
1970        format!("{}_{}", name, id)
1971    }
1972
1973
1974    pub struct Pseudo<'a, A> {
1975        class_name: &'a str,
1976        pseudos: A,
1977    }
1978
1979    impl<'a, A> Pseudo<'a, A> where A: MultiStr {
1980        #[inline]
1981        pub fn new(class_name: &'a str, pseudos: A) -> Self {
1982            Self { class_name, pseudos }
1983        }
1984    }
1985
1986    impl<'a, A> MultiStr for Pseudo<'a, A> where A: MultiStr {
1987        #[inline]
1988        fn find_map<B, F>(&self, mut f: F) -> Option<B> where F: FnMut(&str) -> Option<B> {
1989            self.pseudos.find_map(|x| {
1990                f(&format!(".{}{}", self.class_name, x))
1991            })
1992        }
1993    }
1994
1995
1996    #[derive(Debug)]
1997    struct FnFragment<F>(F);
1998
1999    impl<F> Fragment for FnFragment<F> where F: Fn(FragmentBuilder<'_>) -> FragmentBuilder<'_> {
2000        #[inline]
2001        fn apply<'a>(&self, dom: FragmentBuilder<'a>) -> FragmentBuilder<'a> {
2002            (self.0)(dom)
2003        }
2004    }
2005
2006    #[inline]
2007    pub fn fragment<F>(f: F) -> impl Fragment where F: Fn(FragmentBuilder<'_>) -> FragmentBuilder<'_> {
2008        FnFragment(f)
2009    }
2010
2011    #[inline]
2012    pub fn box_fragment<F>(f: F) -> BoxFragment where F: Fn(FragmentBuilder<'_>) -> FragmentBuilder<'_> + Send + Sync + 'static {
2013        Box::new(FnFragment(f))
2014    }
2015}
2016
2017
2018#[cfg(test)]
2019mod tests {
2020    use super::{DomBuilder, text_signal, RefFn};
2021    use crate::{html, shadow_root, ShadowRootMode, with_cfg};
2022    use futures_signals::signal::{always, SignalExt};
2023    use once_cell::sync::Lazy;
2024    use web_sys::HtmlElement;
2025
2026    #[test]
2027    fn apply() {
2028        let a: DomBuilder<HtmlElement> = DomBuilder::new_html("div");
2029
2030        fn my_mixin<A: AsRef<HtmlElement>>(builder: DomBuilder<A>) -> DomBuilder<A> {
2031            builder.style("foo", "bar")
2032        }
2033
2034        let _ = a.apply(my_mixin);
2035    }
2036
2037    #[test]
2038    fn children_mut() {
2039        let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
2040            .children(&mut [
2041                DomBuilder::<HtmlElement>::new_html("div").into_dom(),
2042                DomBuilder::<HtmlElement>::new_html("div").into_dom(),
2043                DomBuilder::<HtmlElement>::new_html("div").into_dom(),
2044            ]);
2045    }
2046
2047    #[test]
2048    fn children_value() {
2049        let v: Vec<u32> = vec![];
2050
2051        let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
2052            .children(v.iter().map(|_| {
2053                DomBuilder::<HtmlElement>::new_html("div").into_dom()
2054            }));
2055    }
2056
2057    #[test]
2058    fn text_signal_types() {
2059        let _ = text_signal(always("foo"));
2060        let _ = text_signal(always("foo".to_owned()));
2061        let _ = text_signal(always("foo".to_owned()).map(|x| RefFn::new(x, |x| x.as_str())));
2062        //text_signal(always(Arc::new("foo")));
2063        //text_signal(always(Arc::new("foo".to_owned())));
2064        //text_signal(always(Rc::new("foo")));
2065        //text_signal(always(Rc::new("foo".to_owned())));
2066        //text_signal(always(Box::new("foo")));
2067        //text_signal(always(Box::new("foo".to_owned())));
2068        //text_signal(always(Cow::Borrowed(&"foo")));
2069        //text_signal(always(Cow::Owned::<String>("foo".to_owned())));
2070    }
2071
2072    #[test]
2073    fn property_signal_types() {
2074        let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
2075            .prop("foo", "hi")
2076            .prop("foo", 5)
2077            .prop(["foo", "-webkit-foo", "-ms-foo"], "hi")
2078
2079            .prop_signal("foo", always("hi"))
2080            .prop_signal("foo", always(5))
2081            .prop_signal("foo", always(Some("hi")))
2082
2083            .prop_signal(["foo", "-webkit-foo", "-ms-foo"], always("hi"))
2084            .prop_signal(["foo", "-webkit-foo", "-ms-foo"], always(5))
2085            .prop_signal(["foo", "-webkit-foo", "-ms-foo"], always(Some("hi")))
2086            ;
2087    }
2088
2089    #[test]
2090    fn attribute_signal_types() {
2091        let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
2092            .attr("foo", "hi")
2093            .attr(["foo", "-webkit-foo", "-ms-foo"], "hi")
2094
2095            .attr_signal("foo", always("hi"))
2096            .attr_signal("foo", always(Some("hi")))
2097
2098            .attr_signal(["foo", "-webkit-foo", "-ms-foo"], always("hi"))
2099            .attr_signal(["foo", "-webkit-foo", "-ms-foo"], always(Some("hi")))
2100            ;
2101    }
2102
2103    #[test]
2104    fn class_signal_types() {
2105        let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
2106            .class("foo")
2107            .class(["foo", "-webkit-foo", "-ms-foo"])
2108
2109            .class_signal("foo", always(true))
2110            .class_signal(["foo", "-webkit-foo", "-ms-foo"], always(true))
2111            ;
2112    }
2113
2114    #[test]
2115    fn style_signal_types() {
2116        static FOO: Lazy<String> = Lazy::new(|| "foo".to_owned());
2117
2118        let _a: DomBuilder<HtmlElement> = DomBuilder::new_html("div")
2119            .style_signal("foo", always("bar"))
2120            .style_signal("foo", always("bar".to_owned()))
2121            .style_signal("foo", always("bar".to_owned()).map(|x| RefFn::new(x, |x| x.as_str())))
2122
2123            .style("foo".to_owned(), "bar".to_owned())
2124            .style_signal("foo".to_owned(), always("bar".to_owned()))
2125
2126            .style(&"foo".to_owned(), &"bar".to_owned())
2127            //.style(Box::new("foo".to_owned()), Box::new("bar".to_owned()))
2128            //.style_signal(Box::new("foo".to_owned()), always(Box::new("bar".to_owned())))
2129
2130            .style_signal(&*FOO, always(&*FOO))
2131
2132            //.style(vec!["-moz-foo", "-webkit-foo", "foo"].as_slice(), vec!["bar"].as_slice())
2133            .style_signal(RefFn::new(vec!["-moz-foo", "-webkit-foo", "foo"], |x| x.as_slice()), always(RefFn::new(vec!["bar"], |x| x.as_slice())))
2134
2135            .style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar"))
2136            .style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar".to_owned()))
2137            .style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar".to_owned()).map(|x| RefFn::new(x, |x| x.as_str())))
2138
2139            .style_signal(["-moz-foo", "-webkit-foo", "foo"], always(["bar", "qux"]))
2140            .style_signal(["-moz-foo", "-webkit-foo", "foo"], always(["bar".to_owned(), "qux".to_owned()]))
2141
2142            //.style_signal(["-moz-foo", "-webkit-foo", "foo"], always(AsSlice::new(["foo", "bar"])))
2143            //.style_signal(["-moz-foo", "-webkit-foo", "foo"], always(("bar".to_owned(), "qux".to_owned())).map(|x| RefFn::new(x, |x| AsSlice::new([x.0.as_str(), x.1.as_str()]))))
2144
2145            .style_signal("foo", always(Some("bar")))
2146            .style_signal("foo", always(Some("bar".to_owned())))
2147            .style_signal("foo", always("bar".to_owned()).map(|x| Some(RefFn::new(x, |x| x.as_str()))))
2148
2149            .style_signal(["-moz-foo", "-webkit-foo", "foo"], always(Some("bar")))
2150            .style_signal(["-moz-foo", "-webkit-foo", "foo"], always(Some("bar".to_owned())))
2151            .style_signal(["-moz-foo", "-webkit-foo", "foo"], always("bar".to_owned()).map(|x| Some(RefFn::new(x, |x| x.as_str()))))
2152            ;
2153    }
2154
2155    #[test]
2156    fn shadow_root() {
2157        let _a = html!("div", {
2158            .shadow_root!(ShadowRootMode::Closed => {
2159                .children(&mut [
2160                    html!("span")
2161                ])
2162            })
2163        });
2164    }
2165
2166    #[test]
2167    fn with_cfg() {
2168        let _a = html!("div", {
2169            .with_cfg!(target_arch = "wasm32", {
2170                .attr("foo", "bar")
2171            })
2172
2173            .with_cfg!(all(not(foo), bar = "test", feature = "hi"), {
2174                .attr("foo", "bar")
2175            })
2176        });
2177    }
2178}