diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index b891103..0000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,3 +0,0 @@ -[unstable] -rustflags = ["--cfg=web_sys_unstable_apis"] -rustdocflags = ["--cfg=web_sys_unstable_apis"] \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 82633c2..e1c38c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.2] - 2023-11-09 + +### Fixes 🍕 + +- Fixed SSR for + - use_timestamp + - use_raf_fn + - use_idle + ## [0.8.1] - 2023-10-28 ### Fixes 🍕 diff --git a/Cargo.toml b/Cargo.toml index 05ce484..03e168e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leptos-use" -version = "0.8.1" +version = "0.8.2" edition = "2021" authors = ["Marc-Stefan Cassola"] categories = ["gui", "web-programming"] @@ -27,7 +27,7 @@ prost = { version = "0.12", optional = true } serde = { version = "1", optional = true } serde_json = { version = "1", optional = true } thiserror = "1.0" -wasm-bindgen = "0.2" +wasm-bindgen = "0.2.87" wasm-bindgen-futures = "0.4" [dependencies.web-sys] @@ -102,5 +102,3 @@ ssr = [] [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg=web_sys_unstable_apis"] -rustc-args = ["--cfg=web_sys_unstable_apis"] diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml deleted file mode 100644 index c87f326..0000000 --- a/examples/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build] -rustflags = ["--cfg=web_sys_unstable_apis", "--cfg=has_std"] diff --git a/examples/ssr/src/app.rs b/examples/ssr/src/app.rs index 20f3b91..2d5da01 100644 --- a/examples/ssr/src/app.rs +++ b/examples/ssr/src/app.rs @@ -5,8 +5,8 @@ use leptos_meta::*; use leptos_router::*; use leptos_use::storage::use_local_storage; use leptos_use::{ - use_color_mode, use_debounce_fn, use_event_listener, use_intl_number_format, use_window, - ColorMode, UseColorModeReturn, UseIntlNumberFormatOptions, + use_color_mode, use_debounce_fn, use_event_listener, use_intl_number_format, use_timestamp, + use_window, ColorMode, UseColorModeReturn, UseIntlNumberFormatOptions, }; #[component] @@ -65,6 +65,8 @@ fn HomePage() -> impl IntoView { let UseColorModeReturn { mode, set_mode, .. } = use_color_mode(); + let timestamp = use_timestamp(); + view! {

Leptos-Use SSR Example

