thaw_utils: upgrade leptosv0.7

This commit is contained in:
luoxiao 2024-06-13 16:00:14 +08:00 committed by luoxiaozero
parent 99f585440b
commit 42d76917d0
20 changed files with 362 additions and 355 deletions

View file

@ -12,7 +12,7 @@ license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
leptos = { version = "0.6.10" } leptos = { workspace = true }
thaw_utils = { workspace = true } thaw_utils = { workspace = true }
web-sys = { version = "0.3.69", features = ["DomRect"] } web-sys = { version = "0.3.69", features = ["DomRect"] }
cfg-if = "1.0.0" cfg-if = "1.0.0"

View file

@ -18,6 +18,7 @@ web-sys = "0.3.69"
wasm-bindgen = "0.2.92" wasm-bindgen = "0.2.92"
cfg-if = "1.0.0" cfg-if = "1.0.0"
chrono = "0.4.35" chrono = "0.4.35"
send_wrapper = "0.6"
[features] [features]
csr = ["leptos/csr"] csr = ["leptos/csr"]

View file

@ -1,10 +1,13 @@
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
use leptos::create_render_effect; use leptos::prelude::RenderEffect;
use leptos::{ use leptos::{
Attribute, IntoAttribute, MaybeProp, Memo, Oco, RwSignal, SignalGet, SignalUpdate, SignalWith, prelude::{MaybeProp, Memo, Oco, RwSignal},
reactive_graph::traits::{Get, Update, With, WithUntracked},
tachys::renderer::DomRenderer,
}; };
use std::{collections::HashSet, rc::Rc}; use std::collections::HashSet;
#[derive(Clone)]
pub struct ClassList(RwSignal<HashSet<Oco<'static, str>>>); pub struct ClassList(RwSignal<HashSet<Oco<'static, str>>>);
impl ClassList { impl ClassList {
@ -30,7 +33,7 @@ impl ClassList {
}); });
} }
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
create_render_effect(move |old_name| { RenderEffect::new(move |old_name| {
let name = f(); let name = f();
if let Some(old_name) = old_name { if let Some(old_name) = old_name {
if old_name != name { if old_name != name {
@ -57,7 +60,7 @@ impl ClassList {
} }
} }
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
create_render_effect(move |old_name| { RenderEffect::new(move |old_name| {
let name = f(); let name = f();
if let Some(old_name) = old_name { if let Some(old_name) = old_name {
if old_name != name { if old_name != name {
@ -96,7 +99,7 @@ impl ClassList {
}); });
} }
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
create_render_effect(move |old| { RenderEffect::new(move |old| {
let name = name.clone(); let name = name.clone();
let new = f(); let new = f();
if old.is_none() { if old.is_none() {
@ -121,29 +124,89 @@ impl ClassList {
self self
} }
fn to_class_string(self, class: &mut String) {
self.0.with(|set| {
set.iter().enumerate().for_each(|(index, name)| {
if name.is_empty() {
return;
}
if index != 0 {
class.push(' ');
}
class.push_str(name)
});
});
}
} }
impl IntoAttribute for ClassList { impl<'a, R> leptos::tachys::html::class::IntoClass<R> for ClassList
fn into_attribute(self) -> Attribute { where
Attribute::Fn(Rc::new(move || { R: DomRenderer,
self.0.with(|set| { {
let mut class = String::new(); type State = (R::ClassList, String);
set.iter().enumerate().for_each(|(index, name)| { type Cloneable = Self;
if name.is_empty() { type CloneableOwned = Self;
return;
} fn html_len(&self) -> usize {
if index != 0 { self.0.with_untracked(|set| {
class.push(' '); let mut len = 0;
} set.iter().enumerate().for_each(|(index, name)| {
class.push_str(name) if name.is_empty() {
}); return;
class.into_attribute() }
}) if index != 0 {
})) len += 1;
}
len += name.len();
});
len
})
} }
fn into_attribute_boxed(self: Box<Self>) -> Attribute { fn to_html(self, class: &mut String) {
self.into_attribute() self.to_class_string(class);
}
fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State {
let class_list = R::class_list(el);
let mut class = String::new();
self.to_class_string(&mut class);
if !FROM_SERVER {
R::add_class(&class_list, &class);
}
(class_list, class)
}
fn build(self, el: &R::Element) -> Self::State {
let class_list = R::class_list(el);
let mut class = String::new();
self.to_class_string(&mut class);
if !class.is_empty() {
R::add_class(&class_list, &class);
}
(class_list, class)
}
fn rebuild(self, state: &mut Self::State) {
let mut class = String::new();
self.to_class_string(&mut class);
let (class_list, prev_class) = state;
if class != *prev_class {
R::remove_class(class_list, prev_class);
R::add_class(class_list, &class);
}
*prev_class = class;
}
fn into_cloneable(self) -> Self::Cloneable {
self
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
self
} }
} }
@ -245,21 +308,22 @@ macro_rules! class_list {
}; };
} }
#[cfg(test)] // TODO
mod tests { // #[cfg(test)]
use leptos::{create_runtime, Attribute, IntoAttribute}; // mod tests {
// use leptos::reactive_graph::Run;
#[test] // #[test]
fn macro_class_list() { // fn macro_class_list() {
let rt = create_runtime(); // let rt = create_runtime();
let class_list = class_list!("aa", ("bb", || true), move || "cc"); // let class_list = class_list!("aa", ("bb", || true), move || "cc");
if let Attribute::Fn(f) = class_list.into_attribute() { // if let Attribute::Fn(f) = class_list.into_attribute() {
if let Attribute::String(class) = f() { // if let Attribute::String(class) = f() {
assert!(class.contains("aa")); // assert!(class.contains("aa"));
assert!(class.contains("bb")); // assert!(class.contains("bb"));
assert!(class.contains("cc")); // assert!(class.contains("cc"));
} // }
} // }
rt.dispose(); // rt.dispose();
} // }
} // }

View file

@ -1,55 +1,55 @@
use leptos::{ // use leptos::{
html::{AnyElement, ToHtmlElement}, // html::{AnyElement, ToHtmlElement},
*, // *,
}; // };
pub fn get_scroll_parent(element: &HtmlElement<AnyElement>) -> Option<HtmlElement<AnyElement>> { // pub fn get_scroll_parent(element: &HtmlElement<AnyElement>) -> Option<HtmlElement<AnyElement>> {
let Some(parent_element) = get_parent_element(element) else { // let Some(parent_element) = get_parent_element(element) else {
return None; // return None;
}; // };
if parent_element.node_type() == 9 { // if parent_element.node_type() == 9 {
return Some(parent_element); // return Some(parent_element);
} // }
if parent_element.node_type() == 1 { // if parent_element.node_type() == 1 {
if let Some((overflow, overflow_x, overflow_y)) = get_overflow(&parent_element) { // if let Some((overflow, overflow_x, overflow_y)) = get_overflow(&parent_element) {
let overflow = format!("{overflow}{overflow_x}{overflow_y}"); // let overflow = format!("{overflow}{overflow_x}{overflow_y}");
if overflow.contains("auto") { // if overflow.contains("auto") {
return Some(parent_element); // return Some(parent_element);
} // }
if overflow.contains("scroll") { // if overflow.contains("scroll") {
return Some(parent_element); // return Some(parent_element);
} // }
if overflow.contains("overlay") { // if overflow.contains("overlay") {
return Some(parent_element); // return Some(parent_element);
} // }
} // }
} // }
get_scroll_parent(&parent_element) // get_scroll_parent(&parent_element)
} // }
fn get_parent_element(element: &HtmlElement<AnyElement>) -> Option<HtmlElement<AnyElement>> { // fn get_parent_element(element: &HtmlElement<AnyElement>) -> Option<HtmlElement<AnyElement>> {
if element.node_type() == 9 { // if element.node_type() == 9 {
None // None
} else { // } else {
element.parent_element().map(|ele| ele.to_leptos_element()) // element.parent_element().map(|ele| ele.to_leptos_element())
} // }
} // }
fn get_overflow(parent_element: &HtmlElement<AnyElement>) -> Option<(String, String, String)> { // fn get_overflow(parent_element: &HtmlElement<AnyElement>) -> Option<(String, String, String)> {
let Ok(Some(css_style_declaration)) = window().get_computed_style(parent_element) else { // let Ok(Some(css_style_declaration)) = window().get_computed_style(parent_element) else {
return None; // return None;
}; // };
let Ok(overflow) = css_style_declaration.get_property_value("overflow") else { // let Ok(overflow) = css_style_declaration.get_property_value("overflow") else {
return None; // return None;
}; // };
let Ok(overflow_x) = css_style_declaration.get_property_value("overflowX") else { // let Ok(overflow_x) = css_style_declaration.get_property_value("overflowX") else {
return None; // return None;
}; // };
let Ok(overflow_y) = css_style_declaration.get_property_value("overflowY") else { // let Ok(overflow_y) = css_style_declaration.get_property_value("overflowY") else {
return None; // return None;
}; // };
Some((overflow, overflow_x, overflow_y)) // Some((overflow, overflow_x, overflow_y))
} // }

View file

@ -1,5 +1,5 @@
mod get_scroll_parent; mod get_scroll_parent;
mod mount_style; mod mount_style;
pub use get_scroll_parent::get_scroll_parent; // pub use get_scroll_parent::get_scroll_parent;
pub use mount_style::{mount_dynamic_style, mount_style}; pub use mount_style::{mount_dynamic_style, mount_style};

View file

@ -9,7 +9,7 @@ pub fn mount_style(id: &str, content: &'static str) {
let style_el = style().attr("data-thaw-id", id).child(content); let style_el = style().attr("data-thaw-id", id).child(content);
meta.tags.register(format!("leptos-thaw-{id}").into(), style_el.into_any()); meta.tags.register(format!("leptos-thaw-{id}").into(), style_el.into_any());
} else { } else {
use leptos::document; use leptos::prelude::document;
let head = document().head().expect("head no exist"); let head = document().head().expect("head no exist");
let style = head let style = head
.query_selector(&format!("style[data-thaw-id=\"{id}\"]")) .query_selector(&format!("style[data-thaw-id=\"{id}\"]"))
@ -32,7 +32,7 @@ pub fn mount_style(id: &str, content: &'static str) {
} }
} }
pub fn mount_dynamic_style<T: Fn() -> String + 'static>(id: String, f: T) { pub fn mount_dynamic_style<T: Fn() -> String + Send + Sync + 'static>(id: String, f: T) {
cfg_if! { cfg_if! {
if #[cfg(feature = "ssr")] { if #[cfg(feature = "ssr")] {
use leptos::html::style; use leptos::html::style;
@ -42,35 +42,30 @@ pub fn mount_dynamic_style<T: Fn() -> String + 'static>(id: String, f: T) {
let style_el = style().attr("data-thaw-id", id).child(content); let style_el = style().attr("data-thaw-id", id).child(content);
meta.tags.register(format!("leptos-thaw-{id}").into(), style_el.into_any()); meta.tags.register(format!("leptos-thaw-{id}").into(), style_el.into_any());
} else { } else {
use leptos::document; use leptos::prelude::document;
use send_wrapper::SendWrapper;
let head = document().head().expect("head no exist"); let head = document().head().expect("head no exist");
let style = head let style = head
.query_selector(&format!("style[data-thaw-id=\"{id}\"]")) .query_selector(&format!("style[data-thaw-id=\"{id}\"]"))
.expect("query style element error"); .expect("query style element error").unwrap_or_else(|| {
#[cfg(feature = "hydrate")]
let _ = leptos::leptos_dom::HydrationCtx::id();
leptos::Effect::new_isomorphic(move |prev: Option<Option<web_sys::Element>>| {
let content = f();
if let Some(style) = style.as_ref() {
style.set_text_content(Some(&content));
None
} else if let Some(style) = prev.flatten() {
style.set_text_content(Some(&content));
Some(style)
} else {
let style = document() let style = document()
.create_element("style") .create_element("style")
.expect("create style element error"); .expect("create style element error");
_ = style.set_attribute("data-thaw-id", &id); _ = style.set_attribute("data-thaw-id", &id);
style.set_text_content(Some(&content));
_ = head.prepend_with_node_1(&style); _ = head.prepend_with_node_1(&style);
Some(style) style
} });
#[cfg(feature = "hydrate")]
let _ = leptos::leptos_dom::HydrationCtx::id();
let style = SendWrapper::new(style);
leptos::prelude::Effect::new_isomorphic(move |_| {
let content = f();
style.set_text_content(Some(&content));
}); });
} }
} }

View file

@ -1,9 +1,5 @@
use ::wasm_bindgen::{prelude::Closure, JsCast}; use ::wasm_bindgen::{prelude::Closure, JsCast};
use leptos::{ use leptos::{ev, tachys::renderer::DomRenderer};
ev,
tachys::{renderer::DomRenderer, view::any_view::AnyView},
};
use std::ops::Deref;
use web_sys::EventTarget; use web_sys::EventTarget;
pub fn add_event_listener<E>( pub fn add_event_listener<E>(
@ -15,9 +11,10 @@ where
E: ev::EventDescriptor + 'static, E: ev::EventDescriptor + 'static,
E::EventType: JsCast, E::EventType: JsCast,
{ {
add_event_listener_untyped(target, &event.name(), move |e| { todo!()
cb(e.unchecked_into::<E::EventType>()) // add_event_listener_untyped(target, &event.name(), move |e| {
}) // cb(e.unchecked_into::<E::EventType>())
// })
} }
pub struct EventListenerHandle(Box<dyn FnOnce()>); pub struct EventListenerHandle(Box<dyn FnOnce()>);
@ -34,26 +31,26 @@ impl EventListenerHandle {
} }
} }
fn add_event_listener_untyped( // fn add_event_listener_untyped(
target: impl DomRenderer, // target: impl DomRenderer,
event_name: &str, // event_name: &str,
cb: impl Fn(web_sys::Event) + 'static, // cb: impl Fn(web_sys::Event) + 'static,
) -> EventListenerHandle { // ) -> EventListenerHandle {
fn wel( // fn wel(
target: impl DomRenderer, // target: impl DomRenderer,
cb: Box<dyn FnMut(web_sys::Event)>, // cb: Box<dyn FnMut(web_sys::Event)>,
event_name: &str, // event_name: &str,
) -> EventListenerHandle { // ) -> EventListenerHandle {
let cb = Closure::wrap(cb).into_js_value(); // let cb = Closure::wrap(cb).into_js_value();
_ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref()); // _ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref());
let event_name = event_name.to_string(); // let event_name = event_name.to_string();
EventListenerHandle(Box::new(move || { // EventListenerHandle(Box::new(move || {
_ = target.remove_event_listener_with_callback(&event_name, cb.unchecked_ref()); // _ = target.remove_event_listener_with_callback(&event_name, cb.unchecked_ref());
})) // }))
} // }
wel(target, Box::new(cb), event_name) // wel(target, Box::new(cb), event_name)
} // }
pub fn add_event_listener_with_bool<E: ev::EventDescriptor + 'static>( pub fn add_event_listener_with_bool<E: ev::EventDescriptor + 'static>(
target: impl IntoEventTarget, target: impl IntoEventTarget,
@ -119,8 +116,8 @@ impl IntoEventTarget for web_sys::Document {
} }
} }
impl IntoEventTarget for HtmlElement<AnyElement> { // impl IntoEventTarget for HtmlElement<AnyElement> {
fn into_event_target(self) -> EventTarget { // fn into_event_target(self) -> EventTarget {
self.deref().deref().deref().deref().clone() // self.deref().deref().deref().deref().clone()
} // }
} // }

View file

@ -4,4 +4,4 @@ mod use_next_frame;
pub use use_click_position::use_click_position; pub use use_click_position::use_click_position;
pub use use_lock_html_scroll::use_lock_html_scroll; pub use use_lock_html_scroll::use_lock_html_scroll;
pub use use_next_frame::{use_next_frame, NextFrame}; pub use use_next_frame::NextFrame;

View file

@ -1,10 +1,13 @@
use leptos::{ReadSignal, RwSignal}; use leptos::reactive_graph::signal::{ReadSignal, RwSignal};
pub fn use_click_position() -> ReadSignal<Option<(i32, i32)>> { pub fn use_click_position() -> ReadSignal<Option<(i32, i32)>> {
let mouse_position = RwSignal::new(None); let mouse_position = RwSignal::new(None);
#[cfg(any(feature = "csr", feature = "hydrate"))] #[cfg(any(feature = "csr", feature = "hydrate"))]
{ {
use leptos::{ev, on_cleanup, window_event_listener, SignalSet}; use leptos::{
ev,
prelude::{on_cleanup, window_event_listener, Set},
};
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use web_sys::MouseEvent; use web_sys::MouseEvent;

View file

@ -1,20 +1,25 @@
use leptos::MaybeSignal; use leptos::reactive_graph::wrappers::read::MaybeSignal;
pub fn use_lock_html_scroll(is_lock: MaybeSignal<bool>) { pub fn use_lock_html_scroll(is_lock: MaybeSignal<bool>) {
#[cfg(any(feature = "csr", feature = "hydrate"))] #[cfg(any(feature = "csr", feature = "hydrate"))]
{ {
use leptos::{create_render_effect, document, on_cleanup, SignalGet, StoredValue}; // use leptos::{create_render_effect, document, on_cleanup, SignalGet, StoredValue};
let style_el = StoredValue::new(None::<web_sys::Element>); use leptos::prelude::{
document, effect::RenderEffect, on_cleanup, traits::Get, StoredValue,
};
use send_wrapper::SendWrapper;
let style_el = StoredValue::new(SendWrapper::new(None::<web_sys::Element>));
let remove_style_el = move || { let remove_style_el = move || {
style_el.update_value(move |el| { style_el.update_value(move |el| {
if let Some(el) = el.take() { if let Some(el) = Option::take(el) {
let head = document().head().expect("head no exist"); let head = document().head().expect("head no exist");
_ = head.remove_child(&el); _ = head.remove_child(&el);
} }
}); });
}; };
create_render_effect(move |_| { RenderEffect::new(move |_| {
if is_lock.get() { if is_lock.get() {
let head = document().head().expect("head no exist"); let head = document().head().expect("head no exist");
let style = document() let style = document()
@ -23,7 +28,9 @@ pub fn use_lock_html_scroll(is_lock: MaybeSignal<bool>) {
_ = style.set_attribute("data-id", &format!("thaw-lock-html-scroll")); _ = style.set_attribute("data-id", &format!("thaw-lock-html-scroll"));
style.set_text_content(Some("html { overflow: hidden; }")); style.set_text_content(Some("html { overflow: hidden; }"));
_ = head.append_child(&style); _ = head.append_child(&style);
style_el.set_value(Some(style)); style_el.update_value(move |el| {
*el = SendWrapper::new(Some(style));
});
} else { } else {
remove_style_el(); remove_style_el();
} }

View file

@ -1,24 +1,35 @@
// use leptos::{
// leptos_dom::helpers::AnimationFrameRequestHandle, on_cleanup,
// request_animation_frame_with_handle, StoredValue,
// };
use leptos::{ use leptos::{
leptos_dom::helpers::AnimationFrameRequestHandle, on_cleanup, prelude::{request_animation_frame_with_handle, AnimationFrameRequestHandle},
request_animation_frame_with_handle, StoredValue, reactive_graph::owner::{on_cleanup, StoredValue},
}; };
pub fn use_next_frame() -> NextFrame { #[derive(Clone)]
let next_frame = NextFrame::default();
on_cleanup(move || {
next_frame.cancel();
});
next_frame
}
#[derive(Default, Clone)]
pub struct NextFrame(StoredValue<Option<AnimationFrameRequestHandle>>); pub struct NextFrame(StoredValue<Option<AnimationFrameRequestHandle>>);
impl Default for NextFrame {
fn default() -> Self {
Self(StoredValue::new(None))
}
}
impl Copy for NextFrame {} impl Copy for NextFrame {}
impl NextFrame { impl NextFrame {
pub fn use_() -> Self {
let next_frame = NextFrame::default();
on_cleanup(move || {
next_frame.cancel();
});
next_frame
}
pub fn run(&self, cb: impl FnOnce() + 'static) { pub fn run(&self, cb: impl FnOnce() + 'static) {
self.cancel(); self.cancel();

View file

@ -7,15 +7,11 @@ mod signals;
mod throttle; mod throttle;
mod time; mod time;
pub use dom::{get_scroll_parent, mount_dynamic_style, mount_style}; pub use dom::{mount_dynamic_style, mount_style};
pub use event_listener::{ pub use event_listener::{add_event_listener, add_event_listener_with_bool, EventListenerHandle};
add_event_listener, add_event_listener_with_bool, EventListenerHandle, IntoEventTarget, pub use hooks::{use_click_position, use_lock_html_scroll, NextFrame};
};
pub use hooks::{use_click_position, use_lock_html_scroll, use_next_frame, NextFrame};
pub use optional_prop::OptionalProp; pub use optional_prop::OptionalProp;
pub use signals::{ pub use signals::{ComponentRef, Model, OptionalMaybeSignal, SignalWatch, StoredMaybeSignal};
create_component_ref, ComponentRef, Model, OptionalMaybeSignal, SignalWatch, StoredMaybeSignal,
};
pub use throttle::throttle; pub use throttle::throttle;
pub use time::now_date; pub use time::now_date;

View file

@ -66,19 +66,19 @@ impl From<String> for OptionalProp<MaybeSignal<String>> {
} }
} }
impl<T> From<ReadSignal<T>> for OptionalProp<MaybeSignal<T>> { impl<T: Send + Sync> From<ReadSignal<T>> for OptionalProp<MaybeSignal<T>> {
fn from(value: ReadSignal<T>) -> Self { fn from(value: ReadSignal<T>) -> Self {
Self(Some(MaybeSignal::from(value))) Self(Some(MaybeSignal::from(value)))
} }
} }
impl<T> From<RwSignal<T>> for OptionalProp<MaybeSignal<T>> { impl<T: Send + Sync> From<RwSignal<T>> for OptionalProp<MaybeSignal<T>> {
fn from(value: RwSignal<T>) -> Self { fn from(value: RwSignal<T>) -> Self {
Self(Some(MaybeSignal::from(value))) Self(Some(MaybeSignal::from(value)))
} }
} }
impl<T> From<Memo<T>> for OptionalProp<MaybeSignal<T>> { impl<T: Send + Sync> From<Memo<T>> for OptionalProp<MaybeSignal<T>> {
fn from(value: Memo<T>) -> Self { fn from(value: Memo<T>) -> Self {
Self(Some(MaybeSignal::from(value))) Self(Some(MaybeSignal::from(value)))
} }

View file

@ -1,14 +1,18 @@
use leptos::{ use leptos::{
create_render_effect, create_rw_signal, logging::debug_warn, RwSignal, SignalGet, logging::debug_warn,
SignalGetUntracked, SignalUpdate, reactive_graph::{
effect::RenderEffect,
signal::RwSignal,
traits::{Get, GetUntracked, Update},
},
}; };
use std::cell::Cell; use std::cell::Cell;
pub struct ComponentRef<T: 'static>(RwSignal<Option<T>>); pub struct ComponentRef<T: 'static>(RwSignal<Option<T>>);
impl<T> Default for ComponentRef<T> { impl<T: Send + Sync> Default for ComponentRef<T> {
fn default() -> Self { fn default() -> Self {
Self(create_rw_signal(None)) Self(RwSignal::new(None))
} }
} }
@ -20,11 +24,14 @@ impl<T> Clone for ComponentRef<T> {
impl<T: 'static> Copy for ComponentRef<T> {} impl<T: 'static> Copy for ComponentRef<T> {}
impl<T> ComponentRef<T> { // TODO
impl<T: Send + Sync> ComponentRef<T> {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
}
impl<T> ComponentRef<T> {
pub fn get(&self) -> Option<T> pub fn get(&self) -> Option<T>
where where
T: Clone, T: Clone,
@ -58,14 +65,10 @@ impl<T> ComponentRef<T> {
{ {
let f = Cell::new(Some(f)); let f = Cell::new(Some(f));
create_render_effect(move |_| { RenderEffect::new(move |_| {
if let Some(comp) = self.get() { if let Some(comp) = self.get() {
f.take().unwrap()(comp); f.take().unwrap()(comp);
} }
}); });
} }
} }
pub fn create_component_ref<T>() -> ComponentRef<T> {
ComponentRef::default()
}

View file

@ -4,7 +4,7 @@ mod optional_maybe_signal;
mod signal_watch; mod signal_watch;
mod stored_maybe_signal; mod stored_maybe_signal;
pub use component_ref::{create_component_ref, ComponentRef}; pub use component_ref::ComponentRef;
pub use model::Model; pub use model::Model;
pub use optional_maybe_signal::OptionalMaybeSignal; pub use optional_maybe_signal::OptionalMaybeSignal;
pub use signal_watch::SignalWatch; pub use signal_watch::SignalWatch;

View file

@ -1,6 +1,8 @@
use leptos::{ use leptos::reactive_graph::{
Memo, ReadSignal, RwSignal, Signal, SignalGet, SignalGetUntracked, SignalSet, SignalUpdate, computed::Memo,
SignalWith, SignalWithUntracked, WriteSignal, signal::{ReadSignal, RwSignal, WriteSignal},
traits::{DefinedAt, IsDisposed, Set, Update, With, WithUntracked},
wrappers::read::Signal,
}; };
pub struct Model<T> pub struct Model<T>
@ -12,7 +14,7 @@ where
on_write: Option<WriteSignal<T>>, on_write: Option<WriteSignal<T>>,
} }
impl<T: Default> Default for Model<T> { impl<T: Default + Send + Sync> Default for Model<T> {
fn default() -> Self { fn default() -> Self {
RwSignal::new(Default::default()).into() RwSignal::new(Default::default()).into()
} }
@ -26,7 +28,7 @@ impl<T> Clone for Model<T> {
impl<T> Copy for Model<T> {} impl<T> Copy for Model<T> {}
impl<T> Model<T> { impl<T: Send + Sync> Model<T> {
fn new(value: T) -> Self { fn new(value: T) -> Self {
let rw_signal = RwSignal::new(value); let rw_signal = RwSignal::new(value);
rw_signal.into() rw_signal.into()
@ -37,91 +39,56 @@ impl<T> Model<T> {
} }
} }
impl<T: Clone> SignalGet for Model<T> { impl<T> DefinedAt for Model<T> {
type Value = T; fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> {
todo!()
fn get(&self) -> Self::Value {
self.read.get()
}
fn try_get(&self) -> Option<Self::Value> {
self.read.try_get()
} }
} }
impl<T: Clone> SignalGetUntracked for Model<T> { impl<T: Send + Sync> With for Model<T> {
type Value = T; type Value = T;
fn get_untracked(&self) -> Self::Value {
self.read.get_untracked()
}
fn try_get_untracked(&self) -> Option<Self::Value> {
self.read.try_get_untracked()
}
}
impl<T: Clone> SignalSet for Model<T> {
type Value = T;
fn set(&self, new_value: Self::Value) {
if let Some(on_write) = self.on_write.as_ref() {
on_write.set(new_value.clone());
}
self.write.set(new_value);
}
fn try_set(&self, new_value: Self::Value) -> Option<Self::Value> {
if let Some(on_write) = self.on_write.as_ref() {
on_write.try_set(new_value.clone());
}
self.write.try_set(new_value)
}
}
impl<T> SignalWith for Model<T> {
type Value = T;
fn with<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> O {
self.read.with(f)
}
fn try_with<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> Option<O> { fn try_with<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> Option<O> {
self.read.try_with(f) self.read.try_with(f)
} }
} }
impl<T> SignalWithUntracked for Model<T> { impl<T: Send + Sync> WithUntracked for Model<T> {
type Value = T; type Value = T;
fn with_untracked<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> O {
self.read.with_untracked(f)
}
fn try_with_untracked<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> Option<O> { fn try_with_untracked<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> Option<O> {
self.read.try_with_untracked(f) self.read.try_with_untracked(f)
} }
} }
impl<T> SignalUpdate for Model<T> { // TODO
impl<T: Send + Sync + Clone> Update for Model<T> {
type Value = T; type Value = T;
fn update(&self, f: impl FnOnce(&mut Self::Value)) { fn try_maybe_update<U>(&self, fun: impl FnOnce(&mut Self::Value) -> (bool, U)) -> Option<U> {
self.write.update(f); let value = self.write.try_maybe_update(fun);
}
fn try_update<O>(&self, f: impl FnOnce(&mut Self::Value) -> O) -> Option<O> { if let Some(on_write) = self.on_write.as_ref() {
self.write.try_update(f) on_write.set(self.read.with_untracked(|read| read.clone()));
}
value
} }
} }
impl<T> From<T> for Model<T> { impl<T> IsDisposed for Model<T> {
fn is_disposed(&self) -> bool {
self.write.is_disposed()
}
}
impl<T: Send + Sync> From<T> for Model<T> {
fn from(value: T) -> Self { fn from(value: T) -> Self {
Self::new(value) Self::new(value)
} }
} }
impl<T> From<RwSignal<T>> for Model<T> { impl<T: Send + Sync> From<RwSignal<T>> for Model<T> {
fn from(rw_signal: RwSignal<T>) -> Self { fn from(rw_signal: RwSignal<T>) -> Self {
let (read, write) = rw_signal.split(); let (read, write) = rw_signal.split();
Self { Self {
@ -142,7 +109,7 @@ impl<T> From<(Signal<T>, WriteSignal<T>)> for Model<T> {
} }
} }
impl<T> From<(ReadSignal<T>, WriteSignal<T>)> for Model<T> { impl<T: Send + Sync> From<(ReadSignal<T>, WriteSignal<T>)> for Model<T> {
fn from((read, write): (ReadSignal<T>, WriteSignal<T>)) -> Self { fn from((read, write): (ReadSignal<T>, WriteSignal<T>)) -> Self {
Self { Self {
read: read.into(), read: read.into(),
@ -152,7 +119,7 @@ impl<T> From<(ReadSignal<T>, WriteSignal<T>)> for Model<T> {
} }
} }
impl<T> From<(Memo<T>, WriteSignal<T>)> for Model<T> { impl<T: Send + Sync> From<(Memo<T>, WriteSignal<T>)> for Model<T> {
fn from((read, write): (Memo<T>, WriteSignal<T>)) -> Self { fn from((read, write): (Memo<T>, WriteSignal<T>)) -> Self {
Self { Self {
read: read.into(), read: read.into(),
@ -162,7 +129,7 @@ impl<T> From<(Memo<T>, WriteSignal<T>)> for Model<T> {
} }
} }
impl<T: Default> From<(Option<T>, WriteSignal<T>)> for Model<T> { impl<T: Default + Send + Sync> From<(Option<T>, WriteSignal<T>)> for Model<T> {
fn from((read, write): (Option<T>, WriteSignal<T>)) -> Self { fn from((read, write): (Option<T>, WriteSignal<T>)) -> Self {
let mut model = Self::new(read.unwrap_or_default()); let mut model = Self::new(read.unwrap_or_default());
model.on_write = Some(write); model.on_write = Some(write);
@ -170,35 +137,36 @@ impl<T: Default> From<(Option<T>, WriteSignal<T>)> for Model<T> {
} }
} }
#[cfg(test)] // TODO
mod test { // #[cfg(test)]
use super::Model; // mod test {
use leptos::*; // use super::Model;
// use leptos::*;
#[test] // #[test]
fn from() { // fn from() {
let runtime = create_runtime(); // let runtime = create_runtime();
// T // // T
let model: Model<i32> = 0.into(); // let model: Model<i32> = 0.into();
assert_eq!(model.get_untracked(), 0); // assert_eq!(model.get_untracked(), 0);
model.set(1); // model.set(1);
assert_eq!(model.get_untracked(), 1); // assert_eq!(model.get_untracked(), 1);
// RwSignal // // RwSignal
let rw_signal = RwSignal::new(0); // let rw_signal = RwSignal::new(0);
let model: Model<i32> = rw_signal.into(); // let model: Model<i32> = rw_signal.into();
assert_eq!(model.get_untracked(), 0); // assert_eq!(model.get_untracked(), 0);
model.set(1); // model.set(1);
assert_eq!(model.get_untracked(), 1); // assert_eq!(model.get_untracked(), 1);
// Read Write // // Read Write
let (read, write) = create_signal(0); // let (read, write) = create_signal(0);
let model: Model<i32> = (read, write).into(); // let model: Model<i32> = (read, write).into();
assert_eq!(model.get_untracked(), 0); // assert_eq!(model.get_untracked(), 0);
model.set(1); // model.set(1);
assert_eq!(model.get_untracked(), 1); // assert_eq!(model.get_untracked(), 1);
runtime.dispose(); // runtime.dispose();
} // }
} // }

View file

@ -1,4 +1,4 @@
use leptos::*; use leptos::prelude::*;
use std::ops::Deref; use std::ops::Deref;
pub struct OptionalMaybeSignal<T: 'static>(MaybeSignal<Option<T>>); pub struct OptionalMaybeSignal<T: 'static>(MaybeSignal<Option<T>>);
@ -37,19 +37,19 @@ impl<T> From<Option<T>> for OptionalMaybeSignal<T> {
} }
} }
impl<T> From<ReadSignal<Option<T>>> for OptionalMaybeSignal<T> { impl<T: Send + Sync> From<ReadSignal<Option<T>>> for OptionalMaybeSignal<T> {
fn from(value: ReadSignal<Option<T>>) -> Self { fn from(value: ReadSignal<Option<T>>) -> Self {
Self(MaybeSignal::Dynamic(value.into())) Self(MaybeSignal::Dynamic(value.into()))
} }
} }
impl<T> From<RwSignal<Option<T>>> for OptionalMaybeSignal<T> { impl<T: Send + Sync> From<RwSignal<Option<T>>> for OptionalMaybeSignal<T> {
fn from(value: RwSignal<Option<T>>) -> Self { fn from(value: RwSignal<Option<T>>) -> Self {
Self(MaybeSignal::Dynamic(value.into())) Self(MaybeSignal::Dynamic(value.into()))
} }
} }
impl<T> From<Memo<Option<T>>> for OptionalMaybeSignal<T> { impl<T: Send + Sync> From<Memo<Option<T>>> for OptionalMaybeSignal<T> {
fn from(value: Memo<Option<T>>) -> Self { fn from(value: Memo<Option<T>>) -> Self {
Self(MaybeSignal::Dynamic(value.into())) Self(MaybeSignal::Dynamic(value.into()))
} }
@ -67,19 +67,20 @@ impl<T> From<MaybeSignal<Option<T>>> for OptionalMaybeSignal<T> {
} }
} }
#[cfg(test)] // TODO
mod test { // #[cfg(test)]
use super::OptionalMaybeSignal; // mod test {
use leptos::{create_runtime, MaybeSignal}; // use super::OptionalMaybeSignal;
// use leptos::{create_runtime, MaybeSignal};
#[test] // #[test]
fn into() { // fn into() {
let runtime = create_runtime(); // let runtime = create_runtime();
let _: MaybeSignal<i32> = 12.into(); // let _: MaybeSignal<i32> = 12.into();
let _: OptionalMaybeSignal<i32> = Some(12).into(); // let _: OptionalMaybeSignal<i32> = Some(12).into();
let _: OptionalMaybeSignal<i32> = MaybeSignal::Static(Some(12)).into(); // let _: OptionalMaybeSignal<i32> = MaybeSignal::Static(Some(12)).into();
runtime.dispose(); // runtime.dispose();
} // }
} // }

View file

@ -1,4 +1,4 @@
use leptos::{create_effect, untrack, RwSignal, SignalDispose, SignalWith}; use leptos::reactive_graph::{effect::Effect, signal::RwSignal, traits::{Dispose, With}, untrack};
pub trait SignalWatch { pub trait SignalWatch {
type Value; type Value;
@ -6,7 +6,7 @@ pub trait SignalWatch {
fn watch(&self, f: impl Fn(&Self::Value) + 'static) -> Box<dyn FnOnce()>; fn watch(&self, f: impl Fn(&Self::Value) + 'static) -> Box<dyn FnOnce()>;
} }
impl<T> SignalWatch for RwSignal<T> { impl<T: 'static> SignalWatch for RwSignal<T> {
type Value = T; type Value = T;
/// Listens for RwSignal changes and is not executed immediately /// Listens for RwSignal changes and is not executed immediately
@ -31,7 +31,7 @@ impl<T> SignalWatch for RwSignal<T> {
fn watch(&self, f: impl Fn(&Self::Value) + 'static) -> Box<dyn FnOnce()> { fn watch(&self, f: impl Fn(&Self::Value) + 'static) -> Box<dyn FnOnce()> {
let signal = *self; let signal = *self;
let effect = create_effect(move |prev| { let effect = Effect::new(move |prev| {
signal.with(|value| { signal.with(|value| {
if prev.is_some() { if prev.is_some() {
untrack(|| f(value)); untrack(|| f(value));

View file

@ -1,6 +1,7 @@
use leptos::{ use leptos::reactive_graph::{
MaybeSignal, Signal, SignalGet, SignalGetUntracked, SignalWith, SignalWithUntracked, owner::StoredValue,
StoredValue, traits::{DefinedAt, With, WithUntracked},
wrappers::read::{MaybeSignal, Signal},
}; };
#[derive(Clone)] #[derive(Clone)]
@ -14,52 +15,18 @@ where
impl<T: Clone> Copy for StoredMaybeSignal<T> {} impl<T: Clone> Copy for StoredMaybeSignal<T> {}
impl<T: Clone> SignalGet for StoredMaybeSignal<T> { impl<T> DefinedAt for StoredMaybeSignal<T> {
type Value = T; fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> {
fn get(&self) -> Self::Value {
match self { match self {
StoredMaybeSignal::StoredValue(value) => value.get_value(), StoredMaybeSignal::StoredValue(value) => value.defined_at(),
StoredMaybeSignal::Signal(signal) => signal.get(), StoredMaybeSignal::Signal(signal) => signal.defined_at(),
}
}
fn try_get(&self) -> Option<Self::Value> {
match self {
StoredMaybeSignal::StoredValue(value) => value.try_get_value(),
StoredMaybeSignal::Signal(signal) => signal.try_get(),
} }
} }
} }
impl<T: Clone> SignalGetUntracked for StoredMaybeSignal<T> { impl<T: Send + Sync> With for StoredMaybeSignal<T> {
type Value = T; type Value = T;
fn get_untracked(&self) -> Self::Value {
match self {
StoredMaybeSignal::StoredValue(value) => value.get_value(),
StoredMaybeSignal::Signal(signal) => signal.get_untracked(),
}
}
fn try_get_untracked(&self) -> Option<Self::Value> {
match self {
StoredMaybeSignal::StoredValue(value) => value.try_get_value(),
StoredMaybeSignal::Signal(signal) => signal.try_get_untracked(),
}
}
}
impl<T> SignalWith for StoredMaybeSignal<T> {
type Value = T;
fn with<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> O {
match self {
StoredMaybeSignal::StoredValue(value) => value.with_value(f),
StoredMaybeSignal::Signal(signal) => signal.with(f),
}
}
fn try_with<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> Option<O> { fn try_with<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> Option<O> {
match self { match self {
StoredMaybeSignal::StoredValue(value) => value.try_with_value(f), StoredMaybeSignal::StoredValue(value) => value.try_with_value(f),
@ -68,16 +35,9 @@ impl<T> SignalWith for StoredMaybeSignal<T> {
} }
} }
impl<T> SignalWithUntracked for StoredMaybeSignal<T> { impl<T: Send + Sync> WithUntracked for StoredMaybeSignal<T> {
type Value = T; type Value = T;
fn with_untracked<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> O {
match self {
StoredMaybeSignal::StoredValue(value) => value.with_value(f),
StoredMaybeSignal::Signal(signal) => signal.with_untracked(f),
}
}
fn try_with_untracked<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> Option<O> { fn try_with_untracked<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> Option<O> {
match self { match self {
StoredMaybeSignal::StoredValue(value) => value.try_with_value(f), StoredMaybeSignal::StoredValue(value) => value.try_with_value(f),
@ -86,7 +46,7 @@ impl<T> SignalWithUntracked for StoredMaybeSignal<T> {
} }
} }
impl<T> From<MaybeSignal<T>> for StoredMaybeSignal<T> { impl<T: Send + Sync> From<MaybeSignal<T>> for StoredMaybeSignal<T> {
fn from(value: MaybeSignal<T>) -> Self { fn from(value: MaybeSignal<T>) -> Self {
match value { match value {
MaybeSignal::Static(value) => Self::StoredValue(StoredValue::new(value)), MaybeSignal::Static(value) => Self::StoredValue(StoredValue::new(value)),

View file

@ -1,7 +1,7 @@
use leptos::{leptos_dom::helpers::TimeoutHandle, prelude::*}; use leptos::{leptos_dom::helpers::TimeoutHandle, prelude::*};
use std::time::Duration; use std::time::Duration;
pub fn throttle(cb: impl Fn() + 'static, duration: Duration) -> impl Fn() -> () { pub fn throttle(cb: impl Fn() + Send + Sync + 'static, duration: Duration) -> impl Fn() -> () {
let cb = Callback::new(move |_| cb()); let cb = Callback::new(move |_| cb());
let timeout_handle = StoredValue::new(None::<TimeoutHandle>); let timeout_handle = StoredValue::new(None::<TimeoutHandle>);
on_cleanup(move || { on_cleanup(move || {
@ -16,6 +16,7 @@ pub fn throttle(cb: impl Fn() + 'static, duration: Duration) -> impl Fn() -> ()
if timeout_handle.with_value(|handle| handle.is_some()) { if timeout_handle.with_value(|handle| handle.is_some()) {
return; return;
} }
let cb = cb.clone();
let handle = set_timeout_with_handle( let handle = set_timeout_with_handle(
move || { move || {
cb.call(()); cb.call(());