1use blockworx_paint::{Luminance, Mode};
11use wasm_bindgen::{JsCast as _, prelude::Closure};
12use web_sys::{MediaQueryList, Window};
13
14const DARK: &str = "(prefers-color-scheme: dark)";
16
17#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
21pub enum Prefers {
22 Dark,
23 Light,
24 #[default]
25 Unstated,
26}
27
28impl Prefers {
29 #[must_use]
31 pub fn of(window: &Window) -> Self {
32 match window.match_media(DARK) {
33 Ok(Some(query)) => Self::from(&query),
34 _ => Self::Unstated,
35 }
36 }
37
38 fn system_dark(self) -> Option<bool> {
40 match self {
41 Self::Dark => Some(true),
42 Self::Light => Some(false),
43 Self::Unstated => None,
44 }
45 }
46}
47
48impl From<&MediaQueryList> for Prefers {
49 fn from(query: &MediaQueryList) -> Self {
50 if query.matches() {
51 Self::Dark
52 } else {
53 Self::Light
54 }
55 }
56}
57
58pub fn watch(window: &Window, mut told: impl FnMut(Prefers) + 'static) {
65 let Ok(Some(query)) = window.match_media(DARK) else {
66 return;
67 };
68 let heard = Closure::<dyn FnMut(web_sys::MediaQueryListEvent)>::new(
69 move |event: web_sys::MediaQueryListEvent| {
70 told(if event.matches() {
71 Prefers::Dark
72 } else {
73 Prefers::Light
74 });
75 },
76 );
77 if query
78 .add_event_listener_with_callback("change", heard.as_ref().unchecked_ref())
79 .is_ok()
80 {
81 heard.forget();
82 }
83}
84
85#[must_use]
87pub fn luminance(preference: Mode, prefers: Prefers) -> Luminance {
88 if preference.is_dark(prefers.system_dark()) {
89 Luminance::Dark
90 } else {
91 Luminance::Light
92 }
93}
94
95#[must_use]
99pub fn root_class(luminance: Luminance) -> &'static str {
100 if luminance.is_dark() { "dark" } else { "" }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn a_stated_mode_outranks_what_the_browser_prefers() {
109 assert_eq!(luminance(Mode::Light, Prefers::Dark), Luminance::Light);
110 assert_eq!(luminance(Mode::Dark, Prefers::Light), Luminance::Dark);
111 }
112
113 #[test]
114 fn following_the_system_follows_the_query() {
115 assert_eq!(luminance(Mode::System, Prefers::Dark), Luminance::Dark);
116 assert_eq!(luminance(Mode::System, Prefers::Light), Luminance::Light);
117 }
118
119 #[test]
122 fn an_unstated_preference_falls_back_to_the_default() {
123 assert_eq!(
124 luminance(Mode::System, Prefers::Unstated),
125 luminance(Mode::default(), Prefers::Unstated),
126 );
127 }
128}