@@ -75,5 +77,6 @@ fn HomePage() -> impl IntoView { +

{timestamp}

} } diff --git a/examples/use_element_size/.cargo/config.toml b/examples/use_element_size/.cargo/config.toml deleted file mode 100644 index 8467175..0000000 --- a/examples/use_element_size/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build] -rustflags = ["--cfg=web_sys_unstable_apis"] diff --git a/src/core/datetime.rs b/src/core/datetime.rs new file mode 100644 index 0000000..66e72fb --- /dev/null +++ b/src/core/datetime.rs @@ -0,0 +1,16 @@ +use cfg_if::cfg_if; + +/// SSR safe `Date.now()`. +#[inline(always)] +pub(crate) fn now() -> f64 { + cfg_if! { if #[cfg(feature = "ssr")] { + use std::time::{SystemTime, UNIX_EPOCH}; + + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_millis() as f64 + } else { + js_sys::Date::now() + }} +} diff --git a/src/core/element_maybe_signal.rs b/src/core/element_maybe_signal.rs index 20dd423..a100b7e 100644 --- a/src/core/element_maybe_signal.rs +++ b/src/core/element_maybe_signal.rs @@ -179,6 +179,7 @@ where { fn from(target: &'a str) -> Self { cfg_if! { if #[cfg(feature = "ssr")] { + let _ = target; Self::Static(None) } else { Self::Static(document().query_selector(target).unwrap_or_default()) @@ -201,6 +202,7 @@ where { fn from(signal: Signal) -> Self { cfg_if! { if #[cfg(feature = "ssr")] { + let _ = signal; Self::Dynamic(Signal::derive(|| None)) } else { Self::Dynamic( diff --git a/src/core/mod.rs b/src/core/mod.rs index 25d7d04..9abe354 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,5 @@ mod connection_ready_state; +mod datetime; mod direction; mod element_maybe_signal; mod elements_maybe_signal; @@ -10,6 +11,7 @@ mod ssr_safe_method; mod storage; pub use connection_ready_state::*; +pub(crate) use datetime::*; pub use direction::*; pub use element_maybe_signal::*; pub use elements_maybe_signal::*; diff --git a/src/lib.rs b/src/lib.rs index 7ca96a3..caf6dd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,6 @@ // #![feature(doc_cfg)] //! Collection of essential Leptos utilities inspired by SolidJS USE / VueUse -use cfg_if::cfg_if; - pub mod core; #[cfg(feature = "docs")] pub mod docs; @@ -11,14 +9,6 @@ pub mod math; pub mod storage; pub mod utils; -cfg_if! { if #[cfg(web_sys_unstable_apis)] { - mod use_element_size; - mod use_resize_observer; - - pub use use_element_size::*; - pub use use_resize_observer::*; -}} - mod is_err; mod is_none; mod is_ok; @@ -37,6 +27,7 @@ mod use_document_visibility; mod use_draggable; mod use_drop_zone; mod use_element_hover; +mod use_element_size; mod use_element_visibility; mod use_event_listener; mod use_favicon; @@ -53,6 +44,7 @@ mod use_mutation_observer; mod use_preferred_contrast; mod use_preferred_dark; mod use_raf_fn; +mod use_resize_observer; mod use_scroll; mod use_service_worker; mod use_sorted; @@ -89,6 +81,7 @@ pub use use_document_visibility::*; pub use use_draggable::*; pub use use_drop_zone::*; pub use use_element_hover::*; +pub use use_element_size::*; pub use use_element_visibility::*; pub use use_event_listener::*; pub use use_favicon::*; @@ -105,6 +98,7 @@ pub use use_mutation_observer::*; pub use use_preferred_contrast::*; pub use use_preferred_dark::*; pub use use_raf_fn::*; +pub use use_resize_observer::*; pub use use_scroll::*; pub use use_service_worker::*; pub use use_sorted::*; diff --git a/src/use_element_size.rs b/src/use_element_size.rs index 8831505..bc6a226 100644 --- a/src/use_element_size.rs +++ b/src/use_element_size.rs @@ -12,9 +12,6 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] { /// Reactive size of an HTML element. /// -/// > This function requires `--cfg=web_sys_unstable_apis` to be activated as -/// [described in the wasm-bindgen guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html). -/// /// Please refer to [ResizeObserver on MDN](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) /// for more details. /// @@ -25,12 +22,12 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] { /// ## Usage /// /// ``` -/// # use leptos::*; +/// # use leptos::{html::Div, *}; /// # use leptos_use::{use_element_size, UseElementSizeReturn}; /// # /// # #[component] /// # fn Demo() -> impl IntoView { -/// let el = create_node_ref(); +/// let el = create_node_ref::
(); /// /// let UseElementSizeReturn { width, height } = use_element_size(el); /// @@ -175,7 +172,7 @@ where } } -#[derive(DefaultBuilder)] +#[derive(DefaultBuilder, Default)] /// Options for [`use_element_size_with_options`]. pub struct UseElementSizeOptions { /// Initial size returned before any measurements on the `target` are done. Also the value reported @@ -187,15 +184,6 @@ pub struct UseElementSizeOptions { pub box_: Option, } -impl Default for UseElementSizeOptions { - fn default() -> Self { - Self { - initial_size: Size::default(), - box_: None, - } - } -} - /// The return value of [`use_element_size`]. pub struct UseElementSizeReturn { /// The width of the element. diff --git a/src/use_idle.rs b/src/use_idle.rs index 247c867..8a9cc96 100644 --- a/src/use_idle.rs +++ b/src/use_idle.rs @@ -1,8 +1,10 @@ +use crate::core::now; use crate::utils::{create_filter_wrapper, DebounceOptions, FilterOptions, ThrottleOptions}; use crate::{ filter_builder_methods, use_document, use_event_listener, use_event_listener_with_options, UseEventListenerOptions, }; +use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; use leptos::ev::{visibilitychange, Custom}; use leptos::leptos_dom::helpers::TimeoutHandle; @@ -54,6 +56,18 @@ use std::time::Duration; /// # view! { } /// # } /// ``` +/// +/// ## Server-Side Rendering +/// +/// On the server this will always return static signals +/// +/// ```ignore +/// UseIdleReturn{ +/// idle: Signal(initial_state), +/// last_active: Signal(now), +/// reset: || {} +/// } +/// ``` pub fn use_idle(timeout: u64) -> UseIdleReturn { use_idle_with_options(timeout, UseIdleOptions::default()) } @@ -71,57 +85,65 @@ pub fn use_idle_with_options( } = options; let (idle, set_idle) = create_signal(initial_state); - let (last_active, set_last_active) = create_signal(js_sys::Date::now()); + let (last_active, set_last_active) = create_signal(now()); - let reset = { - let timer = Cell::new(None::); + cfg_if! { if #[cfg(feature = "ssr")] { + let reset = || (); + let _ = timeout; + let _ = events; + let _ = listen_for_visibility_change; + let _ = filter; + } else { + let reset = { + let timer = Cell::new(None::); - move || { - set_idle.set(false); - if let Some(timer) = timer.take() { - timer.clear(); + move || { + set_idle.set(false); + if let Some(timer) = timer.take() { + timer.clear(); + } + timer.replace( + set_timeout_with_handle(move || set_idle.set(true), Duration::from_millis(timeout)) + .ok(), + ); } - timer.replace( - set_timeout_with_handle(move || set_idle.set(true), Duration::from_millis(timeout)) - .ok(), + }; + + let on_event = { + let reset = reset.clone(); + + let filtered_callback = create_filter_wrapper(filter.filter_fn(), move || { + set_last_active.set(js_sys::Date::now()); + reset(); + }); + + move |_: web_sys::Event| { + filtered_callback(); + } + }; + + let listener_options = UseEventListenerOptions::default().passive(true); + for event in events { + let _ = use_event_listener_with_options( + use_document(), + Custom::new(event), + on_event.clone(), + listener_options, ); } - }; - let on_event = { - let reset = reset.clone(); + if listen_for_visibility_change { + let on_event = on_event.clone(); - let filtered_callback = create_filter_wrapper(filter.filter_fn(), move || { - set_last_active.set(js_sys::Date::now()); - reset(); - }); - - move |_: web_sys::Event| { - filtered_callback(); + let _ = use_event_listener(use_document(), visibilitychange, move |evt| { + if !document().hidden() { + on_event(evt); + } + }); } - }; - let listener_options = UseEventListenerOptions::default().passive(true); - for event in events { - let _ = use_event_listener_with_options( - use_document(), - Custom::new(event), - on_event.clone(), - listener_options, - ); - } - - if listen_for_visibility_change { - let on_event = on_event.clone(); - - let _ = use_event_listener(use_document(), visibilitychange, move |evt| { - if !document().hidden() { - on_event(evt); - } - }); - } - - reset.clone()(); + reset.clone()(); + }} UseIdleReturn { idle: idle.into(), diff --git a/src/use_raf_fn.rs b/src/use_raf_fn.rs index c44a00a..b18503c 100644 --- a/src/use_raf_fn.rs +++ b/src/use_raf_fn.rs @@ -1,10 +1,9 @@ use crate::utils::Pausable; +use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; use leptos::*; use std::cell::{Cell, RefCell}; use std::rc::Rc; -use wasm_bindgen::closure::Closure; -use wasm_bindgen::JsCast; /// Call function on every requestAnimationFrame. /// With controls of pausing and resuming. @@ -34,6 +33,10 @@ use wasm_bindgen::JsCast; /// /// You can use `use_raf_fn_with_options` and set `immediate` to `false`. In that case /// you have to call `resume()` before the `callback` is executed. +/// +/// ## Server-Side Rendering +/// +/// On the server this does basically nothing. The provided closure will never be called. pub fn use_raf_fn( callback: impl Fn(UseRafFnCallbackArgs) + 'static, ) -> Pausable { @@ -54,24 +57,31 @@ pub fn use_raf_fn_with_options( let loop_ref = Rc::new(RefCell::new(Box::new(|_: f64| {}) as Box)); let request_next_frame = { - let loop_ref = Rc::clone(&loop_ref); - let raf_handle = Rc::clone(&raf_handle); + cfg_if! { if #[cfg(feature = "ssr")] { + move || () + } else { + use wasm_bindgen::JsCast; + use wasm_bindgen::closure::Closure; - move || { let loop_ref = Rc::clone(&loop_ref); + let raf_handle = Rc::clone(&raf_handle); - raf_handle.set( - window() - .request_animation_frame( - Closure::once_into_js(move |timestamp: f64| { - loop_ref.borrow()(timestamp); - }) - .as_ref() - .unchecked_ref(), - ) - .ok(), - ); - } + move || { + let loop_ref = Rc::clone(&loop_ref); + + raf_handle.set( + window() + .request_animation_frame( + Closure::once_into_js(move |timestamp: f64| { + loop_ref.borrow()(timestamp); + }) + .as_ref() + .unchecked_ref(), + ) + .ok(), + ); + } + }} }; let loop_fn = { diff --git a/src/use_resize_observer.rs b/src/use_resize_observer.rs index 2e35e85..07e5dd4 100644 --- a/src/use_resize_observer.rs +++ b/src/use_resize_observer.rs @@ -12,9 +12,6 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] { /// Reports changes to the dimensions of an Element's content or the border-box. /// -/// > This function requires `--cfg=web_sys_unstable_apis` to be activated as -/// [described in the wasm-bindgen guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html). -/// /// Please refer to [ResizeObserver on MDN](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) /// for more details. /// @@ -25,12 +22,12 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] { /// ## Usage /// /// ``` -/// # use leptos::*; +/// # use leptos::{html::Div, *}; /// # use leptos_use::use_resize_observer; /// # /// # #[component] /// # fn Demo() -> impl IntoView { -/// let el = create_node_ref(); +/// let el = create_node_ref::
(); /// let (text, set_text) = create_signal("".to_string()); /// /// use_resize_observer( diff --git a/src/use_timestamp.rs b/src/use_timestamp.rs index 1718e4f..cdc2576 100644 --- a/src/use_timestamp.rs +++ b/src/use_timestamp.rs @@ -1,3 +1,4 @@ +use crate::core::now; use crate::utils::Pausable; use crate::{ use_interval_fn_with_options, use_raf_fn_with_options, UseIntervalFnOptions, UseRafFnOptions, @@ -47,7 +48,8 @@ use std::rc::Rc; /// /// ## Server-Side Rendering /// -/// On the server this function will simply be ignored. +/// On the server this function will return a signal with the milliseconds since the Unix epoch. +/// But the signal will never update (as there's no `request_animation_frame` on the server). pub fn use_timestamp() -> Signal { use_timestamp_with_controls().timestamp } @@ -71,10 +73,10 @@ pub fn use_timestamp_with_controls_and_options(options: UseTimestampOptions) -> callback, } = options; - let (ts, set_ts) = create_signal(js_sys::Date::now() + offset); + let (ts, set_ts) = create_signal(now() + offset); let update = move || { - set_ts.set(js_sys::Date::now() + offset); + set_ts.set(now() + offset); }; let cb = { diff --git a/src/utils/filters/throttle.rs b/src/utils/filters/throttle.rs index caaa15e..ceda428 100644 --- a/src/utils/filters/throttle.rs +++ b/src/utils/filters/throttle.rs @@ -1,8 +1,8 @@ #![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))] +use crate::core::now; use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; -use js_sys::Date; use leptos::leptos_dom::helpers::TimeoutHandle; use leptos::{set_timeout_with_handle, MaybeSignal, SignalGetUntracked}; use std::cell::{Cell, RefCell}; @@ -51,7 +51,7 @@ where move |mut _invoke: Rc R>| { let duration = ms.get_untracked(); - let elapsed = Date::now() - last_exec.get(); + let elapsed = now() - last_exec.get(); let last_return_val = Rc::clone(&last_return_value); let invoke = move || { @@ -65,13 +65,13 @@ where clear(); if duration <= 0.0 { - last_exec.set(Date::now()); + last_exec.set(now()); invoke(); return Rc::clone(&last_return_value); } if elapsed > duration && (options.leading || !is_leading.get()) { - last_exec.set(Date::now()); + last_exec.set(now()); invoke(); } else if options.trailing { cfg_if! { if #[cfg(not(feature = "ssr"))] { @@ -80,7 +80,7 @@ where timer.set( set_timeout_with_handle( move || { - last_exec.set(Date::now()); + last_exec.set(now()); is_leading.set(true); invoke(); clear();