1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]

use crate::use_event_listener;
use cfg_if::cfg_if;
use leptos::ev::change;
use leptos::*;
use std::cell::RefCell;
use std::rc::Rc;

/// Reactive [Media Query](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Testing_media_queries).
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_media_query)
///
/// ## Usage
///
/// ```
/// # use leptos::*;
/// # use leptos_use::use_media_query;
/// #
/// # #[component]
/// # fn Demo() -> impl IntoView {
/// #
/// let is_large_screen = use_media_query("(min-width: 1024px)");
///
/// let is_dark_preferred = use_media_query("(prefers-color-scheme: dark)");
/// #
/// #    view! { }
/// # }
/// ```
///
/// ## Server-Side Rendering
///
/// On the server this functions returns a Signal that is always `false`.
///
/// ## See also
///
/// * [`use_preferred_dark`]
/// * [`use_preferred_contrast`]
pub fn use_media_query(query: impl Into<MaybeSignal<String>>) -> Signal<bool> {
    let query = query.into();

    let (matches, set_matches) = create_signal(false);

    cfg_if! { if #[cfg(not(feature = "ssr"))] {
        let media_query: Rc<RefCell<Option<web_sys::MediaQueryList>>> = Rc::new(RefCell::new(None));
        let remove_listener: RemoveListener = Rc::new(RefCell::new(None));

        let listener = Rc::new(RefCell::new(Rc::new(|_| {}) as Rc<dyn Fn(web_sys::Event)>));

        let cleanup = {
            let remove_listener = Rc::clone(&remove_listener);

            move || {
                if let Some(remove_listener) = remove_listener.take().as_ref() {
                    remove_listener();
                }
            }
        };

        let update = {
            let cleanup = cleanup.clone();
            let listener = Rc::clone(&listener);

            Rc::new(move || {
                cleanup();

                let mut media_query = media_query.borrow_mut();
                *media_query = window().match_media(&query.get()).unwrap_or(None);

                if let Some(media_query) = media_query.as_ref() {
                    set_matches.set(media_query.matches());

                    let listener = Rc::clone(&*listener.borrow());

                    remove_listener.replace(Some(Box::new(use_event_listener(
                        media_query.clone(),
                        change,
                        move |e| listener(e),
                    ))));
                } else {
                    set_matches.set(false);
                }
            })
        };

        {
            let update = Rc::clone(&update);
            listener.replace(Rc::new(move |_| update()) as Rc<dyn Fn(web_sys::Event)>);
        }

        create_effect(move |_| update());

        on_cleanup(cleanup);
    }}

    matches.into()
}

type RemoveListener = Rc<RefCell<Option<Box<dyn Fn()>>>>;