leptos-use/src/use_css_var.rs

221 lines
6.3 KiB
Rust
Raw Normal View History

2023-07-14 22:43:19 +01:00
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
2023-06-16 04:51:33 +01:00
use crate::core::ElementMaybeSignal;
use crate::{
use_mutation_observer_with_options, watch_with_options, UseMutationObserverOptions,
WatchOptions,
};
2023-07-14 22:43:19 +01:00
use cfg_if::cfg_if;
2023-06-16 04:51:33 +01:00
use default_struct_builder::DefaultBuilder;
use leptos::*;
use std::marker::PhantomData;
use std::time::Duration;
use wasm_bindgen::JsCast;
2023-06-16 04:51:33 +01:00
/// Manipulate CSS variables.
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_css_var)
///
/// ## Usage
///
/// ```
/// # use leptos::*;
/// # use leptos_use::use_css_var;
/// #
/// # #[component]
2023-07-27 18:06:36 +01:00
/// # fn Demo() -> impl IntoView {
/// let (color, set_color) = use_css_var("--color");
2023-06-16 04:51:33 +01:00
///
/// set_color.set("red".to_string());
2023-06-16 04:51:33 +01:00
/// #
2023-07-27 18:06:36 +01:00
/// # view! { }
2023-06-16 04:51:33 +01:00
/// # }
/// ```
///
/// The variable name itself can be a `Signal`.
///
/// ```
/// # use leptos::*;
/// # use leptos_use::use_css_var;
/// #
/// # #[component]
2023-07-27 18:06:36 +01:00
/// # fn Demo() -> impl IntoView {
/// let (key, set_key) = create_signal("--color".to_string());
/// let (color, set_color) = use_css_var(key);
2023-06-16 04:51:33 +01:00
/// #
2023-07-27 18:06:36 +01:00
/// # view! { }
2023-06-16 04:51:33 +01:00
/// # }
/// ```
///
/// You can specify the element that the variable is applied to as well as an initial value in case
/// the variable is not set yet. The option to listen for changes to the variable is also available.
///
/// ```
/// # use leptos::*;
2023-07-03 15:16:22 +01:00
/// # use leptos::html::Div;
2023-06-16 04:51:33 +01:00
/// # use leptos_use::{use_css_var_with_options, UseCssVarOptions};
/// #
/// # #[component]
2023-07-27 18:06:36 +01:00
/// # fn Demo() -> impl IntoView {
/// let el = create_node_ref::<Div>();
2023-06-16 04:51:33 +01:00
///
/// let (color, set_color) = use_css_var_with_options(
/// "--color",
/// UseCssVarOptions::default()
/// .target(el)
/// .initial_value("#eee")
/// .observe(true),
/// );
///
2023-07-27 18:06:36 +01:00
/// view! {
2023-06-16 04:51:33 +01:00
/// <div node_ref=el>"..."</div>
/// }
/// # }
/// ```
2023-07-14 22:43:19 +01:00
///
/// ## Server-Side Rendering
///
2023-07-27 18:06:36 +01:00
/// On the server this simply returns `create_signal(options.initial_value)`.
2023-06-16 04:51:33 +01:00
pub fn use_css_var(
prop: impl Into<MaybeSignal<String>>,
) -> (ReadSignal<String>, WriteSignal<String>) {
2023-07-27 18:06:36 +01:00
use_css_var_with_options(prop, UseCssVarOptions::default())
2023-06-16 04:51:33 +01:00
}
/// Version of [`use_css_var`] that takes a `UseCssVarOptions`. See [`use_css_var`] for how to use.
pub fn use_css_var_with_options<P, El, T>(
prop: P,
options: UseCssVarOptions<El, T>,
) -> (ReadSignal<String>, WriteSignal<String>)
where
P: Into<MaybeSignal<String>>,
El: Clone,
2023-07-27 18:06:36 +01:00
El: Into<ElementMaybeSignal<T, web_sys::Element>>,
2023-06-16 04:51:33 +01:00
T: Into<web_sys::Element> + Clone + 'static,
{
let UseCssVarOptions {
target,
initial_value,
observe,
..
} = options;
2023-07-27 18:06:36 +01:00
let (variable, set_variable) = create_signal(initial_value.clone());
2023-06-16 04:51:33 +01:00
2023-07-14 22:43:19 +01:00
cfg_if! { if #[cfg(not(feature = "ssr"))] {
2023-07-27 18:06:36 +01:00
let el_signal = (target).into();
2023-07-14 22:43:19 +01:00
let prop = prop.into();
2023-06-16 04:51:33 +01:00
2023-07-14 22:43:19 +01:00
let update_css_var = {
let prop = prop.clone();
let el_signal = el_signal.clone();
2023-06-16 04:51:33 +01:00
2023-07-14 22:43:19 +01:00
move || {
let key = prop.get_untracked();
2023-06-16 04:51:33 +01:00
2023-07-14 22:43:19 +01:00
if let Some(el) = el_signal.get_untracked() {
if let Ok(Some(style)) = window().get_computed_style(&el.into()) {
if let Ok(value) = style.get_property_value(&key) {
set_variable.update(|var| *var = value.trim().to_string());
return;
}
2023-06-16 04:51:33 +01:00
}
2023-07-14 22:43:19 +01:00
let initial_value = initial_value.clone();
set_variable.update(|var| *var = initial_value);
}
2023-06-16 04:51:33 +01:00
}
2023-07-14 22:43:19 +01:00
};
if observe {
let update_css_var = update_css_var.clone();
let el_signal = el_signal.clone();
use_mutation_observer_with_options::<ElementMaybeSignal<T, web_sys::Element>, T, _>(
2023-07-27 18:06:36 +01:00
el_signal,
2023-07-14 22:43:19 +01:00
move |_, _| update_css_var(),
UseMutationObserverOptions::default()
.attribute_filter(vec!["style".to_string()]),
2023-07-14 22:43:19 +01:00
);
2023-06-16 04:51:33 +01:00
}
2023-07-14 22:43:19 +01:00
// To get around style attributes on node_refs that are not applied after the first render
set_timeout(update_css_var.clone(), Duration::ZERO);
2023-06-16 04:51:33 +01:00
2023-07-14 22:43:19 +01:00
{
let el_signal = el_signal.clone();
let prop = prop.clone();
2023-06-16 04:51:33 +01:00
2023-07-14 22:43:19 +01:00
let _ = watch_with_options(
2023-07-27 18:06:36 +01:00
move || (el_signal.get(), prop.get()),
2023-07-14 22:43:19 +01:00
move |_, _, _| update_css_var(),
WatchOptions::default().immediate(true),
);
}
2023-06-16 04:51:33 +01:00
2023-07-14 22:43:19 +01:00
let _ = watch(
2023-07-27 18:06:36 +01:00
move || variable.get(),
2023-07-14 22:43:19 +01:00
move |val, _, _| {
if let Some(el) = el_signal.get() {
let el = el.into().unchecked_into::<web_sys::HtmlElement>();
let style = el.style();
let _ = style.set_property(&prop.get_untracked(), val);
}
},
2023-07-24 21:16:59 +02:00
false,
2023-06-16 04:51:33 +01:00
);
2023-07-14 22:43:19 +01:00
}}
2023-06-16 04:51:33 +01:00
(variable, set_variable)
}
/// Options for [`use_css_var_with_options`].
#[derive(DefaultBuilder)]
pub struct UseCssVarOptions<El, T>
where
El: Clone,
2023-07-27 18:06:36 +01:00
El: Into<ElementMaybeSignal<T, web_sys::Element>>,
2023-06-16 04:51:33 +01:00
T: Into<web_sys::Element> + Clone + 'static,
{
/// The target element to read the variable from and set the variable on.
/// Defaults to the `document.documentElement`.
target: El,
/// The initial value of the variable before it is read. Also the default value
/// if the variable isn't defined on the target. Defaults to "".
#[builder(into)]
initial_value: String,
/// If `true` use a `MutationObserver` to monitor variable changes. Defaults to `false`.
observe: bool,
#[builder(skip)]
_marker: PhantomData<T>,
}
2023-07-14 22:43:19 +01:00
cfg_if! { if #[cfg(feature = "ssr")] {
impl Default for UseCssVarOptions<Option<web_sys::Element>, web_sys::Element> {
fn default() -> Self {
Self {
target: None,
initial_value: "".into(),
observe: false,
_marker: PhantomData,
}
2023-06-16 04:51:33 +01:00
}
}
2023-07-14 22:43:19 +01:00
} else {
impl Default for UseCssVarOptions<web_sys::Element, web_sys::Element> {
fn default() -> Self {
Self {
target: document().document_element().expect("No document element"),
initial_value: "".into(),
observe: false,
_marker: PhantomData,
}
}
}
}}