diff --git a/Cargo.lock b/Cargo.lock index ae251751..1f52c0f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2136,6 +2136,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "leptos-node-ref" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0497a30ec51e76bdbf9542d29b65399c70d88c0397bc38551d8eff0385847bfc" +dependencies = [ + "leptos", + "send_wrapper", +] + [[package]] name = "leptos-style" version = "0.0.3" @@ -2926,6 +2936,39 @@ dependencies = [ "web-sys", ] +[[package]] +name = "radix-leptos-use-controllable-state" +version = "0.0.2" +dependencies = [ + "leptos", +] + +[[package]] +name = "radix-leptos-use-escape-keydown" +version = "0.0.2" +dependencies = [ + "leptos", + "send_wrapper", + "web-sys", +] + +[[package]] +name = "radix-leptos-use-previous" +version = "0.0.2" +dependencies = [ + "leptos", +] + +[[package]] +name = "radix-leptos-use-size" +version = "0.0.2" +dependencies = [ + "leptos", + "leptos-node-ref", + "send_wrapper", + "web-sys", +] + [[package]] name = "radix-leptos-visually-hidden" version = "0.0.2" diff --git a/Cargo.toml b/Cargo.toml index 3b9211ae..68450696 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,10 @@ members = [ "packages/primitives/leptos/direction", "packages/primitives/leptos/id", "packages/primitives/leptos/label", + "packages/primitives/leptos/use-controllable-state", + "packages/primitives/leptos/use-escape-keydown", + "packages/primitives/leptos/use-previous", + "packages/primitives/leptos/use-size", "packages/primitives/leptos/visually-hidden", "packages/primitives/yew/*", "packages/themes/yew", @@ -42,8 +46,10 @@ dioxus = "0.6.1" leptos = "0.7.2" leptos_dom = "0.7.2" leptos_router = "0.7.2" +leptos-node-ref = "0.0.3" leptos-style = "0.0.3" log = "0.4.22" +send_wrapper = "0.6.0" serde = "1.0.198" serde_json = "1.0.116" tailwind_fuse = { version = "0.3.0", features = ["variant"] } diff --git a/packages/primitives/leptos/use-controllable-state/src/use_controllable_state.rs b/packages/primitives/leptos/use-controllable-state/src/use_controllable_state.rs index e491cadf..1ac0c0b0 100644 --- a/packages/primitives/leptos/use-controllable-state/src/use_controllable_state.rs +++ b/packages/primitives/leptos/use-controllable-state/src/use_controllable_state.rs @@ -1,15 +1,12 @@ -use leptos::{ - create_signal, Callable, Callback, Effect, MaybeProp, ReadSignal, RwSignal, Signal, SignalGet, - SignalGetUntracked, SignalSet, WriteSignal, -}; +use leptos::prelude::*; -pub struct UseControllableStateParams { +pub struct UseControllableStateParams { pub prop: MaybeProp, pub default_prop: MaybeProp, pub on_change: Option>>, } -pub fn use_controllable_state( +pub fn use_controllable_state( UseControllableStateParams { prop, default_prop, @@ -32,7 +29,7 @@ pub fn use_controllable_state( if is_controlled.get() { if next_value != prop.get() { if let Some(on_change) = on_change { - on_change.call(next_value); + on_change.run(next_value); } } } else { @@ -43,18 +40,18 @@ pub fn use_controllable_state( (value, set_value) } -pub struct UseUncontrollableStateParams { +pub struct UseUncontrollableStateParams { pub default_prop: MaybeProp, pub on_change: Option>>, } -fn use_uncontrolled_state( +fn use_uncontrolled_state( UseUncontrollableStateParams { default_prop, on_change, }: UseUncontrollableStateParams, ) -> (ReadSignal>, WriteSignal>) { - let uncontrolled_state = create_signal::>(default_prop.get()); + let uncontrolled_state = signal::>(default_prop.get()); let (value, _) = uncontrolled_state; let prev_value = RwSignal::new(value.get_untracked()); @@ -62,7 +59,7 @@ fn use_uncontrolled_state( let value = value.get(); if prev_value.get() != value { if let Some(on_change) = on_change { - on_change.call(value.clone()); + on_change.run(value.clone()); prev_value.set(value); } } diff --git a/packages/primitives/leptos/use-escape-keydown/Cargo.toml b/packages/primitives/leptos/use-escape-keydown/Cargo.toml index 10e3cafb..fa89c99a 100644 --- a/packages/primitives/leptos/use-escape-keydown/Cargo.toml +++ b/packages/primitives/leptos/use-escape-keydown/Cargo.toml @@ -10,4 +10,5 @@ version.workspace = true [dependencies] leptos.workspace = true +send_wrapper.workspace = true web-sys = { workspace = true, features = ["EventListenerOptions"] } diff --git a/packages/primitives/leptos/use-escape-keydown/src/use_escape_keydown.rs b/packages/primitives/leptos/use-escape-keydown/src/use_escape_keydown.rs index 7dacf670..6302d878 100644 --- a/packages/primitives/leptos/use-escape-keydown/src/use_escape_keydown.rs +++ b/packages/primitives/leptos/use-escape-keydown/src/use_escape_keydown.rs @@ -1,6 +1,7 @@ -use std::rc::Rc; +use std::sync::Arc; -use leptos::{document, ev::KeyboardEvent, on_cleanup, Callable, Callback, Effect, StoredValue}; +use leptos::{ev::KeyboardEvent, prelude::*}; +use send_wrapper::SendWrapper; use web_sys::{ wasm_bindgen::{closure::Closure, JsCast}, AddEventListenerOptions, Document, EventListenerOptions, @@ -11,30 +12,35 @@ pub fn use_escape_keydown( on_escape_key_down: Option>, owner_document: Option, ) { - let owner_document = StoredValue::new(owner_document.unwrap_or(document())); + let owner_document = StoredValue::new(SendWrapper::new(owner_document.unwrap_or(document()))); - let handle_key_down: Rc> = - Rc::new(Closure::new(move |event: KeyboardEvent| { + type HandleKeyDown = dyn Fn(KeyboardEvent); + let handle_key_down: Arc>> = Arc::new(SendWrapper::new( + Closure::new(move |event: KeyboardEvent| { if event.key() == "Escape" { if let Some(on_escape_key_down) = on_escape_key_down { - on_escape_key_down.call(event); + on_escape_key_down.run(event); } } - })); - let cleanup_handle_key_down = handle_key_down.clone(); + }), + )); - Effect::new(move |_| { - let options = AddEventListenerOptions::new(); - options.set_capture(true); + Effect::new({ + let handle_key_down = handle_key_down.clone(); - owner_document - .get_value() - .add_event_listener_with_callback_and_add_event_listener_options( - "keydown", - (*handle_key_down).as_ref().unchecked_ref(), - &options, - ) - .expect("Key down event listener should be added."); + move |_| { + let options = AddEventListenerOptions::new(); + options.set_capture(true); + + owner_document + .get_value() + .add_event_listener_with_callback_and_add_event_listener_options( + "keydown", + (*handle_key_down).as_ref().unchecked_ref(), + &options, + ) + .expect("Key down event listener should be added."); + } }); on_cleanup(move || { @@ -45,7 +51,7 @@ pub fn use_escape_keydown( .get_value() .remove_event_listener_with_callback_and_event_listener_options( "keydown", - (*cleanup_handle_key_down).as_ref().unchecked_ref(), + (*handle_key_down).as_ref().unchecked_ref(), &options, ) .expect("Key down event listener should be removed."); diff --git a/packages/primitives/leptos/use-previous/src/use_previous.rs b/packages/primitives/leptos/use-previous/src/use_previous.rs index 2932aec2..537aa440 100644 --- a/packages/primitives/leptos/use-previous/src/use_previous.rs +++ b/packages/primitives/leptos/use-previous/src/use_previous.rs @@ -1,16 +1,17 @@ -use leptos::{Memo, RwSignal, Signal, SignalGet, SignalGetUntracked, SignalSetUntracked}; +use leptos::prelude::*; -pub fn use_previous(value: Signal) -> Memo { - let current = RwSignal::new(value.get_untracked()); - let previous = RwSignal::new(value.get_untracked()); +pub fn use_previous(value: Signal) -> Memo { + let stored_value = StoredValue::new((value.get_untracked(), value.get_untracked())); Memo::new(move |_| { let value = value.get(); - let current_value = current.get(); + let (current_value, previous_value) = stored_value.get_value(); + if current_value != value { - previous.set_untracked(current_value); - current.set_untracked(value.clone()); + stored_value.set_value((value.clone(), current_value.clone())); + current_value + } else { + previous_value } - previous.get() }) } diff --git a/packages/primitives/leptos/use-size/Cargo.toml b/packages/primitives/leptos/use-size/Cargo.toml index 49056424..24c18efd 100644 --- a/packages/primitives/leptos/use-size/Cargo.toml +++ b/packages/primitives/leptos/use-size/Cargo.toml @@ -10,6 +10,8 @@ version.workspace = true [dependencies] leptos.workspace = true +leptos-node-ref.workspace = true +send_wrapper.workspace = true web-sys = { workspace = true, features = [ "ResizeObserver", "ResizeObserverBoxOptions", diff --git a/packages/primitives/leptos/use-size/src/use_size.rs b/packages/primitives/leptos/use-size/src/use_size.rs index 5c3e6e0e..e46c11a2 100644 --- a/packages/primitives/leptos/use-size/src/use_size.rs +++ b/packages/primitives/leptos/use-size/src/use_size.rs @@ -1,6 +1,8 @@ -use std::{cell::RefCell, rc::Rc}; +use std::sync::{Arc, Mutex}; -use leptos::{create_signal, html::AnyElement, on_cleanup, Effect, NodeRef, ReadSignal, SignalSet}; +use leptos::prelude::*; +use leptos_node_ref::AnyNodeRef; +use send_wrapper::SendWrapper; use web_sys::{ wasm_bindgen::{closure::Closure, JsCast}, ResizeObserver, ResizeObserverBoxOptions, ResizeObserverEntry, ResizeObserverOptions, @@ -13,14 +15,18 @@ pub struct Size { pub height: f64, } -pub fn use_size(element_ref: NodeRef) -> ReadSignal> { - let (size, set_size) = create_signal::>(None); +pub fn use_size(element_ref: AnyNodeRef) -> ReadSignal> { + let (size, set_size) = signal::>(None); - let resize_observer: Rc>> = Rc::new(RefCell::new(None)); + let resize_observer: Arc>>> = + Arc::new(Mutex::new(None)); let cleanup_resize_observer = resize_observer.clone(); Effect::new(move |_| { - if let Some(element) = element_ref.get() { + if let Some(element) = element_ref + .get() + .and_then(|element| element.dyn_into::().ok()) + { // Provide size as early as possible. set_size.set(Some(Size { width: element.offset_width() as f64, @@ -43,7 +49,7 @@ pub fn use_size(element_ref: NodeRef) -> ReadSignal> { } }); - resize_observer.replace(Some( + *resize_observer.lock().expect("Lock should be acquired.") = Some(SendWrapper::new( ResizeObserver::new(resize_closure.into_js_value().unchecked_ref()) .expect("Resize observer should be created."), )); @@ -52,7 +58,8 @@ pub fn use_size(element_ref: NodeRef) -> ReadSignal> { options.set_box(ResizeObserverBoxOptions::BorderBox); resize_observer - .borrow() + .lock() + .expect("Lock should be acquired.") .as_ref() .expect("Resize observer should exist.") .observe_with_options(element.as_ref(), &options); @@ -63,7 +70,11 @@ pub fn use_size(element_ref: NodeRef) -> ReadSignal> { }); on_cleanup(move || { - if let Some(resize_observer) = cleanup_resize_observer.take() { + if let Some(resize_observer) = cleanup_resize_observer + .lock() + .expect("Lock should be acquired.") + .as_ref() + { resize_observer.disconnect(); } });