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
[dependencies]
leptos = { version = "0.6.10" }
leptos = { workspace = true }
thaw_utils = { workspace = true }
web-sys = { version = "0.3.69", features = ["DomRect"] }
cfg-if = "1.0.0"

View file

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

View file

@ -1,10 +1,13 @@
#[cfg(not(feature = "ssr"))]
use leptos::create_render_effect;
use leptos::prelude::RenderEffect;
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>>>);
impl ClassList {
@ -30,7 +33,7 @@ impl ClassList {
});
}
#[cfg(not(feature = "ssr"))]
create_render_effect(move |old_name| {
RenderEffect::new(move |old_name| {
let name = f();
if let Some(old_name) = old_name {
if old_name != name {
@ -57,7 +60,7 @@ impl ClassList {
}
}
#[cfg(not(feature = "ssr"))]
create_render_effect(move |old_name| {
RenderEffect::new(move |old_name| {
let name = f();
if let Some(old_name) = old_name {
if old_name != name {
@ -96,7 +99,7 @@ impl ClassList {
});
}
#[cfg(not(feature = "ssr"))]
create_render_effect(move |old| {
RenderEffect::new(move |old| {
let name = name.clone();
let new = f();
if old.is_none() {
@ -121,13 +124,9 @@ impl ClassList {
self
}
}
impl IntoAttribute for ClassList {
fn into_attribute(self) -> Attribute {
Attribute::Fn(Rc::new(move || {
fn to_class_string(self, class: &mut String) {
self.0.with(|set| {
let mut class = String::new();
set.iter().enumerate().for_each(|(index, name)| {
if name.is_empty() {
return;
@ -137,13 +136,77 @@ impl IntoAttribute for ClassList {
}
class.push_str(name)
});
class.into_attribute()
})
}))
});
}
}
fn into_attribute_boxed(self: Box<Self>) -> Attribute {
self.into_attribute()
impl<'a, R> leptos::tachys::html::class::IntoClass<R> for ClassList
where
R: DomRenderer,
{
type State = (R::ClassList, String);
type Cloneable = Self;
type CloneableOwned = Self;
fn html_len(&self) -> usize {
self.0.with_untracked(|set| {
let mut len = 0;
set.iter().enumerate().for_each(|(index, name)| {
if name.is_empty() {
return;
}
if index != 0 {
len += 1;
}
len += name.len();
});
len
})
}
fn to_html(self, class: &mut String) {
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)]
mod tests {
use leptos::{create_runtime, Attribute, IntoAttribute};
// TODO
// #[cfg(test)]
// mod tests {
// use leptos::reactive_graph::Run;
#[test]
fn macro_class_list() {
let rt = create_runtime();
let class_list = class_list!("aa", ("bb", || true), move || "cc");
if let Attribute::Fn(f) = class_list.into_attribute() {
if let Attribute::String(class) = f() {
assert!(class.contains("aa"));
assert!(class.contains("bb"));
assert!(class.contains("cc"));
}
}
rt.dispose();
}
}
// #[test]
// fn macro_class_list() {
// let rt = create_runtime();
// let class_list = class_list!("aa", ("bb", || true), move || "cc");
// if let Attribute::Fn(f) = class_list.into_attribute() {
// if let Attribute::String(class) = f() {
// assert!(class.contains("aa"));
// assert!(class.contains("bb"));
// assert!(class.contains("cc"));
// }
// }
// rt.dispose();
// }
// }

View file

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

View file

@ -1,5 +1,5 @@
mod get_scroll_parent;
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};

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);
meta.tags.register(format!("leptos-thaw-{id}").into(), style_el.into_any());
} else {
use leptos::document;
use leptos::prelude::document;
let head = document().head().expect("head no exist");
let style = head
.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! {
if #[cfg(feature = "ssr")] {
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);
meta.tags.register(format!("leptos-thaw-{id}").into(), style_el.into_any());
} else {
use leptos::document;
use leptos::prelude::document;
use send_wrapper::SendWrapper;
let head = document().head().expect("head no exist");
let style = head
.query_selector(&format!("style[data-thaw-id=\"{id}\"]"))
.expect("query style element error");
#[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 {
.expect("query style element error").unwrap_or_else(|| {
let style = document()
.create_element("style")
.expect("create style element error");
_ = style.set_attribute("data-thaw-id", &id);
style.set_text_content(Some(&content));
_ = 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 leptos::{
ev,
tachys::{renderer::DomRenderer, view::any_view::AnyView},
};
use std::ops::Deref;
use leptos::{ev, tachys::renderer::DomRenderer};
use web_sys::EventTarget;
pub fn add_event_listener<E>(
@ -15,9 +11,10 @@ where
E: ev::EventDescriptor + 'static,
E::EventType: JsCast,
{
add_event_listener_untyped(target, &event.name(), move |e| {
cb(e.unchecked_into::<E::EventType>())
})
todo!()
// add_event_listener_untyped(target, &event.name(), move |e| {
// cb(e.unchecked_into::<E::EventType>())
// })
}
pub struct EventListenerHandle(Box<dyn FnOnce()>);
@ -34,26 +31,26 @@ impl EventListenerHandle {
}
}
fn add_event_listener_untyped(
target: impl DomRenderer,
event_name: &str,
cb: impl Fn(web_sys::Event) + 'static,
) -> EventListenerHandle {
fn wel(
target: impl DomRenderer,
cb: Box<dyn FnMut(web_sys::Event)>,
event_name: &str,
) -> EventListenerHandle {
let cb = Closure::wrap(cb).into_js_value();
_ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref());
let event_name = event_name.to_string();
EventListenerHandle(Box::new(move || {
_ = target.remove_event_listener_with_callback(&event_name, cb.unchecked_ref());
}))
}
// fn add_event_listener_untyped(
// target: impl DomRenderer,
// event_name: &str,
// cb: impl Fn(web_sys::Event) + 'static,
// ) -> EventListenerHandle {
// fn wel(
// target: impl DomRenderer,
// cb: Box<dyn FnMut(web_sys::Event)>,
// event_name: &str,
// ) -> EventListenerHandle {
// let cb = Closure::wrap(cb).into_js_value();
// _ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref());
// let event_name = event_name.to_string();
// EventListenerHandle(Box::new(move || {
// _ = 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>(
target: impl IntoEventTarget,
@ -119,8 +116,8 @@ impl IntoEventTarget for web_sys::Document {
}
}
impl IntoEventTarget for HtmlElement<AnyElement> {
fn into_event_target(self) -> EventTarget {
self.deref().deref().deref().deref().clone()
}
}
// impl IntoEventTarget for HtmlElement<AnyElement> {
// fn into_event_target(self) -> EventTarget {
// 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_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)>> {
let mouse_position = RwSignal::new(None);
#[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 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>) {
#[cfg(any(feature = "csr", feature = "hydrate"))]
{
use leptos::{create_render_effect, document, on_cleanup, SignalGet, StoredValue};
let style_el = StoredValue::new(None::<web_sys::Element>);
// use leptos::{create_render_effect, document, on_cleanup, SignalGet, StoredValue};
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 || {
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");
_ = head.remove_child(&el);
}
});
};
create_render_effect(move |_| {
RenderEffect::new(move |_| {
if is_lock.get() {
let head = document().head().expect("head no exist");
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_text_content(Some("html { overflow: hidden; }"));
_ = head.append_child(&style);
style_el.set_value(Some(style));
style_el.update_value(move |el| {
*el = SendWrapper::new(Some(style));
});
} else {
remove_style_el();
}

View file

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

View file

@ -7,15 +7,11 @@ mod signals;
mod throttle;
mod time;
pub use dom::{get_scroll_parent, mount_dynamic_style, mount_style};
pub use event_listener::{
add_event_listener, add_event_listener_with_bool, EventListenerHandle, IntoEventTarget,
};
pub use hooks::{use_click_position, use_lock_html_scroll, use_next_frame, NextFrame};
pub use dom::{mount_dynamic_style, mount_style};
pub use event_listener::{add_event_listener, add_event_listener_with_bool, EventListenerHandle};
pub use hooks::{use_click_position, use_lock_html_scroll, NextFrame};
pub use optional_prop::OptionalProp;
pub use signals::{
create_component_ref, ComponentRef, Model, OptionalMaybeSignal, SignalWatch, StoredMaybeSignal,
};
pub use signals::{ComponentRef, Model, OptionalMaybeSignal, SignalWatch, StoredMaybeSignal};
pub use throttle::throttle;
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 {
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 {
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 {
Self(Some(MaybeSignal::from(value)))
}

View file

@ -1,14 +1,18 @@
use leptos::{
create_render_effect, create_rw_signal, logging::debug_warn, RwSignal, SignalGet,
SignalGetUntracked, SignalUpdate,
logging::debug_warn,
reactive_graph::{
effect::RenderEffect,
signal::RwSignal,
traits::{Get, GetUntracked, Update},
},
};
use std::cell::Cell;
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 {
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> ComponentRef<T> {
// TODO
impl<T: Send + Sync> ComponentRef<T> {
pub fn new() -> Self {
Self::default()
}
}
impl<T> ComponentRef<T> {
pub fn get(&self) -> Option<T>
where
T: Clone,
@ -58,14 +65,10 @@ impl<T> ComponentRef<T> {
{
let f = Cell::new(Some(f));
create_render_effect(move |_| {
RenderEffect::new(move |_| {
if let Some(comp) = self.get() {
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 stored_maybe_signal;
pub use component_ref::{create_component_ref, ComponentRef};
pub use component_ref::ComponentRef;
pub use model::Model;
pub use optional_maybe_signal::OptionalMaybeSignal;
pub use signal_watch::SignalWatch;

View file

@ -1,6 +1,8 @@
use leptos::{
Memo, ReadSignal, RwSignal, Signal, SignalGet, SignalGetUntracked, SignalSet, SignalUpdate,
SignalWith, SignalWithUntracked, WriteSignal,
use leptos::reactive_graph::{
computed::Memo,
signal::{ReadSignal, RwSignal, WriteSignal},
traits::{DefinedAt, IsDisposed, Set, Update, With, WithUntracked},
wrappers::read::Signal,
};
pub struct Model<T>
@ -12,7 +14,7 @@ where
on_write: Option<WriteSignal<T>>,
}
impl<T: Default> Default for Model<T> {
impl<T: Default + Send + Sync> Default for Model<T> {
fn default() -> Self {
RwSignal::new(Default::default()).into()
}
@ -26,7 +28,7 @@ impl<T> Clone for Model<T> {
impl<T> Copy for Model<T> {}
impl<T> Model<T> {
impl<T: Send + Sync> Model<T> {
fn new(value: T) -> Self {
let rw_signal = RwSignal::new(value);
rw_signal.into()
@ -37,91 +39,56 @@ impl<T> Model<T> {
}
}
impl<T: Clone> SignalGet for Model<T> {
impl<T> DefinedAt for Model<T> {
fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> {
todo!()
}
}
impl<T: Send + Sync> With for Model<T> {
type Value = T;
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> {
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> {
self.read.try_with(f)
}
}
impl<T> SignalWithUntracked for Model<T> {
impl<T: Send + Sync> WithUntracked for Model<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> {
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;
fn update(&self, f: impl FnOnce(&mut Self::Value)) {
self.write.update(f);
fn try_maybe_update<U>(&self, fun: impl FnOnce(&mut Self::Value) -> (bool, U)) -> Option<U> {
let value = self.write.try_maybe_update(fun);
if let Some(on_write) = self.on_write.as_ref() {
on_write.set(self.read.with_untracked(|read| read.clone()));
}
fn try_update<O>(&self, f: impl FnOnce(&mut Self::Value) -> O) -> Option<O> {
self.write.try_update(f)
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 {
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 {
let (read, write) = rw_signal.split();
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 {
Self {
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 {
Self {
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 {
let mut model = Self::new(read.unwrap_or_default());
model.on_write = Some(write);
@ -170,35 +137,36 @@ impl<T: Default> From<(Option<T>, WriteSignal<T>)> for Model<T> {
}
}
#[cfg(test)]
mod test {
use super::Model;
use leptos::*;
// TODO
// #[cfg(test)]
// mod test {
// use super::Model;
// use leptos::*;
#[test]
fn from() {
let runtime = create_runtime();
// #[test]
// fn from() {
// let runtime = create_runtime();
// T
let model: Model<i32> = 0.into();
assert_eq!(model.get_untracked(), 0);
model.set(1);
assert_eq!(model.get_untracked(), 1);
// // T
// let model: Model<i32> = 0.into();
// assert_eq!(model.get_untracked(), 0);
// model.set(1);
// assert_eq!(model.get_untracked(), 1);
// RwSignal
let rw_signal = RwSignal::new(0);
let model: Model<i32> = rw_signal.into();
assert_eq!(model.get_untracked(), 0);
model.set(1);
assert_eq!(model.get_untracked(), 1);
// // RwSignal
// let rw_signal = RwSignal::new(0);
// let model: Model<i32> = rw_signal.into();
// assert_eq!(model.get_untracked(), 0);
// model.set(1);
// assert_eq!(model.get_untracked(), 1);
// Read Write
let (read, write) = create_signal(0);
let model: Model<i32> = (read, write).into();
assert_eq!(model.get_untracked(), 0);
model.set(1);
assert_eq!(model.get_untracked(), 1);
// // Read Write
// let (read, write) = create_signal(0);
// let model: Model<i32> = (read, write).into();
// assert_eq!(model.get_untracked(), 0);
// model.set(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;
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 {
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 {
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 {
Self(MaybeSignal::Dynamic(value.into()))
}
@ -67,19 +67,20 @@ impl<T> From<MaybeSignal<Option<T>>> for OptionalMaybeSignal<T> {
}
}
#[cfg(test)]
mod test {
use super::OptionalMaybeSignal;
use leptos::{create_runtime, MaybeSignal};
// TODO
// #[cfg(test)]
// mod test {
// use super::OptionalMaybeSignal;
// use leptos::{create_runtime, MaybeSignal};
#[test]
fn into() {
let runtime = create_runtime();
// #[test]
// fn into() {
// let runtime = create_runtime();
let _: MaybeSignal<i32> = 12.into();
let _: OptionalMaybeSignal<i32> = Some(12).into();
let _: OptionalMaybeSignal<i32> = MaybeSignal::Static(Some(12)).into();
// let _: MaybeSignal<i32> = 12.into();
// let _: OptionalMaybeSignal<i32> = 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 {
type Value;
@ -6,7 +6,7 @@ pub trait SignalWatch {
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;
/// 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()> {
let signal = *self;
let effect = create_effect(move |prev| {
let effect = Effect::new(move |prev| {
signal.with(|value| {
if prev.is_some() {
untrack(|| f(value));

View file

@ -1,6 +1,7 @@
use leptos::{
MaybeSignal, Signal, SignalGet, SignalGetUntracked, SignalWith, SignalWithUntracked,
StoredValue,
use leptos::reactive_graph::{
owner::StoredValue,
traits::{DefinedAt, With, WithUntracked},
wrappers::read::{MaybeSignal, Signal},
};
#[derive(Clone)]
@ -14,52 +15,18 @@ where
impl<T: Clone> Copy for StoredMaybeSignal<T> {}
impl<T: Clone> SignalGet for StoredMaybeSignal<T> {
impl<T> DefinedAt for StoredMaybeSignal<T> {
fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> {
match self {
StoredMaybeSignal::StoredValue(value) => value.defined_at(),
StoredMaybeSignal::Signal(signal) => signal.defined_at(),
}
}
}
impl<T: Send + Sync> With for StoredMaybeSignal<T> {
type Value = T;
fn get(&self) -> Self::Value {
match self {
StoredMaybeSignal::StoredValue(value) => value.get_value(),
StoredMaybeSignal::Signal(signal) => signal.get(),
}
}
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> {
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> {
match self {
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;
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> {
match self {
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 {
match value {
MaybeSignal::Static(value) => Self::StoredValue(StoredValue::new(value)),

View file

@ -1,7 +1,7 @@
use leptos::{leptos_dom::helpers::TimeoutHandle, prelude::*};
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 timeout_handle = StoredValue::new(None::<TimeoutHandle>);
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()) {
return;
}
let cb = cb.clone();
let handle = set_timeout_with_handle(
move || {
cb.call(());