mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-29 04:52:46 -05:00
Compare commits
47 Commits
ci
...
two-way-da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3782a8ad70 | ||
|
|
cb11ce0a5f | ||
|
|
37c794b283 | ||
|
|
f5ff45863d | ||
|
|
ca6f934039 | ||
|
|
699c54e16c | ||
|
|
1217ef4d8e | ||
|
|
9aba3efbe4 | ||
|
|
1b48a2f8d5 | ||
|
|
31cb766206 | ||
|
|
de28a317f0 | ||
|
|
d29433b98d | ||
|
|
f2582b6ac9 | ||
|
|
67845be161 | ||
|
|
2bf1f46b88 | ||
|
|
70ae3a0abb | ||
|
|
96e2b5cba1 | ||
|
|
ef27a198d9 | ||
|
|
47299926bb | ||
|
|
bdc2285658 | ||
|
|
9d4ce6e526 | ||
|
|
3d2cdc21a1 | ||
|
|
93d939aef8 | ||
|
|
fb04750607 | ||
|
|
a080496e7e | ||
|
|
9fc1002167 | ||
|
|
bc5c766530 | ||
|
|
17821f863a | ||
|
|
1ca4f34ef3 | ||
|
|
8f0a1554b1 | ||
|
|
38d4f26d03 | ||
|
|
2b04c2710d | ||
|
|
a4937a1236 | ||
|
|
f6f2c39686 | ||
|
|
d7eacf1ab5 | ||
|
|
d1a4bbe28e | ||
|
|
412ecd6b1b | ||
|
|
9bc0152121 | ||
|
|
4b05cada8f | ||
|
|
a818862704 | ||
|
|
173487debc | ||
|
|
449d96cc9a | ||
|
|
f9bf6a95ed | ||
|
|
6ea942f3c5 | ||
|
|
3a079ace46 | ||
|
|
dbf654aa86 | ||
|
|
d16231aa3a |
42
Cargo.toml
42
Cargo.toml
@@ -40,36 +40,36 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.0-beta5"
|
||||
version = "0.7.0-gamma"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
[workspace.dependencies]
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-beta5" }
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-gamma" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.1.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1.0" }
|
||||
either_of = { path = "./either_of/", version = "0.1.0" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-beta5" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-beta5" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-beta5" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-beta5" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-beta5" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-beta5" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-beta5" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-beta5" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-beta5" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-beta5" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-beta5" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-beta5" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-gamma" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-gamma" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-gamma" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-gamma" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-gamma" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-gamma" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-gamma" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-gamma" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-gamma" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-gamma" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-gamma" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-gamma" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-beta5" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-beta5" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-beta5" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-beta5" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-beta5" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-beta5" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-beta5" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-gamma" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-gamma" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-gamma" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-gamma" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-gamma" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-gamma" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-gamma" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "throw_error"
|
||||
version = "0.2.0-beta5"
|
||||
version = "0.2.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "gtk"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos" }
|
||||
throw_error = { path = "../../any_error/" }
|
||||
|
||||
# these are used to build the integration
|
||||
gtk = { version = "0.9.0", package = "gtk4" }
|
||||
next_tuple = { path = "../../next_tuple/" }
|
||||
paste = "1.0"
|
||||
|
||||
# we want to support using glib for the reactive runtime event loop
|
||||
any_spawner = { path = "../../any_spawner/", features = ["glib"] }
|
||||
# yes, we want effects to run: this is a "frontend," not a backend
|
||||
reactive_graph = { path = "../../reactive_graph", features = ["effects"] }
|
||||
@@ -1 +0,0 @@
|
||||
extend = [{ path = "../cargo-make/main.toml" }]
|
||||
@@ -1,8 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="color-scheme" content="dark">
|
||||
<link rel="css" href="style.css" data-trunk>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
@@ -1,645 +0,0 @@
|
||||
use self::properties::Connect;
|
||||
use gtk::{
|
||||
glib::{
|
||||
object::{IsA, IsClass, ObjectExt},
|
||||
Object, Value,
|
||||
},
|
||||
prelude::{Cast, WidgetExt},
|
||||
Label, Orientation, Widget,
|
||||
};
|
||||
use leptos::{
|
||||
reactive_graph::effect::RenderEffect,
|
||||
tachys::{
|
||||
renderer::{CastFrom, Renderer},
|
||||
view::{Mountable, Render},
|
||||
},
|
||||
};
|
||||
use next_tuple::NextTuple;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LeptosGtk;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Element(pub Widget);
|
||||
|
||||
impl Element {
|
||||
pub fn remove(&self) {
|
||||
self.0.unparent();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Text(pub Element);
|
||||
|
||||
impl<T> From<T> for Element
|
||||
where
|
||||
T: Into<Widget>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Element(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mountable<LeptosGtk> for Element {
|
||||
fn unmount(&mut self) {
|
||||
self.remove()
|
||||
}
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
parent: &<LeptosGtk as Renderer>::Element,
|
||||
marker: Option<&<LeptosGtk as Renderer>::Node>,
|
||||
) {
|
||||
self.0
|
||||
.insert_before(&parent.0, marker.as_ref().map(|m| &m.0));
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<LeptosGtk>) -> bool {
|
||||
if let Some(parent) = self.0.parent() {
|
||||
child.mount(&Element(parent), Some(self));
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Mountable<LeptosGtk> for Text {
|
||||
fn unmount(&mut self) {
|
||||
self.0.remove()
|
||||
}
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
parent: &<LeptosGtk as Renderer>::Element,
|
||||
marker: Option<&<LeptosGtk as Renderer>::Node>,
|
||||
) {
|
||||
self.0
|
||||
.0
|
||||
.insert_before(&parent.0, marker.as_ref().map(|m| &m.0));
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<LeptosGtk>) -> bool {
|
||||
self.0.insert_before_this(child)
|
||||
}
|
||||
}
|
||||
|
||||
impl CastFrom<Element> for Element {
|
||||
fn cast_from(source: Element) -> Option<Self> {
|
||||
Some(source)
|
||||
}
|
||||
}
|
||||
|
||||
impl CastFrom<Element> for Text {
|
||||
fn cast_from(source: Element) -> Option<Self> {
|
||||
source
|
||||
.0
|
||||
.downcast::<Label>()
|
||||
.ok()
|
||||
.map(|n| Text(Element::from(n)))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Element> for Element {
|
||||
fn as_ref(&self) -> &Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Element> for Text {
|
||||
fn as_ref(&self) -> &Element {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderer for LeptosGtk {
|
||||
type Node = Element;
|
||||
type Element = Element;
|
||||
type Text = Text;
|
||||
type Placeholder = Element;
|
||||
|
||||
fn intern(text: &str) -> &str {
|
||||
text
|
||||
}
|
||||
|
||||
fn create_text_node(text: &str) -> Self::Text {
|
||||
Text(Element::from(Label::new(Some(text))))
|
||||
}
|
||||
|
||||
fn create_placeholder() -> Self::Placeholder {
|
||||
let label = Label::new(None);
|
||||
label.set_visible(false);
|
||||
Element::from(label)
|
||||
}
|
||||
|
||||
fn set_text(node: &Self::Text, text: &str) {
|
||||
let node_as_text = node.0 .0.downcast_ref::<Label>().unwrap();
|
||||
node_as_text.set_label(text);
|
||||
}
|
||||
|
||||
fn set_attribute(node: &Self::Element, name: &str, value: &str) {
|
||||
node.0.set_property(name, value);
|
||||
}
|
||||
|
||||
fn remove_attribute(node: &Self::Element, name: &str) {
|
||||
node.0.set_property(name, None::<&str>);
|
||||
}
|
||||
|
||||
fn insert_node(
|
||||
parent: &Self::Element,
|
||||
new_child: &Self::Node,
|
||||
marker: Option<&Self::Node>,
|
||||
) {
|
||||
new_child
|
||||
.0
|
||||
.insert_before(&parent.0, marker.as_ref().map(|n| &n.0));
|
||||
}
|
||||
|
||||
fn remove_node(
|
||||
_parent: &Self::Element,
|
||||
_child: &Self::Node,
|
||||
) -> Option<Self::Node> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn remove(_node: &Self::Node) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_parent(node: &Self::Node) -> Option<Self::Node> {
|
||||
node.0.parent().map(Element::from)
|
||||
}
|
||||
|
||||
fn first_child(_node: &Self::Node) -> Option<Self::Node> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn next_sibling(_node: &Self::Node) -> Option<Self::Node> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn log_node(node: &Self::Node) {
|
||||
println!("{node:?}");
|
||||
}
|
||||
|
||||
fn clear_children(_parent: &Self::Element) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root<Chil>(children: Chil) -> (Widget, impl Mountable<LeptosGtk>)
|
||||
where
|
||||
Chil: Render<LeptosGtk>,
|
||||
{
|
||||
let state = r#box()
|
||||
.orientation(Orientation::Vertical)
|
||||
.spacing(12)
|
||||
.child(children)
|
||||
.build();
|
||||
(state.as_widget().clone(), state)
|
||||
}
|
||||
|
||||
pub trait WidgetClass {
|
||||
type Widget: Into<Widget> + IsA<Object> + IsClass;
|
||||
}
|
||||
|
||||
pub struct LGtkWidget<Widg, Props, Chil> {
|
||||
widget: PhantomData<Widg>,
|
||||
properties: Props,
|
||||
children: Chil,
|
||||
}
|
||||
|
||||
impl<Widg, Props, Chil> LGtkWidget<Widg, Props, Chil>
|
||||
where
|
||||
Widg: WidgetClass,
|
||||
Chil: NextTuple,
|
||||
{
|
||||
pub fn child<T>(
|
||||
self,
|
||||
child: T,
|
||||
) -> LGtkWidget<Widg, Props, Chil::Output<T>> {
|
||||
let LGtkWidget {
|
||||
widget,
|
||||
properties,
|
||||
children,
|
||||
} = self;
|
||||
LGtkWidget {
|
||||
widget,
|
||||
properties,
|
||||
children: children.next_tuple(child),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Widg, Props, Chil> LGtkWidget<Widg, Props, Chil>
|
||||
where
|
||||
Widg: WidgetClass,
|
||||
Props: NextTuple,
|
||||
Chil: Render<LeptosGtk>,
|
||||
{
|
||||
pub fn connect<F>(
|
||||
self,
|
||||
signal_name: &'static str,
|
||||
callback: F,
|
||||
) -> LGtkWidget<Widg, Props::Output<Connect<F>>, Chil>
|
||||
where
|
||||
F: Fn(&[Value]) -> Option<Value> + Send + Sync + 'static,
|
||||
{
|
||||
let LGtkWidget {
|
||||
widget,
|
||||
properties,
|
||||
children,
|
||||
} = self;
|
||||
LGtkWidget {
|
||||
widget,
|
||||
properties: properties.next_tuple(Connect {
|
||||
signal_name,
|
||||
callback,
|
||||
}),
|
||||
children,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LGtkWidgetState<Widg, Props, Chil>
|
||||
where
|
||||
Chil: Render<LeptosGtk>,
|
||||
Props: Property,
|
||||
Widg: WidgetClass,
|
||||
{
|
||||
ty: PhantomData<Widg>,
|
||||
widget: Element,
|
||||
properties: Props::State,
|
||||
children: Chil::State,
|
||||
}
|
||||
|
||||
impl<Widg, Props, Chil> LGtkWidgetState<Widg, Props, Chil>
|
||||
where
|
||||
Chil: Render<LeptosGtk>,
|
||||
Props: Property,
|
||||
Widg: WidgetClass,
|
||||
{
|
||||
pub fn as_widget(&self) -> &Widget {
|
||||
&self.widget.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<Widg, Props, Chil> Render<LeptosGtk> for LGtkWidget<Widg, Props, Chil>
|
||||
where
|
||||
Widg: WidgetClass,
|
||||
Props: Property,
|
||||
Chil: Render<LeptosGtk>,
|
||||
{
|
||||
type State = LGtkWidgetState<Widg, Props, Chil>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let widget = Object::new::<Widg::Widget>();
|
||||
let widget = Element::from(widget);
|
||||
let properties = self.properties.build(&widget);
|
||||
let mut children = self.children.build();
|
||||
children.mount(&widget, None);
|
||||
LGtkWidgetState {
|
||||
ty: PhantomData,
|
||||
widget,
|
||||
properties,
|
||||
children,
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.properties
|
||||
.rebuild(&state.widget, &mut state.properties);
|
||||
self.children.rebuild(&mut state.children);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Widg, Props, Chil> Mountable<LeptosGtk>
|
||||
for LGtkWidgetState<Widg, Props, Chil>
|
||||
where
|
||||
Widg: WidgetClass,
|
||||
Props: Property,
|
||||
Chil: Render<LeptosGtk>,
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
self.children.unmount();
|
||||
self.widget.remove();
|
||||
}
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
parent: &<LeptosGtk as Renderer>::Element,
|
||||
marker: Option<&<LeptosGtk as Renderer>::Node>,
|
||||
) {
|
||||
self.children.mount(&self.widget, None);
|
||||
LeptosGtk::insert_node(parent, &self.widget, marker);
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<LeptosGtk>) -> bool {
|
||||
self.widget.insert_before_this(child)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Property {
|
||||
type State;
|
||||
|
||||
fn build(self, element: &Element) -> Self::State;
|
||||
|
||||
fn rebuild(self, element: &Element, state: &mut Self::State);
|
||||
}
|
||||
|
||||
impl<T, F> Property for F
|
||||
where
|
||||
T: Property,
|
||||
T::State: 'static,
|
||||
F: Fn() -> T + 'static,
|
||||
{
|
||||
type State = RenderEffect<T::State>;
|
||||
|
||||
fn build(self, widget: &Element) -> Self::State {
|
||||
let widget = widget.clone();
|
||||
RenderEffect::new(move |prev| {
|
||||
let value = self();
|
||||
if let Some(mut prev) = prev {
|
||||
value.rebuild(&widget, &mut prev);
|
||||
prev
|
||||
} else {
|
||||
value.build(&widget)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn rebuild(self, widget: &Element, state: &mut Self::State) {
|
||||
let prev_value = state.take_value();
|
||||
let widget = widget.to_owned();
|
||||
*state = RenderEffect::new_with_value(
|
||||
move |prev| {
|
||||
let value = self();
|
||||
if let Some(mut state) = prev {
|
||||
value.rebuild(&widget, &mut state);
|
||||
state
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
},
|
||||
prev_value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button() -> LGtkWidget<gtk::Button, (), ()> {
|
||||
LGtkWidget {
|
||||
widget: PhantomData,
|
||||
properties: (),
|
||||
children: (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#box() -> LGtkWidget<gtk::Box, (), ()> {
|
||||
LGtkWidget {
|
||||
widget: PhantomData,
|
||||
properties: (),
|
||||
children: (),
|
||||
}
|
||||
}
|
||||
|
||||
mod widgets {
|
||||
use super::WidgetClass;
|
||||
|
||||
impl WidgetClass for gtk::Button {
|
||||
type Widget = Self;
|
||||
}
|
||||
|
||||
impl WidgetClass for gtk::Box {
|
||||
type Widget = Self;
|
||||
}
|
||||
}
|
||||
|
||||
pub mod properties {
|
||||
#![allow(dead_code)]
|
||||
|
||||
use super::{Element, LGtkWidget, LeptosGtk, Property, WidgetClass};
|
||||
use gtk::glib::{object::ObjectExt, Value};
|
||||
use leptos::tachys::{renderer::Renderer, view::Render};
|
||||
use next_tuple::NextTuple;
|
||||
|
||||
pub struct Connect<F>
|
||||
where
|
||||
F: Fn(&[Value]) -> Option<Value> + Send + Sync + 'static,
|
||||
{
|
||||
pub signal_name: &'static str,
|
||||
pub callback: F,
|
||||
}
|
||||
|
||||
impl<F> Property for Connect<F>
|
||||
where
|
||||
F: Fn(&[Value]) -> Option<Value> + Send + Sync + 'static,
|
||||
{
|
||||
type State = ();
|
||||
|
||||
fn build(self, element: &Element) -> Self::State {
|
||||
element.0.connect(self.signal_name, false, self.callback);
|
||||
}
|
||||
|
||||
fn rebuild(self, _element: &Element, _state: &mut Self::State) {
|
||||
// TODO we want to *remove* the previous listener, and reconnect with this new one
|
||||
}
|
||||
}
|
||||
|
||||
/* examples for macro */
|
||||
pub struct Orientation {
|
||||
value: gtk::Orientation,
|
||||
}
|
||||
|
||||
pub struct OrientationState {
|
||||
value: gtk::Orientation,
|
||||
}
|
||||
|
||||
impl Property for Orientation {
|
||||
type State = OrientationState;
|
||||
|
||||
fn build(self, element: &Element) -> Self::State {
|
||||
element.0.set_property("orientation", self.value);
|
||||
OrientationState { value: self.value }
|
||||
}
|
||||
|
||||
fn rebuild(self, element: &Element, state: &mut Self::State) {
|
||||
if self.value != state.value {
|
||||
element.0.set_property("orientation", self.value);
|
||||
state.value = self.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Widg, Props, Chil> LGtkWidget<Widg, Props, Chil>
|
||||
where
|
||||
Widg: WidgetClass,
|
||||
Props: NextTuple,
|
||||
Chil: Render<LeptosGtk>,
|
||||
{
|
||||
pub fn orientation(
|
||||
self,
|
||||
value: impl Into<gtk::Orientation>,
|
||||
) -> LGtkWidget<Widg, Props::Output<Orientation>, Chil> {
|
||||
let LGtkWidget {
|
||||
widget,
|
||||
properties,
|
||||
children,
|
||||
} = self;
|
||||
LGtkWidget {
|
||||
widget,
|
||||
properties: properties.next_tuple(Orientation {
|
||||
value: value.into(),
|
||||
}),
|
||||
children,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Spacing {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
pub struct SpacingState {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
impl Property for Spacing {
|
||||
type State = SpacingState;
|
||||
|
||||
fn build(self, element: &Element) -> Self::State {
|
||||
element.0.set_property("spacing", self.value);
|
||||
SpacingState { value: self.value }
|
||||
}
|
||||
|
||||
fn rebuild(self, element: &Element, state: &mut Self::State) {
|
||||
if self.value != state.value {
|
||||
element.0.set_property("spacing", self.value);
|
||||
state.value = self.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Widg, Props, Chil> LGtkWidget<Widg, Props, Chil>
|
||||
where
|
||||
Widg: WidgetClass,
|
||||
Props: NextTuple,
|
||||
Chil: Render<LeptosGtk>,
|
||||
{
|
||||
pub fn spacing(
|
||||
self,
|
||||
value: impl Into<i32>,
|
||||
) -> LGtkWidget<Widg, Props::Output<Spacing>, Chil> {
|
||||
let LGtkWidget {
|
||||
widget,
|
||||
properties,
|
||||
children,
|
||||
} = self;
|
||||
LGtkWidget {
|
||||
widget,
|
||||
properties: properties.next_tuple(Spacing {
|
||||
value: value.into(),
|
||||
}),
|
||||
children,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* end examples for properties macro */
|
||||
#[derive(Debug)]
|
||||
pub struct Label {
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl Label {
|
||||
pub fn new(value: impl Into<String>) -> Self {
|
||||
Self {
|
||||
value: value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LabelState {
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl Property for Label {
|
||||
type State = LabelState;
|
||||
|
||||
fn build(self, element: &Element) -> Self::State {
|
||||
LeptosGtk::set_attribute(element, "label", &self.value);
|
||||
LabelState { value: self.value }
|
||||
}
|
||||
|
||||
fn rebuild(self, element: &Element, state: &mut Self::State) {
|
||||
if self.value != state.value {
|
||||
LeptosGtk::set_attribute(element, "label", &self.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Property for () {
|
||||
type State = ();
|
||||
|
||||
fn build(self, _element: &Element) -> Self::State {}
|
||||
|
||||
fn rebuild(self, _element: &Element, _state: &mut Self::State) {}
|
||||
}
|
||||
|
||||
macro_rules! tuples {
|
||||
($($ty:ident),* $(,)?) => {
|
||||
impl<$($ty,)*> Property for ($($ty,)*)
|
||||
where $($ty: Property,)*
|
||||
{
|
||||
type State = ($($ty::State,)*);
|
||||
|
||||
fn build(self, element: &Element) -> Self::State {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($ty,)*) = self;
|
||||
($($ty.build(element),)*)
|
||||
}
|
||||
|
||||
fn rebuild(self, element: &Element, state: &mut Self::State) {
|
||||
paste::paste! {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($ty,)*) = self;
|
||||
#[allow(non_snake_case)]
|
||||
let ($([<state_ $ty:lower>],)*) = state;
|
||||
$($ty.rebuild(element, [<state_ $ty:lower>]));*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tuples!(A);
|
||||
tuples!(A, B);
|
||||
tuples!(A, B, C);
|
||||
tuples!(A, B, C, D);
|
||||
tuples!(A, B, C, D, E);
|
||||
tuples!(A, B, C, D, E, F);
|
||||
tuples!(A, B, C, D, E, F, G);
|
||||
tuples!(A, B, C, D, E, F, G, H);
|
||||
tuples!(A, B, C, D, E, F, G, H, I);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
|
||||
tuples!(
|
||||
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W
|
||||
);
|
||||
tuples!(
|
||||
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X
|
||||
);
|
||||
tuples!(
|
||||
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X,
|
||||
Y
|
||||
);
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
use any_spawner::Executor;
|
||||
use gtk::{prelude::*, Application, ApplicationWindow, Orientation};
|
||||
use leptos::prelude::*;
|
||||
use leptos_gtk::LeptosGtk;
|
||||
use std::{mem, thread, time::Duration};
|
||||
mod leptos_gtk;
|
||||
|
||||
const APP_ID: &str = "dev.leptos.Counter";
|
||||
|
||||
// Basic GTK app setup from https://gtk-rs.org/gtk4-rs/stable/latest/book/hello_world.html
|
||||
fn main() {
|
||||
// use the glib event loop to power the reactive system
|
||||
_ = Executor::init_glib();
|
||||
let app = Application::builder().application_id(APP_ID).build();
|
||||
|
||||
app.connect_startup(|_| load_css());
|
||||
|
||||
app.connect_activate(|app| {
|
||||
// Connect to "activate" signal of `app`
|
||||
let owner = Owner::new();
|
||||
let view = owner.with(ui);
|
||||
let (root, state) = leptos_gtk::root(view);
|
||||
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.title("TachyGTK")
|
||||
.child(&root)
|
||||
.build();
|
||||
// Present window
|
||||
window.present();
|
||||
mem::forget((owner, state));
|
||||
});
|
||||
|
||||
app.run();
|
||||
}
|
||||
|
||||
fn ui() -> impl Render<LeptosGtk> {
|
||||
let value = RwSignal::new(0);
|
||||
let rows = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
||||
|
||||
Effect::new(move |_| {
|
||||
println!("value = {}", value.get());
|
||||
});
|
||||
|
||||
// just an example of multithreaded reactivity
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
value.update(|n| *n += 1);
|
||||
});
|
||||
|
||||
vstack((
|
||||
hstack((
|
||||
button("-1", move || {
|
||||
println!("clicked -1");
|
||||
value.update(|n| *n -= 1);
|
||||
}),
|
||||
move || value.get().to_string(),
|
||||
button("+1", move || value.update(|n| *n += 1)),
|
||||
)),
|
||||
button("Swap", move || {
|
||||
rows.update(|items| {
|
||||
items.swap(1, 3);
|
||||
})
|
||||
}),
|
||||
hstack(rows),
|
||||
))
|
||||
}
|
||||
|
||||
fn button(
|
||||
label: impl Render<LeptosGtk>,
|
||||
callback: impl Fn() + Send + Sync + 'static,
|
||||
) -> impl Render<LeptosGtk> {
|
||||
leptos_gtk::button()
|
||||
.child(label)
|
||||
.connect("clicked", move |_| {
|
||||
callback();
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn vstack(children: impl Render<LeptosGtk>) -> impl Render<LeptosGtk> {
|
||||
leptos_gtk::r#box()
|
||||
.orientation(Orientation::Vertical)
|
||||
.spacing(12)
|
||||
.child(children)
|
||||
}
|
||||
|
||||
fn hstack(children: impl Render<LeptosGtk>) -> impl Render<LeptosGtk> {
|
||||
leptos_gtk::r#box()
|
||||
.orientation(Orientation::Horizontal)
|
||||
.spacing(12)
|
||||
.child(children)
|
||||
}
|
||||
|
||||
fn load_css() {
|
||||
use gtk::{gdk::Display, CssProvider};
|
||||
|
||||
let provider = CssProvider::new();
|
||||
provider.load_from_path("style.css");
|
||||
|
||||
// Add the provider to the default screen
|
||||
gtk::style_context_add_provider_for_display(
|
||||
&Display::default().expect("Could not connect to a display."),
|
||||
&provider,
|
||||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
}
|
||||
@@ -65,7 +65,7 @@ pub fn RouterExample() -> impl IntoView {
|
||||
// You can define other routes in their own component.
|
||||
// Routes implement the MatchNestedRoutes
|
||||
#[component]
|
||||
pub fn ContactRoutes() -> impl MatchNestedRoutes<Dom> + Clone {
|
||||
pub fn ContactRoutes() -> impl MatchNestedRoutes + Clone {
|
||||
view! {
|
||||
<ParentRoute path=path!("") view=ContactList>
|
||||
<Route path=path!("/") view=|| "Select a contact."/>
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use chrono::{Local, NaiveDate};
|
||||
use leptos::prelude::*;
|
||||
use reactive_stores::{Field, Store};
|
||||
use reactive_stores_macro::Store;
|
||||
use reactive_stores::{Field, Patch, Store};
|
||||
use reactive_stores_macro::{Patch, Store};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// ID starts higher than 0 because we have a few starting todos by default
|
||||
@@ -11,11 +11,17 @@ static NEXT_ID: AtomicUsize = AtomicUsize::new(3);
|
||||
|
||||
#[derive(Debug, Store, Serialize, Deserialize)]
|
||||
struct Todos {
|
||||
user: String,
|
||||
user: User,
|
||||
#[store(key: usize = |todo| todo.id)]
|
||||
todos: Vec<Todo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Store, Patch, Serialize, Deserialize)]
|
||||
struct User {
|
||||
name: String,
|
||||
email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Store, Serialize, Deserialize)]
|
||||
struct Todo {
|
||||
id: usize,
|
||||
@@ -58,7 +64,10 @@ impl Todo {
|
||||
|
||||
fn data() -> Todos {
|
||||
Todos {
|
||||
user: "Bob".to_string(),
|
||||
user: User {
|
||||
name: "Bob".to_string(),
|
||||
email: "lawblog@bobloblaw.com".into(),
|
||||
},
|
||||
todos: vec![
|
||||
Todo {
|
||||
id: 0,
|
||||
@@ -86,7 +95,9 @@ pub fn App() -> impl IntoView {
|
||||
let input_ref = NodeRef::new();
|
||||
|
||||
view! {
|
||||
<p>"Hello, " {move || store.user().get()}</p>
|
||||
<p>"Hello, " {move || store.user().name().get()}</p>
|
||||
<UserForm user=store.user()/>
|
||||
<hr/>
|
||||
<form on:submit=move |ev| {
|
||||
ev.prevent_default();
|
||||
store.todos().write().push(Todo::new(input_ref.get().unwrap().value()));
|
||||
@@ -98,7 +109,15 @@ pub fn App() -> impl IntoView {
|
||||
// because `todos` is a keyed field, `store.todos()` returns a struct that
|
||||
// directly implements IntoIterator, so we can use it in <For/> and
|
||||
// it will manage reactivity for the store fields correctly
|
||||
<For each=move || store.todos() key=|row| row.id().get() let:todo>
|
||||
<For
|
||||
each=move || {
|
||||
leptos::logging::log!("RERUNNING FOR CALCULATION");
|
||||
store.todos()
|
||||
}
|
||||
|
||||
key=|row| row.id().get()
|
||||
let:todo
|
||||
>
|
||||
<TodoRow store todo/>
|
||||
</For>
|
||||
|
||||
@@ -107,6 +126,33 @@ pub fn App() -> impl IntoView {
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn UserForm(#[prop(into)] user: Field<User>) -> impl IntoView {
|
||||
let error = RwSignal::new(None);
|
||||
|
||||
view! {
|
||||
{move || error.get().map(|n| view! { <p>{n}</p> })}
|
||||
<form on:submit:target=move |ev| {
|
||||
ev.prevent_default();
|
||||
match User::from_event(&ev) {
|
||||
Ok(new_user) => {
|
||||
error.set(None);
|
||||
user.patch(new_user);
|
||||
}
|
||||
Err(e) => error.set(Some(e.to_string())),
|
||||
}
|
||||
}>
|
||||
<label>
|
||||
"Name" <input type="text" name="name" prop:value=move || user.name().get()/>
|
||||
</label>
|
||||
<label>
|
||||
"Email" <input type="email" name="email" prop:value=move || user.email().get()/>
|
||||
</label>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TodoRow(
|
||||
store: Store<Todos>,
|
||||
|
||||
@@ -319,10 +319,7 @@ pub fn Todo(todo: Todo) -> impl IntoView {
|
||||
node_ref=todo_input
|
||||
class="toggle"
|
||||
type="checkbox"
|
||||
prop:checked=move || todo.completed.get()
|
||||
on:input:target=move |ev| {
|
||||
todo.completed.set(ev.target().checked());
|
||||
}
|
||||
bind:checked=todo.completed
|
||||
/>
|
||||
|
||||
<label on:dblclick=move |_| {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "hydration_context"
|
||||
version = "0.2.0-beta5"
|
||||
version = "0.2.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -3,13 +3,10 @@ use std::{
|
||||
fmt::{self, Debug},
|
||||
sync::Arc,
|
||||
};
|
||||
use tachys::{
|
||||
renderer::dom::Dom,
|
||||
view::{
|
||||
any_view::{AnyView, IntoAny},
|
||||
fragment::{Fragment, IntoFragment},
|
||||
RenderHtml,
|
||||
},
|
||||
use tachys::view::{
|
||||
any_view::{AnyView, IntoAny},
|
||||
fragment::{Fragment, IntoFragment},
|
||||
RenderHtml,
|
||||
};
|
||||
|
||||
/// The most common type for the `children` property on components,
|
||||
@@ -17,31 +14,31 @@ use tachys::{
|
||||
///
|
||||
/// This does not support iterating over individual nodes within the children.
|
||||
/// To iterate over children, use [`ChildrenFragment`].
|
||||
pub type Children = Box<dyn FnOnce() -> AnyView<Dom> + Send>;
|
||||
pub type Children = Box<dyn FnOnce() -> AnyView + Send>;
|
||||
|
||||
/// A type for the `children` property on components that can be called only once,
|
||||
/// and provides a collection of all the children passed to this component.
|
||||
pub type ChildrenFragment = Box<dyn FnOnce() -> Fragment<Dom> + Send>;
|
||||
pub type ChildrenFragment = Box<dyn FnOnce() -> Fragment + Send>;
|
||||
|
||||
/// A type for the `children` property on components that can be called
|
||||
/// more than once.
|
||||
pub type ChildrenFn = Arc<dyn Fn() -> AnyView<Dom> + Send + Sync>;
|
||||
pub type ChildrenFn = Arc<dyn Fn() -> AnyView + Send + Sync>;
|
||||
|
||||
/// A type for the `children` property on components that can be called more than once,
|
||||
/// and provides a collection of all the children passed to this component.
|
||||
pub type ChildrenFragmentFn = Arc<dyn Fn() -> Fragment<Dom> + Send>;
|
||||
pub type ChildrenFragmentFn = Arc<dyn Fn() -> Fragment + Send>;
|
||||
|
||||
/// A type for the `children` property on components that can be called
|
||||
/// more than once, but may mutate the children.
|
||||
pub type ChildrenFnMut = Box<dyn FnMut() -> AnyView<Dom> + Send>;
|
||||
pub type ChildrenFnMut = Box<dyn FnMut() -> AnyView + Send>;
|
||||
|
||||
/// A type for the `children` property on components that can be called more than once,
|
||||
/// but may mutate the children, and provides a collection of all the children
|
||||
/// passed to this component.
|
||||
pub type ChildrenFragmentMut = Box<dyn FnMut() -> Fragment<Dom> + Send>;
|
||||
pub type ChildrenFragmentMut = Box<dyn FnMut() -> Fragment + Send>;
|
||||
|
||||
// This is to still support components that accept `Box<dyn Fn() -> AnyView>` as a children.
|
||||
type BoxedChildrenFn = Box<dyn Fn() -> AnyView<Dom> + Send>;
|
||||
type BoxedChildrenFn = Box<dyn Fn() -> AnyView + Send>;
|
||||
|
||||
/// This trait can be used when constructing a component that takes children without needing
|
||||
/// to know exactly what children type the component expects. This is used internally by the
|
||||
@@ -97,7 +94,7 @@ pub trait ToChildren<F> {
|
||||
impl<F, C> ToChildren<F> for Children
|
||||
where
|
||||
F: FnOnce() -> C + Send + 'static,
|
||||
C: RenderHtml<Dom> + Send + 'static,
|
||||
C: RenderHtml + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(f: F) -> Self {
|
||||
@@ -108,7 +105,7 @@ where
|
||||
impl<F, C> ToChildren<F> for ChildrenFn
|
||||
where
|
||||
F: Fn() -> C + Send + Sync + 'static,
|
||||
C: RenderHtml<Dom> + Send + 'static,
|
||||
C: RenderHtml + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(f: F) -> Self {
|
||||
@@ -119,7 +116,7 @@ where
|
||||
impl<F, C> ToChildren<F> for ChildrenFnMut
|
||||
where
|
||||
F: Fn() -> C + Send + 'static,
|
||||
C: RenderHtml<Dom> + Send + 'static,
|
||||
C: RenderHtml + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(f: F) -> Self {
|
||||
@@ -130,7 +127,7 @@ where
|
||||
impl<F, C> ToChildren<F> for BoxedChildrenFn
|
||||
where
|
||||
F: Fn() -> C + Send + 'static,
|
||||
C: RenderHtml<Dom> + Send + 'static,
|
||||
C: RenderHtml + Send + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(f: F) -> Self {
|
||||
@@ -141,7 +138,7 @@ where
|
||||
impl<F, C> ToChildren<F> for ChildrenFragment
|
||||
where
|
||||
F: FnOnce() -> C + Send + 'static,
|
||||
C: IntoFragment<Dom>,
|
||||
C: IntoFragment,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(f: F) -> Self {
|
||||
@@ -152,7 +149,7 @@ where
|
||||
impl<F, C> ToChildren<F> for ChildrenFragmentFn
|
||||
where
|
||||
F: Fn() -> C + Send + 'static,
|
||||
C: IntoFragment<Dom>,
|
||||
C: IntoFragment,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(f: F) -> Self {
|
||||
@@ -163,7 +160,7 @@ where
|
||||
impl<F, C> ToChildren<F> for ChildrenFragmentMut
|
||||
where
|
||||
F: FnMut() -> C + Send + 'static,
|
||||
C: IntoFragment<Dom>,
|
||||
C: IntoFragment,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(mut f: F) -> Self {
|
||||
@@ -174,7 +171,7 @@ where
|
||||
/// New-type wrapper for a function that returns a view with `From` and `Default` traits implemented
|
||||
/// to enable optional props in for example `<Show>` and `<Suspense>`.
|
||||
#[derive(Clone)]
|
||||
pub struct ViewFn(Arc<dyn Fn() -> AnyView<Dom> + Send + Sync + 'static>);
|
||||
pub struct ViewFn(Arc<dyn Fn() -> AnyView + Send + Sync + 'static>);
|
||||
|
||||
impl Default for ViewFn {
|
||||
fn default() -> Self {
|
||||
@@ -185,7 +182,7 @@ impl Default for ViewFn {
|
||||
impl<F, C> From<F> for ViewFn
|
||||
where
|
||||
F: Fn() -> C + Send + Sync + 'static,
|
||||
C: RenderHtml<Dom> + Send + 'static,
|
||||
C: RenderHtml + Send + 'static,
|
||||
{
|
||||
fn from(value: F) -> Self {
|
||||
Self(Arc::new(move || value().into_any()))
|
||||
@@ -194,14 +191,14 @@ where
|
||||
|
||||
impl ViewFn {
|
||||
/// Execute the wrapped function
|
||||
pub fn run(&self) -> AnyView<Dom> {
|
||||
pub fn run(&self) -> AnyView {
|
||||
(self.0)()
|
||||
}
|
||||
}
|
||||
|
||||
/// New-type wrapper for a function, which will only be called once and returns a view with `From` and
|
||||
/// `Default` traits implemented to enable optional props in for example `<Show>` and `<Suspense>`.
|
||||
pub struct ViewFnOnce(Box<dyn FnOnce() -> AnyView<Dom> + Send + 'static>);
|
||||
pub struct ViewFnOnce(Box<dyn FnOnce() -> AnyView + Send + 'static>);
|
||||
|
||||
impl Default for ViewFnOnce {
|
||||
fn default() -> Self {
|
||||
@@ -212,7 +209,7 @@ impl Default for ViewFnOnce {
|
||||
impl<F, C> From<F> for ViewFnOnce
|
||||
where
|
||||
F: FnOnce() -> C + Send + 'static,
|
||||
C: RenderHtml<Dom> + Send + 'static,
|
||||
C: RenderHtml + Send + 'static,
|
||||
{
|
||||
fn from(value: F) -> Self {
|
||||
Self(Box::new(move || value().into_any()))
|
||||
@@ -221,7 +218,7 @@ where
|
||||
|
||||
impl ViewFnOnce {
|
||||
/// Execute the wrapped function
|
||||
pub fn run(self) -> AnyView<Dom> {
|
||||
pub fn run(self) -> AnyView {
|
||||
(self.0)()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,11 @@ use reactive_graph::{
|
||||
traits::{Get, Update, With, WithUntracked},
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
use tachys::{
|
||||
html::attribute::Attribute,
|
||||
hydration::Cursor,
|
||||
reactive_graph::OwnedView,
|
||||
renderer::Renderer,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
@@ -112,20 +111,18 @@ where
|
||||
children,
|
||||
errors,
|
||||
fallback,
|
||||
rndr: PhantomData,
|
||||
},
|
||||
owner,
|
||||
)
|
||||
}
|
||||
|
||||
struct ErrorBoundaryView<Chil, FalFn, Rndr> {
|
||||
struct ErrorBoundaryView<Chil, FalFn> {
|
||||
hook: Arc<dyn ErrorHook>,
|
||||
boundary_id: SerializedDataId,
|
||||
errors_empty: ArcMemo<bool>,
|
||||
children: Chil,
|
||||
fallback: FalFn,
|
||||
errors: ArcRwSignal<Errors>,
|
||||
rndr: PhantomData<Rndr>,
|
||||
}
|
||||
|
||||
struct ErrorBoundaryViewState<Chil, Fal> {
|
||||
@@ -134,11 +131,10 @@ struct ErrorBoundaryViewState<Chil, Fal> {
|
||||
fallback: Option<Fal>,
|
||||
}
|
||||
|
||||
impl<Chil, Fal, Rndr> Mountable<Rndr> for ErrorBoundaryViewState<Chil, Fal>
|
||||
impl<Chil, Fal> Mountable for ErrorBoundaryViewState<Chil, Fal>
|
||||
where
|
||||
Chil: Mountable<Rndr>,
|
||||
Fal: Mountable<Rndr>,
|
||||
Rndr: Renderer,
|
||||
Chil: Mountable,
|
||||
Fal: Mountable,
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
if let Some(fallback) = &mut self.fallback {
|
||||
@@ -148,7 +144,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn mount(&mut self, parent: &Rndr::Element, marker: Option<&Rndr::Node>) {
|
||||
fn mount(
|
||||
&mut self,
|
||||
parent: &tachys::renderer::types::Element,
|
||||
marker: Option<&tachys::renderer::types::Node>,
|
||||
) {
|
||||
if let Some(fallback) = &mut self.fallback {
|
||||
fallback.mount(parent, marker);
|
||||
} else {
|
||||
@@ -156,7 +156,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<Rndr>) -> bool {
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
|
||||
if let Some(fallback) = &self.fallback {
|
||||
fallback.insert_before_this(child)
|
||||
} else {
|
||||
@@ -165,13 +165,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Chil, FalFn, Fal, Rndr> Render<Rndr>
|
||||
for ErrorBoundaryView<Chil, FalFn, Rndr>
|
||||
impl<Chil, FalFn, Fal> Render for ErrorBoundaryView<Chil, FalFn>
|
||||
where
|
||||
Chil: Render<Rndr> + 'static,
|
||||
Chil: Render + 'static,
|
||||
FalFn: FnMut(ArcRwSignal<Errors>) -> Fal + Send + 'static,
|
||||
Fal: Render<Rndr> + 'static,
|
||||
Rndr: Renderer,
|
||||
Fal: Render + 'static,
|
||||
{
|
||||
type State = RenderEffect<ErrorBoundaryViewState<Chil::State, Fal::State>>;
|
||||
|
||||
@@ -228,26 +226,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Chil, FalFn, Fal, Rndr> AddAnyAttr<Rndr>
|
||||
for ErrorBoundaryView<Chil, FalFn, Rndr>
|
||||
impl<Chil, FalFn, Fal> AddAnyAttr for ErrorBoundaryView<Chil, FalFn>
|
||||
where
|
||||
Chil: RenderHtml<Rndr> + 'static,
|
||||
Chil: RenderHtml + 'static,
|
||||
FalFn: FnMut(ArcRwSignal<Errors>) -> Fal + Send + 'static,
|
||||
Fal: RenderHtml<Rndr> + Send + 'static,
|
||||
Rndr: Renderer,
|
||||
Fal: RenderHtml + Send + 'static,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Rndr>> = ErrorBoundaryView<
|
||||
Chil::Output<SomeNewAttr::CloneableOwned>,
|
||||
FalFn,
|
||||
Rndr,
|
||||
>;
|
||||
type Output<SomeNewAttr: Attribute> =
|
||||
ErrorBoundaryView<Chil::Output<SomeNewAttr::CloneableOwned>, FalFn>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
let ErrorBoundaryView {
|
||||
hook,
|
||||
@@ -256,7 +249,6 @@ where
|
||||
children,
|
||||
fallback,
|
||||
errors,
|
||||
rndr,
|
||||
} = self;
|
||||
ErrorBoundaryView {
|
||||
hook,
|
||||
@@ -265,20 +257,17 @@ where
|
||||
children: children.add_any_attr(attr.into_cloneable_owned()),
|
||||
fallback,
|
||||
errors,
|
||||
rndr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Chil, FalFn, Fal, Rndr> RenderHtml<Rndr>
|
||||
for ErrorBoundaryView<Chil, FalFn, Rndr>
|
||||
impl<Chil, FalFn, Fal> RenderHtml for ErrorBoundaryView<Chil, FalFn>
|
||||
where
|
||||
Chil: RenderHtml<Rndr> + Send + 'static,
|
||||
Chil: RenderHtml + Send + 'static,
|
||||
FalFn: FnMut(ArcRwSignal<Errors>) -> Fal + Send + 'static,
|
||||
Fal: RenderHtml<Rndr> + Send + 'static,
|
||||
Rndr: Renderer,
|
||||
Fal: RenderHtml + Send + 'static,
|
||||
{
|
||||
type AsyncOutput = ErrorBoundaryView<Chil::AsyncOutput, FalFn, Rndr>;
|
||||
type AsyncOutput = ErrorBoundaryView<Chil::AsyncOutput, FalFn>;
|
||||
|
||||
const MIN_LENGTH: usize = Chil::MIN_LENGTH;
|
||||
|
||||
@@ -303,7 +292,6 @@ where
|
||||
children: children.resolve().await,
|
||||
fallback,
|
||||
errors,
|
||||
rndr: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +365,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
mut self,
|
||||
cursor: &Cursor<Rndr>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let mut children = Some(self.children);
|
||||
|
||||
@@ -157,7 +157,7 @@ where
|
||||
};
|
||||
move || keyed(each(), key.clone(), children.clone())
|
||||
}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
@@ -168,7 +168,7 @@ mod tests {
|
||||
fn creates_list() {
|
||||
Owner::new().with(|| {
|
||||
let values = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
||||
let list: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
let list: View<HtmlElement<_, _, _>> = view! {
|
||||
<ol>
|
||||
<For each=move || values.get() key=|i| *i let:i>
|
||||
<li>{i}</li>
|
||||
@@ -187,7 +187,7 @@ mod tests {
|
||||
fn creates_list_enumerate() {
|
||||
Owner::new().with(|| {
|
||||
let values = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
||||
let list: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
let list: View<HtmlElement<_, _, _>> = view! {
|
||||
<ol>
|
||||
<ForEnumerate each=move || values.get() key=|i| *i let(index, i)>
|
||||
<li>{move || index.get()}"-"{i}</li>
|
||||
@@ -200,7 +200,7 @@ mod tests {
|
||||
<!>-<!>4</li><li>4<!>-<!>5</li><!></ol>"
|
||||
);
|
||||
|
||||
let list: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
let list: View<HtmlElement<_, _, _>> = view! {
|
||||
<ol>
|
||||
<ForEnumerate each=move || values.get() key=|i| *i let(index, i)>
|
||||
<li>{move || index.get()}"-"{i}</li>
|
||||
@@ -216,3 +216,4 @@ mod tests {
|
||||
});
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
(function (root, pkg_path, output_name, wasm_output_name) {
|
||||
import(`${root}/${pkg_path}/${output_name}.js`)
|
||||
.then(mod => {
|
||||
mod.default(`/${pkg_path}/${wasm_output_name}.wasm`).then(() => {
|
||||
mod.default(`${root}/${pkg_path}/${wasm_output_name}.wasm`).then(() => {
|
||||
mod.hydrate();
|
||||
});
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
if (islandFn) {
|
||||
islandFn(el);
|
||||
} else {
|
||||
console.warn(`Could not find WASM function for the island ${l}.`);
|
||||
console.warn(`Could not find WASM function for the island ${id}.`);
|
||||
}
|
||||
}
|
||||
function hydrateIslands(entry, mod) {
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::borrow::Cow;
|
||||
use tachys::{
|
||||
html::attribute::Attribute,
|
||||
hydration::Cursor,
|
||||
renderer::{dom::Dom, Renderer},
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml,
|
||||
@@ -50,14 +49,14 @@ impl<T> View<T> {
|
||||
|
||||
pub trait IntoView
|
||||
where
|
||||
Self: Sized + Render<Dom> + RenderHtml<Dom> + Send,
|
||||
Self: Sized + Render + RenderHtml + Send,
|
||||
{
|
||||
fn into_view(self) -> View<Self>;
|
||||
}
|
||||
|
||||
impl<T> IntoView for T
|
||||
where
|
||||
T: Sized + Render<Dom> + RenderHtml<Dom> + Send, //+ AddAnyAttr<Dom>,
|
||||
T: Sized + Render + RenderHtml + Send, //+ AddAnyAttr,
|
||||
{
|
||||
fn into_view(self) -> View<Self> {
|
||||
View {
|
||||
@@ -68,7 +67,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Render<Rndr>, Rndr: Renderer> Render<Rndr> for View<T> {
|
||||
impl<T: Render> Render for View<T> {
|
||||
type State = T::State;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
@@ -80,10 +79,10 @@ impl<T: Render<Rndr>, Rndr: Renderer> Render<Rndr> for View<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RenderHtml<Rndr>, Rndr: Renderer> RenderHtml<Rndr> for View<T> {
|
||||
impl<T: RenderHtml> RenderHtml for View<T> {
|
||||
type AsyncOutput = T::AsyncOutput;
|
||||
|
||||
const MIN_LENGTH: usize = <T as RenderHtml<Rndr>>::MIN_LENGTH;
|
||||
const MIN_LENGTH: usize = <T as RenderHtml>::MIN_LENGTH;
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self.inner.resolve().await
|
||||
@@ -147,7 +146,7 @@ impl<T: RenderHtml<Rndr>, Rndr: Renderer> RenderHtml<Rndr> for View<T> {
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Rndr>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
self.inner.hydrate::<FROM_SERVER>(cursor, position)
|
||||
@@ -166,18 +165,15 @@ impl<T: ToTemplate> ToTemplate for View<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AddAnyAttr<Rndr>, Rndr> AddAnyAttr<Rndr> for View<T>
|
||||
where
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Rndr>> = View<T::Output<SomeNewAttr>>;
|
||||
impl<T: AddAnyAttr> AddAnyAttr for View<T> {
|
||||
type Output<SomeNewAttr: Attribute> = View<T::Output<SomeNewAttr>>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
let View {
|
||||
inner,
|
||||
|
||||
@@ -174,7 +174,7 @@ pub mod prelude {
|
||||
pub use server_fn::{self, ServerFnError};
|
||||
pub use tachys::{
|
||||
self,
|
||||
reactive_graph::{node_ref::*, Suspend},
|
||||
reactive_graph::{bind::BindAttribute, node_ref::*, Suspend},
|
||||
view::template::ViewTemplate,
|
||||
};
|
||||
}
|
||||
@@ -293,6 +293,10 @@ pub mod spawn {
|
||||
pub async fn tick() {
|
||||
Executor::tick().await
|
||||
}
|
||||
|
||||
pub use reactive_graph::{
|
||||
spawn_local_scoped, spawn_local_scoped_with_cancellation,
|
||||
};
|
||||
}
|
||||
|
||||
// these reexports are used in islands
|
||||
|
||||
@@ -5,10 +5,8 @@ use any_spawner::Executor;
|
||||
use reactive_graph::owner::Owner;
|
||||
#[cfg(debug_assertions)]
|
||||
use std::cell::Cell;
|
||||
use std::marker::PhantomData;
|
||||
use tachys::{
|
||||
dom::body,
|
||||
renderer::{dom::Dom, Renderer},
|
||||
view::{Mountable, Render},
|
||||
};
|
||||
#[cfg(feature = "hydrate")]
|
||||
@@ -38,10 +36,7 @@ thread_local! {
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
/// Runs the provided closure and mounts the result to the provided element.
|
||||
pub fn hydrate_from<F, N>(
|
||||
parent: HtmlElement,
|
||||
f: F,
|
||||
) -> UnmountHandle<N::State, Dom>
|
||||
pub fn hydrate_from<F, N>(parent: HtmlElement, f: F) -> UnmountHandle<N::State>
|
||||
where
|
||||
F: FnOnce() -> N + 'static,
|
||||
N: IntoView,
|
||||
@@ -85,11 +80,7 @@ where
|
||||
|
||||
// returns a handle that owns the owner
|
||||
// when this is dropped, it will clean up the reactive system and unmount the view
|
||||
UnmountHandle {
|
||||
owner,
|
||||
mountable,
|
||||
rndr: PhantomData,
|
||||
}
|
||||
UnmountHandle { owner, mountable }
|
||||
}
|
||||
|
||||
/// Runs the provided closure and mounts the result to the `<body>`.
|
||||
@@ -103,7 +94,7 @@ where
|
||||
}
|
||||
|
||||
/// Runs the provided closure and mounts the result to the provided element.
|
||||
pub fn mount_to<F, N>(parent: HtmlElement, f: F) -> UnmountHandle<N::State, Dom>
|
||||
pub fn mount_to<F, N>(parent: HtmlElement, f: F) -> UnmountHandle<N::State>
|
||||
where
|
||||
F: FnOnce() -> N + 'static,
|
||||
N: IntoView,
|
||||
@@ -140,22 +131,17 @@ where
|
||||
|
||||
// returns a handle that owns the owner
|
||||
// when this is dropped, it will clean up the reactive system and unmount the view
|
||||
UnmountHandle {
|
||||
owner,
|
||||
mountable,
|
||||
rndr: PhantomData,
|
||||
}
|
||||
UnmountHandle { owner, mountable }
|
||||
}
|
||||
|
||||
/// Runs the provided closure and mounts the result to the provided element.
|
||||
pub fn mount_to_renderer<F, N, R>(
|
||||
parent: &R::Element,
|
||||
pub fn mount_to_renderer<F, N>(
|
||||
parent: &tachys::renderer::types::Element,
|
||||
f: F,
|
||||
) -> UnmountHandle<N::State, R>
|
||||
) -> UnmountHandle<N::State>
|
||||
where
|
||||
F: FnOnce() -> N + 'static,
|
||||
N: Render<R>,
|
||||
R: Renderer,
|
||||
N: Render,
|
||||
{
|
||||
// use wasm-bindgen-futures to drive the reactive system
|
||||
// we ignore the return value because an Err here just means the wasm-bindgen executor is
|
||||
@@ -173,11 +159,7 @@ where
|
||||
|
||||
// returns a handle that owns the owner
|
||||
// when this is dropped, it will clean up the reactive system and unmount the view
|
||||
UnmountHandle {
|
||||
owner,
|
||||
mountable,
|
||||
rndr: PhantomData,
|
||||
}
|
||||
UnmountHandle { owner, mountable }
|
||||
}
|
||||
|
||||
/// Hydrates any islands that are currently present on the page.
|
||||
@@ -211,21 +193,18 @@ pub fn hydrate_islands() {
|
||||
reactive system. You should either call `.forget()` to keep the \
|
||||
view permanently mounted, or store the `UnmountHandle` somewhere \
|
||||
and drop it when you'd like to unmount the view."]
|
||||
pub struct UnmountHandle<M, R>
|
||||
pub struct UnmountHandle<M>
|
||||
where
|
||||
M: Mountable<R>,
|
||||
R: Renderer,
|
||||
M: Mountable,
|
||||
{
|
||||
#[allow(dead_code)]
|
||||
owner: Owner,
|
||||
mountable: M,
|
||||
rndr: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<M, R> UnmountHandle<M, R>
|
||||
impl<M> UnmountHandle<M>
|
||||
where
|
||||
M: Mountable<R>,
|
||||
R: Renderer,
|
||||
M: Mountable,
|
||||
{
|
||||
/// Leaks the handle, preventing the reactive system from being cleaned up and the view from
|
||||
/// being unmounted. This should always be called when [`mount_to`] is used for the root of an
|
||||
@@ -235,10 +214,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, R> Drop for UnmountHandle<M, R>
|
||||
impl<M> Drop for UnmountHandle<M>
|
||||
where
|
||||
M: Mountable<R>,
|
||||
R: Renderer,
|
||||
M: Mountable,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
self.mountable.unmount();
|
||||
|
||||
@@ -6,7 +6,7 @@ use base64::{
|
||||
};
|
||||
use rand::{thread_rng, RngCore};
|
||||
use std::{fmt::Display, ops::Deref, sync::Arc};
|
||||
use tachys::{html::attribute::AttributeValue, renderer::Renderer};
|
||||
use tachys::html::attribute::AttributeValue;
|
||||
|
||||
/// A cryptographic nonce ("number used once") which can be
|
||||
/// used by Content Security Policy to determine whether or not a given
|
||||
@@ -65,12 +65,9 @@ impl Display for Nonce {
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> AttributeValue<R> for Nonce
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
impl AttributeValue for Nonce {
|
||||
type AsyncOutput = Self;
|
||||
type State = <Arc<str> as AttributeValue<R>>::State;
|
||||
type State = <Arc<str> as AttributeValue>::State;
|
||||
type Cloneable = Self;
|
||||
type CloneableOwned = Self;
|
||||
|
||||
@@ -79,7 +76,7 @@ where
|
||||
}
|
||||
|
||||
fn to_html(self, key: &str, buf: &mut String) {
|
||||
<Arc<str> as AttributeValue<R>>::to_html(self.0, key, buf)
|
||||
<Arc<str> as AttributeValue>::to_html(self.0, key, buf)
|
||||
}
|
||||
|
||||
fn to_template(_key: &str, _buf: &mut String) {}
|
||||
@@ -87,17 +84,21 @@ where
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
key: &str,
|
||||
el: &<R as Renderer>::Element,
|
||||
el: &tachys::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
<Arc<str> as AttributeValue<R>>::hydrate::<FROM_SERVER>(self.0, key, el)
|
||||
<Arc<str> as AttributeValue>::hydrate::<FROM_SERVER>(self.0, key, el)
|
||||
}
|
||||
|
||||
fn build(self, el: &<R as Renderer>::Element, key: &str) -> Self::State {
|
||||
<Arc<str> as AttributeValue<R>>::build(self.0, el, key)
|
||||
fn build(
|
||||
self,
|
||||
el: &tachys::renderer::types::Element,
|
||||
key: &str,
|
||||
) -> Self::State {
|
||||
<Arc<str> as AttributeValue>::build(self.0, el, key)
|
||||
}
|
||||
|
||||
fn rebuild(self, key: &str, state: &mut Self::State) {
|
||||
<Arc<str> as AttributeValue<R>>::rebuild(self.0, key, state)
|
||||
<Arc<str> as AttributeValue>::rebuild(self.0, key, state)
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
|
||||
@@ -21,7 +21,6 @@ use tachys::{
|
||||
html::attribute::Attribute,
|
||||
hydration::Cursor,
|
||||
reactive_graph::{OwnedView, OwnedViewState},
|
||||
renderer::Renderer,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr,
|
||||
@@ -135,15 +134,14 @@ pub(crate) struct SuspenseBoundary<const TRANSITION: bool, Fal, Chil> {
|
||||
pub children: Chil,
|
||||
}
|
||||
|
||||
impl<const TRANSITION: bool, Fal, Chil, Rndr> Render<Rndr>
|
||||
impl<const TRANSITION: bool, Fal, Chil> Render
|
||||
for SuspenseBoundary<TRANSITION, Fal, Chil>
|
||||
where
|
||||
Fal: Render<Rndr> + Send + 'static,
|
||||
Chil: Render<Rndr> + Send + 'static,
|
||||
Rndr: Renderer + 'static,
|
||||
Fal: Render + Send + 'static,
|
||||
Chil: Render + Send + 'static,
|
||||
{
|
||||
type State = RenderEffect<
|
||||
OwnedViewState<EitherKeepAliveState<Chil::State, Fal::State>, Rndr>,
|
||||
OwnedViewState<EitherKeepAliveState<Chil::State, Fal::State>>,
|
||||
>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
@@ -187,25 +185,24 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<const TRANSITION: bool, Fal, Chil, Rndr> AddAnyAttr<Rndr>
|
||||
impl<const TRANSITION: bool, Fal, Chil> AddAnyAttr
|
||||
for SuspenseBoundary<TRANSITION, Fal, Chil>
|
||||
where
|
||||
Fal: RenderHtml<Rndr> + Send + 'static,
|
||||
Chil: RenderHtml<Rndr> + Send + 'static,
|
||||
Rndr: Renderer + 'static,
|
||||
Fal: RenderHtml + Send + 'static,
|
||||
Chil: RenderHtml + Send + 'static,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Rndr>> = SuspenseBoundary<
|
||||
type Output<SomeNewAttr: Attribute> = SuspenseBoundary<
|
||||
TRANSITION,
|
||||
Fal,
|
||||
Chil::Output<SomeNewAttr::CloneableOwned>,
|
||||
>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
let attr = attr.into_cloneable_owned();
|
||||
let SuspenseBoundary {
|
||||
@@ -223,12 +220,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<const TRANSITION: bool, Fal, Chil, Rndr> RenderHtml<Rndr>
|
||||
impl<const TRANSITION: bool, Fal, Chil> RenderHtml
|
||||
for SuspenseBoundary<TRANSITION, Fal, Chil>
|
||||
where
|
||||
Fal: RenderHtml<Rndr> + Send + 'static,
|
||||
Chil: RenderHtml<Rndr> + Send + 'static,
|
||||
Rndr: Renderer + 'static,
|
||||
Fal: RenderHtml + Send + 'static,
|
||||
Chil: RenderHtml + Send + 'static,
|
||||
{
|
||||
// i.e., if this is the child of another Suspense during SSR, don't wait for it: it will handle
|
||||
// itself
|
||||
@@ -405,7 +401,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Rndr>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let cursor = cursor.to_owned();
|
||||
@@ -455,10 +451,9 @@ impl<T> Unsuspend<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rndr> Render<Rndr> for Unsuspend<T>
|
||||
impl<T> Render for Unsuspend<T>
|
||||
where
|
||||
T: Render<Rndr>,
|
||||
Rndr: Renderer,
|
||||
T: Render,
|
||||
{
|
||||
type State = T::State;
|
||||
|
||||
@@ -471,30 +466,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rndr> AddAnyAttr<Rndr> for Unsuspend<T>
|
||||
impl<T> AddAnyAttr for Unsuspend<T>
|
||||
where
|
||||
T: AddAnyAttr<Rndr> + 'static,
|
||||
Rndr: Renderer,
|
||||
T: AddAnyAttr + 'static,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Rndr>> =
|
||||
type Output<SomeNewAttr: Attribute> =
|
||||
Unsuspend<T::Output<SomeNewAttr::CloneableOwned>>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
let attr = attr.into_cloneable_owned();
|
||||
Unsuspend::new(move || (self.0)().add_any_attr(attr))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rndr> RenderHtml<Rndr> for Unsuspend<T>
|
||||
impl<T> RenderHtml for Unsuspend<T>
|
||||
where
|
||||
T: RenderHtml<Rndr> + 'static,
|
||||
Rndr: Renderer,
|
||||
T: RenderHtml + 'static,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
@@ -535,7 +528,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Rndr>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
(self.0)().hydrate::<FROM_SERVER>(cursor, position)
|
||||
|
||||
@@ -5,7 +5,7 @@ fn simple_ssr_test() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
let (value, set_value) = signal(0);
|
||||
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
let rendered: View<HtmlElement<_, _, _>> = view! {
|
||||
<div>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
|
||||
<span>"Value: " {move || value.get().to_string()} "!"</span>
|
||||
@@ -36,7 +36,7 @@ fn ssr_test_with_components() {
|
||||
}
|
||||
}
|
||||
|
||||
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
let rendered: View<HtmlElement<_, _, _>> = view! {
|
||||
<div class="counters">
|
||||
<Counter initial_value=1/>
|
||||
<Counter initial_value=2/>
|
||||
@@ -66,7 +66,7 @@ fn ssr_test_with_snake_case_components() {
|
||||
</div>
|
||||
}
|
||||
}
|
||||
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
let rendered: View<HtmlElement<_, _, _>> = view! {
|
||||
<div class="counters">
|
||||
<SnakeCaseCounter initial_value=1/>
|
||||
<SnakeCaseCounter initial_value=2/>
|
||||
@@ -86,7 +86,7 @@ fn test_classes() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
let (value, _set_value) = signal(5);
|
||||
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||
let rendered: View<HtmlElement<_, _, _>> = view! {
|
||||
<div
|
||||
class="my big"
|
||||
class:a=move || { value.get() > 10 }
|
||||
@@ -104,7 +104,7 @@ fn ssr_with_styles() {
|
||||
|
||||
let (_, set_value) = signal(0);
|
||||
let styles = "myclass";
|
||||
let rendered: View<HtmlElement<_, _, _, Dom>> = view! { class=styles,
|
||||
let rendered: View<HtmlElement<_, _, _>> = view! { class=styles,
|
||||
<div>
|
||||
<button class="btn" on:click=move |_| set_value.update(|value| *value -= 1)>
|
||||
"-1"
|
||||
@@ -124,7 +124,7 @@ fn ssr_option() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
let (_, _) = signal(0);
|
||||
let rendered: View<HtmlElement<_, _, _, Dom>> = view! { <option></option> };
|
||||
let rendered: View<HtmlElement<_, _, _>> = view! { <option></option> };
|
||||
|
||||
assert_eq!(rendered.to_html(), "<option></option>");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.7.0-beta5"
|
||||
version = "0.7.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -347,41 +347,60 @@ impl ToTokens for Model {
|
||||
let island_props = if is_island_with_children
|
||||
|| is_island_with_other_props
|
||||
{
|
||||
let (destructure, prop_builders) = if is_island_with_other_props
|
||||
{
|
||||
let prop_names = props
|
||||
.iter()
|
||||
.filter_map(|prop| {
|
||||
if prop.name.ident == "children" {
|
||||
None
|
||||
} else {
|
||||
let name = &prop.name.ident;
|
||||
Some(quote! { #name, })
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
let destructure = quote! {
|
||||
let #props_serialized_name {
|
||||
#prop_names
|
||||
} = props;
|
||||
let (destructure, prop_builders, optional_props) =
|
||||
if is_island_with_other_props {
|
||||
let prop_names = props
|
||||
.iter()
|
||||
.filter_map(|prop| {
|
||||
if prop.name.ident == "children" {
|
||||
None
|
||||
} else {
|
||||
let name = &prop.name.ident;
|
||||
Some(quote! { #name, })
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
let destructure = quote! {
|
||||
let #props_serialized_name {
|
||||
#prop_names
|
||||
} = props;
|
||||
};
|
||||
let prop_builders = props
|
||||
.iter()
|
||||
.filter_map(|prop| {
|
||||
if prop.name.ident == "children"
|
||||
|| prop.prop_opts.optional
|
||||
{
|
||||
None
|
||||
} else {
|
||||
let name = &prop.name.ident;
|
||||
Some(quote! {
|
||||
.#name(#name)
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
let optional_props = props
|
||||
.iter()
|
||||
.filter_map(|prop| {
|
||||
if prop.name.ident == "children"
|
||||
|| !prop.prop_opts.optional
|
||||
{
|
||||
None
|
||||
} else {
|
||||
let name = &prop.name.ident;
|
||||
Some(quote! {
|
||||
if let Some(#name) = #name {
|
||||
props.#name = Some(#name)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
(destructure, prop_builders, optional_props)
|
||||
} else {
|
||||
(quote! {}, quote! {}, quote! {})
|
||||
};
|
||||
let prop_builders = props
|
||||
.iter()
|
||||
.filter_map(|prop| {
|
||||
if prop.name.ident == "children" {
|
||||
None
|
||||
} else {
|
||||
let name = &prop.name.ident;
|
||||
Some(quote! {
|
||||
.#name(#name)
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
(destructure, prop_builders)
|
||||
} else {
|
||||
(quote! {}, quote! {})
|
||||
};
|
||||
let children = if is_island_with_children {
|
||||
quote! {
|
||||
.children({Box::new(|| {
|
||||
@@ -405,10 +424,14 @@ impl ToTokens for Model {
|
||||
|
||||
quote! {{
|
||||
#destructure
|
||||
#props_name::builder()
|
||||
let mut props = #props_name::builder()
|
||||
#prop_builders
|
||||
#children
|
||||
.build()
|
||||
.build();
|
||||
|
||||
#optional_props
|
||||
|
||||
props
|
||||
}}
|
||||
} else {
|
||||
quote! {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{fragment_to_tokens, TagType};
|
||||
use crate::view::attribute_absolute;
|
||||
use crate::view::{attribute_absolute, utils::filter_prefixed_attrs};
|
||||
use proc_macro2::{Ident, TokenStream, TokenTree};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use rstml::node::{
|
||||
@@ -105,20 +105,13 @@ pub(crate) fn component_to_tokens(
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let inputs = &binding.inputs;
|
||||
Some(quote! { #inputs })
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let items_to_clone = attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
attr.key
|
||||
.to_string()
|
||||
.strip_prefix("clone:")
|
||||
.map(|ident| format_ident!("{ident}", span = attr.key.span()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let items_to_clone = filter_prefixed_attrs(attrs.iter(), "clone:");
|
||||
|
||||
// include all attribute that are either
|
||||
// 1) blocks ({..attrs} or {attrs}),
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
mod component_builder;
|
||||
mod slot_helper;
|
||||
mod utils;
|
||||
|
||||
use self::{
|
||||
component_builder::component_to_tokens,
|
||||
slot_helper::{get_slot, slot_to_tokens},
|
||||
};
|
||||
use convert_case::{Case::Snake, Casing};
|
||||
use convert_case::{
|
||||
Case::{Snake, UpperCamel},
|
||||
Casing,
|
||||
};
|
||||
use leptos_hot_reload::parsing::{is_component_node, value_to_string};
|
||||
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
|
||||
use proc_macro_error2::abort;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use quote::{format_ident, quote, quote_spanned, ToTokens};
|
||||
use rstml::node::{
|
||||
CustomNode, KVAttributeValue, KeyedAttribute, Node, NodeAttribute,
|
||||
NodeBlock, NodeElement, NodeName, NodeNameFragment,
|
||||
@@ -874,6 +879,8 @@ fn attribute_to_tokens(
|
||||
directive_call_from_attribute_node(node, name)
|
||||
} else if let Some(name) = name.strip_prefix("on:") {
|
||||
event_to_tokens(name, node)
|
||||
} else if let Some(name) = name.strip_prefix("bind:") {
|
||||
two_way_binding_to_tokens(name, node)
|
||||
} else if let Some(name) = name.strip_prefix("class:") {
|
||||
let class = match &node.key {
|
||||
NodeName::Punctuated(parts) => &parts[0],
|
||||
@@ -1057,6 +1064,20 @@ pub(crate) fn attribute_absolute(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn two_way_binding_to_tokens(
|
||||
name: &str,
|
||||
node: &KeyedAttribute,
|
||||
) -> TokenStream {
|
||||
let value = attribute_value(node);
|
||||
|
||||
let ident =
|
||||
format_ident!("{}", name.to_case(UpperCamel), span = node.key.span());
|
||||
|
||||
quote! {
|
||||
.bind(::leptos::attr::#ident, #value)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn event_to_tokens(
|
||||
name: &str,
|
||||
node: &KeyedAttribute,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{convert_to_snake_case, ident_from_tag_name};
|
||||
use crate::view::{fragment_to_tokens, TagType};
|
||||
use crate::view::{fragment_to_tokens, utils::filter_prefixed_attrs, TagType};
|
||||
use proc_macro2::{Ident, TokenStream, TokenTree};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use quote::{quote, quote_spanned};
|
||||
use rstml::node::{CustomNode, KeyedAttribute, NodeAttribute, NodeElement};
|
||||
use std::collections::HashMap;
|
||||
use syn::spanned::Spanned;
|
||||
@@ -70,25 +70,9 @@ pub(crate) fn slot_to_tokens(
|
||||
}
|
||||
});
|
||||
|
||||
let items_to_bind = attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
attr.key
|
||||
.to_string()
|
||||
.strip_prefix("let:")
|
||||
.map(|ident| format_ident!("{ident}", span = attr.key.span()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let items_to_bind = filter_prefixed_attrs(attrs.iter(), "let:");
|
||||
|
||||
let items_to_clone = attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
attr.key
|
||||
.to_string()
|
||||
.strip_prefix("clone:")
|
||||
.map(|ident| format_ident!("{ident}", span = attr.key.span()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let items_to_clone = filter_prefixed_attrs(attrs.iter(), "clone:");
|
||||
|
||||
let dyn_attrs = attrs
|
||||
.iter()
|
||||
|
||||
19
leptos_macro/src/view/utils.rs
Normal file
19
leptos_macro/src/view/utils.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use proc_macro2::Ident;
|
||||
use quote::format_ident;
|
||||
use rstml::node::KeyedAttribute;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
pub fn filter_prefixed_attrs<'a, A>(attrs: A, prefix: &str) -> Vec<Ident>
|
||||
where
|
||||
A: IntoIterator<Item = &'a KeyedAttribute> + Clone,
|
||||
{
|
||||
attrs
|
||||
.into_iter()
|
||||
.filter_map(|attr| {
|
||||
attr.key
|
||||
.to_string()
|
||||
.strip_prefix(prefix)
|
||||
.map(|ident| format_ident!("{ident}", span = attr.key.span()))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -188,20 +188,18 @@ mod view_implementations {
|
||||
html::attribute::Attribute,
|
||||
hydration::Cursor,
|
||||
reactive_graph::{RenderEffectState, Suspend, SuspendState},
|
||||
renderer::Renderer,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml,
|
||||
},
|
||||
};
|
||||
|
||||
impl<T, R, Ser> Render<R> for Resource<T, Ser>
|
||||
impl<T, Ser> Render for Resource<T, Ser>
|
||||
where
|
||||
T: Render<R> + Send + Sync + Clone,
|
||||
T: Render + Send + Sync + Clone,
|
||||
Ser: Send + 'static,
|
||||
R: Renderer,
|
||||
{
|
||||
type State = RenderEffectState<SuspendState<T, R>>;
|
||||
type State = RenderEffectState<SuspendState<T>>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
(move || Suspend::new(async move { self.await })).build()
|
||||
@@ -212,19 +210,18 @@ mod view_implementations {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R, Ser> AddAnyAttr<R> for Resource<T, Ser>
|
||||
impl<T, Ser> AddAnyAttr for Resource<T, Ser>
|
||||
where
|
||||
T: RenderHtml<R> + Send + Sync + Clone,
|
||||
T: RenderHtml + Send + Sync + Clone,
|
||||
Ser: Send + 'static,
|
||||
R: Renderer,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<R>> = Box<
|
||||
type Output<SomeNewAttr: Attribute> = Box<
|
||||
dyn FnMut() -> Suspend<
|
||||
Pin<
|
||||
Box<
|
||||
dyn Future<
|
||||
Output = <T as AddAnyAttr<R>>::Output<
|
||||
<SomeNewAttr::CloneableOwned as Attribute<R>>::CloneableOwned,
|
||||
Output = <T as AddAnyAttr>::Output<
|
||||
<SomeNewAttr::CloneableOwned as Attribute>::CloneableOwned,
|
||||
>,
|
||||
> + Send,
|
||||
>,
|
||||
@@ -232,22 +229,21 @@ mod view_implementations {
|
||||
> + Send,
|
||||
>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<R>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<R>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
(move || Suspend::new(async move { self.await })).add_any_attr(attr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R, Ser> RenderHtml<R> for Resource<T, Ser>
|
||||
impl<T, Ser> RenderHtml for Resource<T, Ser>
|
||||
where
|
||||
T: RenderHtml<R> + Send + Sync + Clone,
|
||||
T: RenderHtml + Send + Sync + Clone,
|
||||
Ser: Send + 'static,
|
||||
R: Renderer,
|
||||
{
|
||||
type AsyncOutput = Option<T>;
|
||||
|
||||
@@ -296,7 +292,7 @@ mod view_implementations {
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<R>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
(move || Suspend::new(async move { self.await }))
|
||||
|
||||
@@ -24,7 +24,35 @@ use reactive_graph::{
|
||||
prelude::*,
|
||||
signal::{ArcRwSignal, RwSignal},
|
||||
};
|
||||
use std::{future::IntoFuture, ops::Deref, panic::Location};
|
||||
use std::{
|
||||
future::{pending, IntoFuture},
|
||||
ops::Deref,
|
||||
panic::Location,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
static IS_SUPPRESSING_RESOURCE_LOAD: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub struct SuppressResourceLoad;
|
||||
|
||||
impl SuppressResourceLoad {
|
||||
pub fn new() -> Self {
|
||||
IS_SUPPRESSING_RESOURCE_LOAD.store(true, Ordering::Relaxed);
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SuppressResourceLoad {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SuppressResourceLoad {
|
||||
fn drop(&mut self) {
|
||||
IS_SUPPRESSING_RESOURCE_LOAD.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ArcResource<T, Ser = JsonSerdeCodec> {
|
||||
ser: PhantomData<Ser>,
|
||||
@@ -77,6 +105,49 @@ impl<T, Ser> Deref for ArcResource<T, Ser> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> Track for ArcResource<T, Ser>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn track(&self) {
|
||||
self.data.track();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> ReadUntracked for ArcResource<T, Ser>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Value = <ArcAsyncDerived<T> as ReadUntracked>::Value;
|
||||
|
||||
#[track_caller]
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
#[cfg(all(feature = "hydration", debug_assertions))]
|
||||
{
|
||||
use reactive_graph::{
|
||||
computed::suspense::SuspenseContext, owner::use_context,
|
||||
};
|
||||
let suspense = use_context::<SuspenseContext>();
|
||||
if suspense.is_none() {
|
||||
let location = std::panic::Location::caller();
|
||||
reactive_graph::log_warning(format_args!(
|
||||
"At {location}, you are reading a resource in `hydrate` \
|
||||
mode outside a <Suspense/> or <Transition/>. This can \
|
||||
cause hydration mismatch errors and loses out on a \
|
||||
significant performance optimization. To fix this issue, \
|
||||
you can either: \n1. Wrap the place where you read the \
|
||||
resource in a <Suspense/> or <Transition/> component, or \
|
||||
\n2. Switch to using ArcLocalResource::new(), which will \
|
||||
wait to load the resource until the app is hydrated on \
|
||||
the client side. (This will have worse performance in \
|
||||
most cases.)",
|
||||
));
|
||||
}
|
||||
}
|
||||
self.data.try_read_untracked()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> ArcResource<T, Ser>
|
||||
where
|
||||
Ser: Encoder<T> + Decoder<T>,
|
||||
@@ -116,7 +187,14 @@ where
|
||||
let source = source.clone();
|
||||
move || {
|
||||
let (_, source) = source.get();
|
||||
fetcher(source)
|
||||
let fut = fetcher(source);
|
||||
async move {
|
||||
if IS_SUPPRESSING_RESOURCE_LOAD.load(Ordering::Relaxed) {
|
||||
pending().await
|
||||
} else {
|
||||
fut.await
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -529,6 +607,49 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> Track for Resource<T, Ser>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
fn track(&self) {
|
||||
self.data.track();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> ReadUntracked for Resource<T, Ser>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
type Value = <AsyncDerived<T> as ReadUntracked>::Value;
|
||||
|
||||
#[track_caller]
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
#[cfg(all(feature = "hydration", debug_assertions))]
|
||||
{
|
||||
use reactive_graph::{
|
||||
computed::suspense::SuspenseContext, owner::use_context,
|
||||
};
|
||||
let suspense = use_context::<SuspenseContext>();
|
||||
if suspense.is_none() {
|
||||
let location = std::panic::Location::caller();
|
||||
reactive_graph::log_warning(format_args!(
|
||||
"At {location}, you are reading a resource in `hydrate` \
|
||||
mode outside a <Suspense/> or <Transition/>. This can \
|
||||
cause hydration mismatch errors and loses out on a \
|
||||
significant performance optimization. To fix this issue, \
|
||||
you can either: \n1. Wrap the place where you read the \
|
||||
resource in a <Suspense/> or <Transition/> component, or \
|
||||
\n2. Switch to using LocalResource::new(), which will \
|
||||
wait to load the resource until the app is hydrated on \
|
||||
the client side. (This will have worse performance in \
|
||||
most cases.)",
|
||||
));
|
||||
}
|
||||
}
|
||||
self.data.try_read_untracked()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Resource<T, FromToStringCodec>
|
||||
where
|
||||
FromToStringCodec: Encoder<T> + Decoder<T>,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.7.0-beta5"
|
||||
version = "0.7.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
|
||||
@@ -7,7 +7,6 @@ use leptos::{
|
||||
dom::document,
|
||||
html::attribute::Attribute,
|
||||
hydration::Cursor,
|
||||
renderer::{dom::Dom, Renderer},
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
@@ -56,14 +55,14 @@ struct BodyView<At> {
|
||||
|
||||
struct BodyViewState<At>
|
||||
where
|
||||
At: Attribute<Dom>,
|
||||
At: Attribute,
|
||||
{
|
||||
attributes: At::State,
|
||||
}
|
||||
|
||||
impl<At> Render<Dom> for BodyView<At>
|
||||
impl<At> Render for BodyView<At>
|
||||
where
|
||||
At: Attribute<Dom>,
|
||||
At: Attribute,
|
||||
{
|
||||
type State = BodyViewState<At>;
|
||||
|
||||
@@ -79,19 +78,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<At> AddAnyAttr<Dom> for BodyView<At>
|
||||
impl<At> AddAnyAttr for BodyView<At>
|
||||
where
|
||||
At: Attribute<Dom>,
|
||||
At: Attribute,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Dom>> =
|
||||
BodyView<<At as NextAttribute<Dom>>::Output<SomeNewAttr>>;
|
||||
type Output<SomeNewAttr: Attribute> =
|
||||
BodyView<<At as NextAttribute>::Output<SomeNewAttr>>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Dom>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Dom>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
BodyView {
|
||||
attributes: self.attributes.add_any_attr(attr),
|
||||
@@ -99,9 +98,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<At> RenderHtml<Dom> for BodyView<At>
|
||||
impl<At> RenderHtml for BodyView<At>
|
||||
where
|
||||
At: Attribute<Dom>,
|
||||
At: Attribute,
|
||||
{
|
||||
type AsyncOutput = BodyView<At::AsyncOutput>;
|
||||
|
||||
@@ -135,7 +134,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
_cursor: &Cursor<Dom>,
|
||||
_cursor: &Cursor,
|
||||
_position: &PositionState,
|
||||
) -> Self::State {
|
||||
let el = document().body().expect("there to be a <body> element");
|
||||
@@ -145,20 +144,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<At> Mountable<Dom> for BodyViewState<At>
|
||||
impl<At> Mountable for BodyViewState<At>
|
||||
where
|
||||
At: Attribute<Dom>,
|
||||
At: Attribute,
|
||||
{
|
||||
fn unmount(&mut self) {}
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
_parent: &<Dom as Renderer>::Element,
|
||||
_marker: Option<&<Dom as Renderer>::Node>,
|
||||
_parent: &leptos::tachys::renderer::types::Element,
|
||||
_marker: Option<&leptos::tachys::renderer::types::Node>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, _child: &mut dyn Mountable<Dom>) -> bool {
|
||||
fn insert_before_this(&self, _child: &mut dyn Mountable) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use leptos::{
|
||||
dom::document,
|
||||
html::attribute::Attribute,
|
||||
hydration::Cursor,
|
||||
renderer::{dom::Dom, Renderer},
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
@@ -53,14 +52,14 @@ struct HtmlView<At> {
|
||||
|
||||
struct HtmlViewState<At>
|
||||
where
|
||||
At: Attribute<Dom>,
|
||||
At: Attribute,
|
||||
{
|
||||
attributes: At::State,
|
||||
}
|
||||
|
||||
impl<At> Render<Dom> for HtmlView<At>
|
||||
impl<At> Render for HtmlView<At>
|
||||
where
|
||||
At: Attribute<Dom>,
|
||||
At: Attribute,
|
||||
{
|
||||
type State = HtmlViewState<At>;
|
||||
|
||||
@@ -79,19 +78,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<At> AddAnyAttr<Dom> for HtmlView<At>
|
||||
impl<At> AddAnyAttr for HtmlView<At>
|
||||
where
|
||||
At: Attribute<Dom>,
|
||||
At: Attribute,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Dom>> =
|
||||
HtmlView<<At as NextAttribute<Dom>>::Output<SomeNewAttr>>;
|
||||
type Output<SomeNewAttr: Attribute> =
|
||||
HtmlView<<At as NextAttribute>::Output<SomeNewAttr>>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Dom>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Dom>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
HtmlView {
|
||||
attributes: self.attributes.add_any_attr(attr),
|
||||
@@ -99,9 +98,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<At> RenderHtml<Dom> for HtmlView<At>
|
||||
impl<At> RenderHtml for HtmlView<At>
|
||||
where
|
||||
At: Attribute<Dom>,
|
||||
At: Attribute,
|
||||
{
|
||||
type AsyncOutput = HtmlView<At::AsyncOutput>;
|
||||
|
||||
@@ -135,7 +134,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
_cursor: &Cursor<Dom>,
|
||||
_cursor: &Cursor,
|
||||
_position: &PositionState,
|
||||
) -> Self::State {
|
||||
let el = document()
|
||||
@@ -148,22 +147,22 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<At> Mountable<Dom> for HtmlViewState<At>
|
||||
impl<At> Mountable for HtmlViewState<At>
|
||||
where
|
||||
At: Attribute<Dom>,
|
||||
At: Attribute,
|
||||
{
|
||||
fn unmount(&mut self) {}
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
_parent: &<Dom as Renderer>::Element,
|
||||
_marker: Option<&<Dom as Renderer>::Node>,
|
||||
_parent: &leptos::tachys::renderer::types::Element,
|
||||
_marker: Option<&leptos::tachys::renderer::types::Node>,
|
||||
) {
|
||||
// <Html> only sets attributes
|
||||
// the <html> tag doesn't need to be mounted anywhere, of course
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, _child: &mut dyn Mountable<Dom>) -> bool {
|
||||
fn insert_before_this(&self, _child: &mut dyn Mountable) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +57,9 @@ use leptos::{
|
||||
dom::document,
|
||||
html::{
|
||||
attribute::Attribute,
|
||||
element::{CreateElement, ElementType, HtmlElement},
|
||||
element::{ElementType, HtmlElement},
|
||||
},
|
||||
hydration::Cursor,
|
||||
renderer::{dom::Dom, Renderer},
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
@@ -106,7 +105,7 @@ pub struct MetaContext {
|
||||
/// Metadata associated with the `<title>` element.
|
||||
pub(crate) title: TitleContext,
|
||||
/// The hydration cursor for the location in the `<head>` for arbitrary tags will be rendered.
|
||||
pub(crate) cursor: Arc<Lazy<SendWrapper<Cursor<Dom>>>>,
|
||||
pub(crate) cursor: Arc<Lazy<SendWrapper<Cursor>>>,
|
||||
}
|
||||
|
||||
impl MetaContext {
|
||||
@@ -123,7 +122,7 @@ const COMMENT_NODE: u16 = 8;
|
||||
|
||||
impl Default for MetaContext {
|
||||
fn default() -> Self {
|
||||
let build_cursor: fn() -> SendWrapper<Cursor<Dom>> = || {
|
||||
let build_cursor: fn() -> SendWrapper<Cursor> = || {
|
||||
let head = document().head().expect("missing <head> element");
|
||||
let mut cursor = None;
|
||||
let mut child = head.first_child();
|
||||
@@ -323,10 +322,10 @@ pub fn use_head() -> MetaContext {
|
||||
}
|
||||
|
||||
pub(crate) fn register<E, At, Ch>(
|
||||
el: HtmlElement<E, At, Ch, Dom>,
|
||||
el: HtmlElement<E, At, Ch>,
|
||||
) -> RegisteredMetaTag<E, At, Ch>
|
||||
where
|
||||
HtmlElement<E, At, Ch, Dom>: RenderHtml<Dom>,
|
||||
HtmlElement<E, At, Ch>: RenderHtml,
|
||||
{
|
||||
#[allow(unused_mut)] // used for `ssr`
|
||||
let mut el = Some(el);
|
||||
@@ -358,19 +357,19 @@ where
|
||||
struct RegisteredMetaTag<E, At, Ch> {
|
||||
// this is `None` if we've already taken it out to render to HTML on the server
|
||||
// we don't render it in place in RenderHtml, so it's fine
|
||||
el: Option<HtmlElement<E, At, Ch, Dom>>,
|
||||
el: Option<HtmlElement<E, At, Ch>>,
|
||||
}
|
||||
|
||||
struct RegisteredMetaTagState<E, At, Ch>
|
||||
where
|
||||
HtmlElement<E, At, Ch, Dom>: Render<Dom>,
|
||||
HtmlElement<E, At, Ch>: Render,
|
||||
{
|
||||
state: <HtmlElement<E, At, Ch, Dom> as Render<Dom>>::State,
|
||||
state: <HtmlElement<E, At, Ch> as Render>::State,
|
||||
}
|
||||
|
||||
impl<E, At, Ch> Drop for RegisteredMetaTagState<E, At, Ch>
|
||||
where
|
||||
HtmlElement<E, At, Ch, Dom>: Render<Dom>,
|
||||
HtmlElement<E, At, Ch>: Render,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
self.state.unmount();
|
||||
@@ -387,11 +386,11 @@ fn document_head() -> HtmlHeadElement {
|
||||
})
|
||||
}
|
||||
|
||||
impl<E, At, Ch> Render<Dom> for RegisteredMetaTag<E, At, Ch>
|
||||
impl<E, At, Ch> Render for RegisteredMetaTag<E, At, Ch>
|
||||
where
|
||||
E: ElementType + CreateElement<Dom>,
|
||||
At: Attribute<Dom>,
|
||||
Ch: Render<Dom>,
|
||||
E: ElementType,
|
||||
At: Attribute,
|
||||
Ch: Render,
|
||||
{
|
||||
type State = RegisteredMetaTagState<E, At, Ch>;
|
||||
|
||||
@@ -405,24 +404,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, At, Ch> AddAnyAttr<Dom> for RegisteredMetaTag<E, At, Ch>
|
||||
impl<E, At, Ch> AddAnyAttr for RegisteredMetaTag<E, At, Ch>
|
||||
where
|
||||
E: ElementType + CreateElement<Dom> + Send,
|
||||
At: Attribute<Dom> + Send,
|
||||
Ch: RenderHtml<Dom> + Send,
|
||||
E: ElementType + Send,
|
||||
At: Attribute + Send,
|
||||
Ch: RenderHtml + Send,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Dom>> = RegisteredMetaTag<
|
||||
E,
|
||||
<At as NextAttribute<Dom>>::Output<SomeNewAttr>,
|
||||
Ch,
|
||||
>;
|
||||
type Output<SomeNewAttr: Attribute> =
|
||||
RegisteredMetaTag<E, <At as NextAttribute>::Output<SomeNewAttr>, Ch>;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Dom>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Dom>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
RegisteredMetaTag {
|
||||
el: self.el.map(|inner| inner.add_any_attr(attr)),
|
||||
@@ -430,11 +426,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, At, Ch> RenderHtml<Dom> for RegisteredMetaTag<E, At, Ch>
|
||||
impl<E, At, Ch> RenderHtml for RegisteredMetaTag<E, At, Ch>
|
||||
where
|
||||
E: ElementType + CreateElement<Dom>,
|
||||
At: Attribute<Dom>,
|
||||
Ch: RenderHtml<Dom> + Send,
|
||||
E: ElementType,
|
||||
At: Attribute,
|
||||
Ch: RenderHtml + Send,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
@@ -461,7 +457,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
_cursor: &Cursor<Dom>,
|
||||
_cursor: &Cursor,
|
||||
_position: &PositionState,
|
||||
) -> Self::State {
|
||||
let cursor = use_context::<MetaContext>()
|
||||
@@ -471,18 +467,18 @@ where
|
||||
)
|
||||
.cursor;
|
||||
let state = self.el.unwrap().hydrate::<FROM_SERVER>(
|
||||
&*cursor,
|
||||
&cursor,
|
||||
&PositionState::new(Position::NextChild),
|
||||
);
|
||||
RegisteredMetaTagState { state }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, At, Ch> Mountable<Dom> for RegisteredMetaTagState<E, At, Ch>
|
||||
impl<E, At, Ch> Mountable for RegisteredMetaTagState<E, At, Ch>
|
||||
where
|
||||
E: ElementType + CreateElement<Dom>,
|
||||
At: Attribute<Dom>,
|
||||
Ch: Render<Dom>,
|
||||
E: ElementType,
|
||||
At: Attribute,
|
||||
Ch: Render,
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
self.state.unmount();
|
||||
@@ -490,8 +486,8 @@ where
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
_parent: &<Dom as Renderer>::Element,
|
||||
_marker: Option<&<Dom as Renderer>::Node>,
|
||||
_parent: &leptos::tachys::renderer::types::Element,
|
||||
_marker: Option<&leptos::tachys::renderer::types::Node>,
|
||||
) {
|
||||
// we always mount this to the <head>, which is the whole point
|
||||
// but this shouldn't warn about the parent being a regular element or being unused
|
||||
@@ -500,7 +496,7 @@ where
|
||||
self.state.mount(&document_head(), None);
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, _child: &mut dyn Mountable<Dom>) -> bool {
|
||||
fn insert_before_this(&self, _child: &mut dyn Mountable) -> bool {
|
||||
// Registered meta tags will be mounted in the <head>, but *seem* to be mounted somewhere
|
||||
// else in the DOM. We should never tell the renderer that we have successfully mounted
|
||||
// something before this, because if e.g., a <Meta/> is the first item in an Either, then
|
||||
@@ -525,7 +521,7 @@ struct MetaTagsView;
|
||||
// rendering HTML for all the tags that will be injected into the `<head>`
|
||||
//
|
||||
// client-side rendering is handled by the individual components
|
||||
impl Render<Dom> for MetaTagsView {
|
||||
impl Render for MetaTagsView {
|
||||
type State = ();
|
||||
|
||||
fn build(self) -> Self::State {}
|
||||
@@ -533,21 +529,21 @@ impl Render<Dom> for MetaTagsView {
|
||||
fn rebuild(self, _state: &mut Self::State) {}
|
||||
}
|
||||
|
||||
impl AddAnyAttr<Dom> for MetaTagsView {
|
||||
type Output<SomeNewAttr: Attribute<Dom>> = MetaTagsView;
|
||||
impl AddAnyAttr for MetaTagsView {
|
||||
type Output<SomeNewAttr: Attribute> = MetaTagsView;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Dom>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Dom>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderHtml<Dom> for MetaTagsView {
|
||||
impl RenderHtml for MetaTagsView {
|
||||
type AsyncOutput = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
@@ -570,7 +566,7 @@ impl RenderHtml<Dom> for MetaTagsView {
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
_cursor: &Cursor<Dom>,
|
||||
_cursor: &Cursor,
|
||||
_position: &PositionState,
|
||||
) -> Self::State {
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use leptos::{
|
||||
tachys::{
|
||||
dom::document,
|
||||
hydration::Cursor,
|
||||
renderer::{dom::Dom, Renderer},
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
@@ -187,7 +186,7 @@ struct TitleViewState {
|
||||
effect: RenderEffect<Oco<'static, str>>,
|
||||
}
|
||||
|
||||
impl Render<Dom> for TitleView {
|
||||
impl Render for TitleView {
|
||||
type State = TitleViewState;
|
||||
|
||||
fn build(mut self) -> Self::State {
|
||||
@@ -219,21 +218,21 @@ impl Render<Dom> for TitleView {
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAnyAttr<Dom> for TitleView {
|
||||
type Output<SomeNewAttr: Attribute<Dom>> = TitleView;
|
||||
impl AddAnyAttr for TitleView {
|
||||
type Output<SomeNewAttr: Attribute> = TitleView;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Dom>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Dom>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderHtml<Dom> for TitleView {
|
||||
impl RenderHtml for TitleView {
|
||||
type AsyncOutput = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
@@ -257,7 +256,7 @@ impl RenderHtml<Dom> for TitleView {
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
mut self,
|
||||
_cursor: &Cursor<Dom>,
|
||||
_cursor: &Cursor,
|
||||
_position: &PositionState,
|
||||
) -> Self::State {
|
||||
let el = self.el();
|
||||
@@ -285,19 +284,19 @@ impl RenderHtml<Dom> for TitleView {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mountable<Dom> for TitleViewState {
|
||||
impl Mountable for TitleViewState {
|
||||
fn unmount(&mut self) {}
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
_parent: &<Dom as Renderer>::Element,
|
||||
_marker: Option<&<Dom as Renderer>::Node>,
|
||||
_parent: &leptos::tachys::renderer::types::Element,
|
||||
_marker: Option<&leptos::tachys::renderer::types::Node>,
|
||||
) {
|
||||
// <title> doesn't need to be mounted
|
||||
// TitleView::el() guarantees that there is a <title> in the <head>
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, _child: &mut dyn Mountable<Dom>) -> bool {
|
||||
fn insert_before_this(&self, _child: &mut dyn Mountable) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "next_tuple"
|
||||
version = "0.1.0-beta5"
|
||||
version = "0.1.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_graph"
|
||||
version = "0.1.0-beta5"
|
||||
version = "0.1.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
computed::{ArcMemo, Memo},
|
||||
diagnostics::is_suppressing_resource_load,
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::{ArcRwSignal, RwSignal},
|
||||
traits::{DefinedAt, Dispose, Get, GetUntracked, Update},
|
||||
unwrap_signal,
|
||||
@@ -24,7 +24,7 @@ use std::{future::Future, panic::Location, pin::Pin, sync::Arc};
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -135,7 +135,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = ArcAction::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -367,7 +367,7 @@ impl<I, O> ArcAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = ArcAction::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -395,7 +395,7 @@ impl<I, O> ArcAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = ArcAction::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -425,7 +425,7 @@ impl<I, O> ArcAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = ArcAction::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -456,7 +456,7 @@ impl<I, O> ArcAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = ArcAction::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -510,7 +510,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -561,7 +561,7 @@ where
|
||||
/// function, because it is stored in [Action::input] as well.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::actions::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = Action::new(|input: &String| {
|
||||
/// let input = input.clone();
|
||||
@@ -575,7 +575,7 @@ where
|
||||
/// let action3 = Action::new(|input: &(usize, String)| async { todo!() });
|
||||
/// ```
|
||||
pub struct Action<I, O, S = SyncStorage> {
|
||||
inner: StoredValue<ArcAction<I, O>, S>,
|
||||
inner: ArenaItem<ArcAction<I, O>, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
@@ -607,7 +607,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = Action::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -639,7 +639,7 @@ where
|
||||
Fu: Future<Output = O> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new(ArcAction::new(action_fn)),
|
||||
inner: ArenaItem::new(ArcAction::new(action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
@@ -664,9 +664,7 @@ where
|
||||
Fu: Future<Output = O> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new(ArcAction::new_with_value(
|
||||
value, action_fn,
|
||||
)),
|
||||
inner: ArenaItem::new(ArcAction::new_with_value(value, action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
@@ -688,7 +686,7 @@ where
|
||||
Fu: Future<Output = O> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new_local(ArcAction::new_unsync(action_fn)),
|
||||
inner: ArenaItem::new_local(ArcAction::new_unsync(action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
@@ -704,7 +702,7 @@ where
|
||||
Fu: Future<Output = O> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new_local(ArcAction::new_unsync_with_value(
|
||||
inner: ArenaItem::new_local(ArcAction::new_unsync_with_value(
|
||||
value, action_fn,
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -723,7 +721,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = Action::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -754,7 +752,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = Action::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -793,7 +791,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = ArcAction::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -849,7 +847,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = Action::new(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
@@ -908,7 +906,9 @@ where
|
||||
/// Calls the `async` function with a reference to the input type as its argument.
|
||||
#[track_caller]
|
||||
pub fn dispatch(&self, input: I) -> ActionAbortHandle {
|
||||
self.inner.with_value(|inner| inner.dispatch(input))
|
||||
self.inner
|
||||
.try_with_value(|inner| inner.dispatch(input))
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -921,7 +921,9 @@ where
|
||||
/// Calls the `async` function with a reference to the input type as its argument.
|
||||
#[track_caller]
|
||||
pub fn dispatch_local(&self, input: I) -> ActionAbortHandle {
|
||||
self.inner.with_value(|inner| inner.dispatch_local(input))
|
||||
self.inner
|
||||
.try_with_value(|inner| inner.dispatch_local(input))
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,7 +944,7 @@ where
|
||||
Fu: Future<Output = O> + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new_with_storage(ArcAction::new_unsync(
|
||||
inner: ArenaItem::new_with_storage(ArcAction::new_unsync(
|
||||
action_fn,
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -961,7 +963,7 @@ where
|
||||
Fu: Future<Output = O> + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new_with_storage(
|
||||
inner: ArenaItem::new_with_storage(
|
||||
ArcAction::new_unsync_with_value(value, action_fn),
|
||||
),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -1007,7 +1009,7 @@ impl<I, O, S> Copy for Action<I, O, S> {}
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let act = create_action(|n: &u8| {
|
||||
/// let n = n.to_owned();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
diagnostics::is_suppressing_resource_load,
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
|
||||
traits::{DefinedAt, Dispose, GetUntracked, Set, Update},
|
||||
unwrap_signal,
|
||||
@@ -24,7 +24,7 @@ use std::{fmt::Debug, future::Future, panic::Location, pin::Pin, sync::Arc};
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -45,7 +45,7 @@ use std::{fmt::Debug, future::Future, panic::Location, pin::Pin, sync::Arc};
|
||||
/// # });
|
||||
/// ```
|
||||
pub struct MultiAction<I, O, S = SyncStorage> {
|
||||
inner: StoredValue<ArcMultiAction<I, O>, S>,
|
||||
inner: ArenaItem<ArcMultiAction<I, O>, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
@@ -105,7 +105,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = MultiAction::new(|input: &String| {
|
||||
@@ -129,9 +129,7 @@ where
|
||||
Fut: Future<Output = O> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
inner: StoredValue::new_with_storage(ArcMultiAction::new(
|
||||
action_fn,
|
||||
)),
|
||||
inner: ArenaItem::new_with_storage(ArcMultiAction::new(action_fn)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
@@ -153,7 +151,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -189,7 +187,7 @@ where
|
||||
/// ```
|
||||
pub fn dispatch(&self, input: I) {
|
||||
if !is_suppressing_resource_load() {
|
||||
self.inner.with_value(|inner| inner.dispatch(input));
|
||||
self.inner.try_with_value(|inner| inner.dispatch(input));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +202,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -232,7 +230,8 @@ where
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn dispatch_sync(&self, value: O) {
|
||||
self.inner.with_value(|inner| inner.dispatch_sync(value));
|
||||
self.inner
|
||||
.try_with_value(|inner| inner.dispatch_sync(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +245,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -287,7 +286,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -337,7 +336,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -404,7 +403,7 @@ impl<I, O> ArcMultiAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = ArcMultiAction::new(|input: &String| {
|
||||
@@ -453,7 +452,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -530,7 +529,7 @@ where
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -580,7 +579,7 @@ impl<I, O> ArcMultiAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
@@ -610,7 +609,7 @@ impl<I, O> ArcMultiAction<I, O> {
|
||||
/// # use reactive_graph::actions::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
|
||||
@@ -34,7 +34,7 @@ pub use selector::*;
|
||||
/// In the example below, setting an auth token will only trigger
|
||||
/// the token signal, but none of the other derived signals.
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::prelude::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::signal::RwSignal;
|
||||
/// # use reactive_graph::computed::*;
|
||||
|
||||
@@ -40,7 +40,7 @@ use std::{
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::prelude::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
@@ -132,10 +132,7 @@ where
|
||||
pub fn new_with_compare(
|
||||
fun: impl Fn(Option<&T>) -> T + Send + Sync + 'static,
|
||||
changed: fn(Option<&T>, Option<&T>) -> bool,
|
||||
) -> Self
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
) -> Self {
|
||||
Self::new_owning(move |prev: Option<T>| {
|
||||
let new_value = fun(prev.as_ref());
|
||||
let changed = changed(prev.as_ref(), Some(&new_value));
|
||||
@@ -157,10 +154,7 @@ where
|
||||
)]
|
||||
pub fn new_owning(
|
||||
fun: impl Fn(Option<T>) -> (T, bool) + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
) -> Self {
|
||||
let inner = Arc::new_cyclic(|weak| {
|
||||
let subscriber = AnySubscriber(
|
||||
weak.as_ptr() as usize,
|
||||
|
||||
@@ -53,10 +53,10 @@ use std::{
|
||||
/// ## Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
///
|
||||
/// let signal1 = RwSignal::new(0);
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
AnySource, AnySubscriber, ReactiveNode, Source, Subscriber,
|
||||
ToAnySource, ToAnySubscriber,
|
||||
},
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::guards::{AsyncPlain, ReadGuard, WriteGuard},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
|
||||
@@ -29,10 +29,10 @@ use std::{future::Future, ops::DerefMut, panic::Location};
|
||||
/// ## Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
///
|
||||
/// let signal1 = RwSignal::new(0);
|
||||
@@ -85,7 +85,7 @@ use std::{future::Future, ops::DerefMut, panic::Location};
|
||||
pub struct AsyncDerived<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: StoredValue<ArcAsyncDerived<T>, S>,
|
||||
pub(crate) inner: ArenaItem<ArcAsyncDerived<T>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for AsyncDerived<T, S> {
|
||||
@@ -104,7 +104,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcAsyncDerived::new(fun)),
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new(fun)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(
|
||||
inner: ArenaItem::new_with_storage(
|
||||
ArcAsyncDerived::new_with_initial(initial_value, fun),
|
||||
),
|
||||
}
|
||||
@@ -176,9 +176,7 @@ impl<T> AsyncDerived<SendWrapper<T>> {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcAsyncDerived::new_mock(
|
||||
fun,
|
||||
)),
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new_mock(fun)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +198,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcAsyncDerived::new_unsync(
|
||||
inner: ArenaItem::new_with_storage(ArcAsyncDerived::new_unsync(
|
||||
fun,
|
||||
)),
|
||||
}
|
||||
@@ -221,7 +219,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(
|
||||
inner: ArenaItem::new_with_storage(
|
||||
ArcAsyncDerived::new_unsync_with_initial(initial_value, fun),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{inner::MemoInner, ArcMemo};
|
||||
use crate::{
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::{
|
||||
guards::{Mapped, Plain, ReadGuard},
|
||||
ArcReadSignal,
|
||||
@@ -36,7 +36,7 @@ use std::{fmt::Debug, hash::Hash, panic::Location};
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # tokio::task::LocalSet::new().run_until(async {
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
/// let (value, set_value) = signal(0);
|
||||
@@ -102,7 +102,7 @@ where
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: StoredValue<ArcMemo<T, S>, S>,
|
||||
inner: ArenaItem<ArcMemo<T, S>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for Memo<T, S>
|
||||
@@ -123,7 +123,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,7 @@ where
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
/// let (value, set_value) = signal(0);
|
||||
///
|
||||
@@ -177,7 +177,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcMemo::new(fun)),
|
||||
inner: ArenaItem::new_with_storage(ArcMemo::new(fun)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,14 +195,11 @@ where
|
||||
pub fn new_with_compare(
|
||||
fun: impl Fn(Option<&T>) -> T + Send + Sync + 'static,
|
||||
changed: fn(Option<&T>, Option<&T>) -> bool,
|
||||
) -> Self
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcMemo::new_with_compare(
|
||||
inner: ArenaItem::new_with_storage(ArcMemo::new_with_compare(
|
||||
fun, changed,
|
||||
)),
|
||||
}
|
||||
@@ -222,14 +219,11 @@ where
|
||||
)]
|
||||
pub fn new_owning(
|
||||
fun: impl Fn(Option<T>) -> (T, bool) + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcMemo::new_owning(fun)),
|
||||
inner: ArenaItem::new_with_storage(ArcMemo::new_owning(fun)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ use std::{
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let a = RwSignal::new(0);
|
||||
/// let is_selected = Selector::new(move || a.get());
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
AnySubscriber, ReactiveNode, SourceSet, Subscriber, ToAnySubscriber,
|
||||
WithObserver,
|
||||
},
|
||||
owner::{LocalStorage, Owner, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, LocalStorage, Owner, Storage, SyncStorage},
|
||||
traits::Dispose,
|
||||
};
|
||||
use any_spawner::Executor;
|
||||
@@ -37,13 +37,13 @@ use std::{
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::ArenaItem;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let a = RwSignal::new(0);
|
||||
/// let b = RwSignal::new(0);
|
||||
///
|
||||
@@ -78,7 +78,7 @@ use std::{
|
||||
/// If you need an effect to run on the server, use [`Effect::new_isomorphic`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Effect<S> {
|
||||
inner: Option<StoredValue<StoredEffect, S>>,
|
||||
inner: Option<ArenaItem<StoredEffect, S>>,
|
||||
}
|
||||
|
||||
type StoredEffect = Option<Arc<RwLock<EffectInner>>>;
|
||||
@@ -165,7 +165,7 @@ impl Effect<LocalStorage> {
|
||||
}
|
||||
});
|
||||
|
||||
StoredValue::new_with_storage(Some(inner))
|
||||
ArenaItem::new_with_storage(Some(inner))
|
||||
});
|
||||
|
||||
Self { inner }
|
||||
@@ -185,7 +185,7 @@ impl Effect<LocalStorage> {
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// #
|
||||
/// let (num, set_num) = signal(0);
|
||||
///
|
||||
@@ -217,7 +217,7 @@ impl Effect<LocalStorage> {
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// #
|
||||
/// let (num, set_num) = signal(0);
|
||||
/// let (cb_num, set_cb_num) = signal(0);
|
||||
@@ -256,7 +256,7 @@ impl Effect<LocalStorage> {
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// #
|
||||
/// let (num, set_num) = signal(0);
|
||||
///
|
||||
@@ -334,7 +334,7 @@ impl Effect<LocalStorage> {
|
||||
}
|
||||
});
|
||||
|
||||
StoredValue::new_with_storage(Some(inner))
|
||||
ArenaItem::new_with_storage(Some(inner))
|
||||
});
|
||||
|
||||
Self { inner }
|
||||
@@ -383,7 +383,7 @@ impl Effect<SyncStorage> {
|
||||
}
|
||||
});
|
||||
|
||||
StoredValue::new_with_storage(Some(inner))
|
||||
ArenaItem::new_with_storage(Some(inner))
|
||||
});
|
||||
|
||||
Self { inner }
|
||||
@@ -430,7 +430,7 @@ impl Effect<SyncStorage> {
|
||||
crate::spawn(task);
|
||||
|
||||
Self {
|
||||
inner: Some(StoredValue::new_with_storage(Some(inner))),
|
||||
inner: Some(ArenaItem::new_with_storage(Some(inner))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ impl Effect<SyncStorage> {
|
||||
}
|
||||
});
|
||||
|
||||
StoredValue::new_with_storage(Some(inner))
|
||||
ArenaItem::new_with_storage(Some(inner))
|
||||
});
|
||||
|
||||
Self { inner }
|
||||
|
||||
@@ -104,11 +104,11 @@ impl Observer {
|
||||
///
|
||||
/// ```rust
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::untrack;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let (a, set_a) = signal(0);
|
||||
/// let (b, set_b) = signal(0);
|
||||
/// let c = Memo::new(move |_| {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
//!
|
||||
//! ```rust
|
||||
//! # any_spawner::Executor::init_futures_executor();
|
||||
//! # let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
//! use reactive_graph::{
|
||||
//! computed::ArcMemo,
|
||||
//! effect::Effect,
|
||||
@@ -87,6 +88,7 @@ pub mod traits;
|
||||
pub mod transition;
|
||||
pub mod wrappers;
|
||||
|
||||
use computed::ScopedFuture;
|
||||
pub use graph::untrack;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
@@ -130,3 +132,36 @@ pub(crate) fn spawn(task: impl Future<Output = ()> + Send + 'static) {
|
||||
|
||||
any_spawner::Executor::spawn(task);
|
||||
}
|
||||
|
||||
/// Calls [`Executor::spawn_local`], but ensures that the task runs under the current reactive [`Owner`]
|
||||
/// and [`Observed`]. Does not cancel the task if the owner is cleaned up.
|
||||
pub fn spawn_local_scoped(task: impl Future<Output = ()> + 'static) {
|
||||
let task = ScopedFuture::new(task);
|
||||
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
let task = owner::Sandboxed::new(task);
|
||||
|
||||
any_spawner::Executor::spawn_local(task);
|
||||
}
|
||||
|
||||
/// Calls [`Executor::spawn_local`], but ensures that the task runs under the current reactive [`Owner`]
|
||||
/// and [`Observed`]. Cancels the task if the owner is cleaned up.
|
||||
pub fn spawn_local_scoped_with_cancellation(
|
||||
task: impl Future<Output = ()> + 'static,
|
||||
) {
|
||||
use crate::owner::on_cleanup;
|
||||
use futures::future::{AbortHandle, Abortable};
|
||||
|
||||
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||
on_cleanup(move || abort_handle.abort());
|
||||
|
||||
let task = Abortable::new(task, abort_registration);
|
||||
let task = ScopedFuture::new(task);
|
||||
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
let task = owner::Sandboxed::new(task);
|
||||
|
||||
any_spawner::Executor::spawn_local(async move {
|
||||
_ = task.await;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,24 +13,27 @@ use std::{
|
||||
};
|
||||
|
||||
mod arena;
|
||||
mod arena_item;
|
||||
mod context;
|
||||
mod storage;
|
||||
mod stored_value;
|
||||
use self::arena::Arena;
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
pub use arena::sandboxed::Sandboxed;
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
use arena::ArenaMap;
|
||||
use arena::NodeId;
|
||||
pub use arena_item::*;
|
||||
pub use context::*;
|
||||
pub use storage::*;
|
||||
#[allow(deprecated)] // allow exporting deprecated fn
|
||||
pub use stored_value::{
|
||||
store_value, FromLocal, LocalStorage, Storage, StorageAccess, StoredValue,
|
||||
SyncStorage,
|
||||
};
|
||||
pub use stored_value::{store_value, FromLocal, StoredValue};
|
||||
|
||||
/// A reactive owner, which manages
|
||||
/// 1) the cancelation of [`Effect`](crate::effect::Effect)s,
|
||||
/// 2) providing and accessing environment data via [`provide_context`] and [`use_context`],
|
||||
/// 3) running cleanup functions defined via [`Owner::on_cleanup`], and
|
||||
/// 4) an arena storage system to provide `Copy` handles via [`StoredValue`], which is what allows
|
||||
/// 4) an arena storage system to provide `Copy` handles via [`ArenaItem`], which is what allows
|
||||
/// types like [`RwSignal`](crate::signal::RwSignal), [`Memo`](crate::computed::Memo), and so on to be `Copy`.
|
||||
///
|
||||
/// Every effect and computed reactive value has an associated `Owner`. While it is running, this
|
||||
@@ -119,6 +122,12 @@ impl Owner {
|
||||
contexts: Default::default(),
|
||||
cleanups: Default::default(),
|
||||
children: Default::default(),
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
arena: parent
|
||||
.as_ref()
|
||||
.and_then(|parent| parent.upgrade())
|
||||
.map(|parent| parent.read().or_poisoned().arena.clone())
|
||||
.unwrap_or_default(),
|
||||
})),
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context,
|
||||
@@ -139,11 +148,10 @@ impl Owner {
|
||||
/// Only one `SharedContext` needs to be created per request, and will be automatically shared
|
||||
/// by any other `Owner`s created under this one.
|
||||
#[cfg(feature = "hydration")]
|
||||
#[track_caller]
|
||||
pub fn new_root(
|
||||
shared_context: Option<Arc<dyn SharedContext + Send + Sync>>,
|
||||
) -> Self {
|
||||
Arena::enter_new();
|
||||
|
||||
let this = Self {
|
||||
inner: Arc::new(RwLock::new(OwnerInner {
|
||||
parent: None,
|
||||
@@ -151,17 +159,21 @@ impl Owner {
|
||||
contexts: Default::default(),
|
||||
cleanups: Default::default(),
|
||||
children: Default::default(),
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
arena: Default::default(),
|
||||
})),
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context,
|
||||
};
|
||||
OWNER.with_borrow_mut(|owner| *owner = Some(this.clone()));
|
||||
this.set();
|
||||
this
|
||||
}
|
||||
|
||||
/// Creates a new `Owner` that is the child of the current `Owner`, if any.
|
||||
pub fn child(&self) -> Self {
|
||||
let parent = Some(Arc::downgrade(&self.inner));
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
let arena = self.inner.read().or_poisoned().arena.clone();
|
||||
let child = Self {
|
||||
inner: Arc::new(RwLock::new(OwnerInner {
|
||||
parent,
|
||||
@@ -169,6 +181,8 @@ impl Owner {
|
||||
contexts: Default::default(),
|
||||
cleanups: Default::default(),
|
||||
children: Default::default(),
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
arena,
|
||||
})),
|
||||
#[cfg(feature = "hydration")]
|
||||
shared_context: self.shared_context.clone(),
|
||||
@@ -184,6 +198,8 @@ impl Owner {
|
||||
/// Sets this as the current `Owner`.
|
||||
pub fn set(&self) {
|
||||
OWNER.with_borrow_mut(|owner| *owner = Some(self.clone()));
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
Arena::set(&self.inner.read().or_poisoned().arena);
|
||||
}
|
||||
|
||||
/// Runs the given function with this as the current `Owner`.
|
||||
@@ -193,6 +209,8 @@ impl Owner {
|
||||
mem::replace(&mut *o.borrow_mut(), Some(self.clone()))
|
||||
})
|
||||
};
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
Arena::set(&self.inner.read().or_poisoned().arena);
|
||||
let val = fun();
|
||||
OWNER.with(|o| {
|
||||
*o.borrow_mut() = prev;
|
||||
@@ -209,7 +227,7 @@ impl Owner {
|
||||
/// Cleans up this owner in the following order:
|
||||
/// 1) Runs `cleanup` on all children,
|
||||
/// 2) Runs all cleanup functions registered with [`Owner::on_cleanup`],
|
||||
/// 3) Drops the values of any arena-allocated [`StoredValue`]s.
|
||||
/// 3) Drops the values of any arena-allocated [`ArenaItem`]s.
|
||||
pub fn cleanup(&self) {
|
||||
self.inner.cleanup();
|
||||
}
|
||||
@@ -333,6 +351,8 @@ pub(crate) struct OwnerInner {
|
||||
pub contexts: FxHashMap<TypeId, Box<dyn Any + Send + Sync>>,
|
||||
pub cleanups: Vec<Box<dyn FnOnce() + Send + Sync>>,
|
||||
pub children: Vec<Weak<RwLock<OwnerInner>>>,
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
arena: Arc<RwLock<ArenaMap>>,
|
||||
}
|
||||
|
||||
impl Debug for OwnerInner {
|
||||
@@ -360,11 +380,19 @@ impl Drop for OwnerInner {
|
||||
|
||||
let nodes = mem::take(&mut self.nodes);
|
||||
if !nodes.is_empty() {
|
||||
#[cfg(not(feature = "sandboxed-arenas"))]
|
||||
Arena::with_mut(|arena| {
|
||||
for node in nodes {
|
||||
_ = arena.remove(node);
|
||||
}
|
||||
});
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
let mut arena = self.arena.write().or_poisoned();
|
||||
for node in nodes {
|
||||
_ = arena.remove(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,11 +421,20 @@ impl Cleanup for RwLock<OwnerInner> {
|
||||
}
|
||||
|
||||
if !nodes.is_empty() {
|
||||
#[cfg(not(feature = "sandboxed-arenas"))]
|
||||
Arena::with_mut(|arena| {
|
||||
for node in nodes {
|
||||
_ = arena.remove(node);
|
||||
}
|
||||
});
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
let arena = self.read().or_poisoned().arena.clone();
|
||||
let mut arena = arena.write().or_poisoned();
|
||||
for node in nodes {
|
||||
_ = arena.remove(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
use or_poisoned::OrPoisoned;
|
||||
use slotmap::{new_key_type, SlotMap};
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
use std::cell::RefCell;
|
||||
#[cfg(not(feature = "sandboxed-arenas"))]
|
||||
use std::sync::OnceLock;
|
||||
use std::{any::Any, hash::Hash, sync::RwLock};
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
use std::{cell::RefCell, sync::Arc};
|
||||
use std::sync::Weak;
|
||||
use std::{
|
||||
any::Any,
|
||||
hash::Hash,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
new_key_type! {
|
||||
/// Unique identifier for an item stored in the arena.
|
||||
pub struct NodeId;
|
||||
}
|
||||
|
||||
pub(crate) struct Arena;
|
||||
pub struct Arena;
|
||||
|
||||
type ArenaMap = SlotMap<NodeId, Box<dyn Any + Send + Sync>>;
|
||||
pub type ArenaMap = SlotMap<NodeId, Box<dyn Any + Send + Sync>>;
|
||||
|
||||
#[cfg(not(feature = "sandboxed-arenas"))]
|
||||
static MAP: OnceLock<RwLock<ArenaMap>> = OnceLock::new();
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
thread_local! {
|
||||
pub(crate) static MAP: RefCell<Option<Arc<RwLock<ArenaMap>>>> = RefCell::new(Some(Default::default()));
|
||||
pub(crate) static MAP: RefCell<Option<Weak<RwLock<ArenaMap>>>> = RefCell::new(Some(Default::default()));
|
||||
}
|
||||
|
||||
impl Arena {
|
||||
#[cfg(feature = "hydration")]
|
||||
#[inline(always)]
|
||||
pub fn enter_new() {
|
||||
#[allow(unused)]
|
||||
pub fn set(arena: &Arc<RwLock<ArenaMap>>) {
|
||||
#[cfg(feature = "sandboxed-arenas")]
|
||||
{
|
||||
use std::sync::Arc;
|
||||
let new_arena = Arc::downgrade(arena);
|
||||
MAP.with_borrow_mut(|arena| {
|
||||
*arena = Some(Arc::new(RwLock::new(
|
||||
SlotMap::with_capacity_and_key(32),
|
||||
)))
|
||||
*arena = Some(new_arena);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -48,6 +52,7 @@ impl Arena {
|
||||
MAP.with_borrow(|arena| {
|
||||
fun(&arena
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"at {}, the `sandboxed-arenas` feature is active, \
|
||||
@@ -73,6 +78,7 @@ impl Arena {
|
||||
MAP.with_borrow(|arena| {
|
||||
fun(&mut arena
|
||||
.as_ref()
|
||||
.and_then(Weak::upgrade)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"at {}, the `sandboxed-arenas` feature is active, \
|
||||
@@ -94,20 +100,11 @@ pub mod sandboxed {
|
||||
use pin_project_lite::pin_project;
|
||||
use std::{
|
||||
future::Future,
|
||||
mem,
|
||||
pin::Pin,
|
||||
sync::{Arc, RwLock},
|
||||
sync::{Arc, RwLock, Weak},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
impl Arena {
|
||||
fn set(new_arena: Arc<RwLock<ArenaMap>>) -> UnsetArenaOnDrop {
|
||||
MAP.with_borrow_mut(|arena| {
|
||||
UnsetArenaOnDrop(mem::replace(arena, Some(new_arena)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// A [`Future`] that restores its associated arena as the current arena whenever it is
|
||||
/// polled.
|
||||
@@ -117,22 +114,17 @@ pub mod sandboxed {
|
||||
/// stored values. Wrapping a `Future` in `Sandboxed` ensures that it will always use the
|
||||
/// same arena that it was created under.
|
||||
pub struct Sandboxed<T> {
|
||||
arena: Arc<RwLock<ArenaMap>>,
|
||||
arena: Option<Arc<RwLock<ArenaMap>>>,
|
||||
#[pin]
|
||||
inner: T,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Sandboxed<T> {
|
||||
/// Wraps the given [`Future`], ensuring that any [`StoredValue`] created while it is being
|
||||
/// Wraps the given [`Future`], ensuring that any [`ArenaItem`] created while it is being
|
||||
/// polled will be associated with the same arena that was active when this was called.
|
||||
pub fn new(inner: T) -> Self {
|
||||
let arena = MAP.with_borrow(|current| {
|
||||
Arc::clone(current.as_ref().expect(
|
||||
"the `sandboxed-arenas` feature is active, but no Arena \
|
||||
is active",
|
||||
))
|
||||
});
|
||||
let arena = MAP.with_borrow(|n| n.as_ref().and_then(Weak::upgrade));
|
||||
Self { arena, inner }
|
||||
}
|
||||
}
|
||||
@@ -147,11 +139,11 @@ pub mod sandboxed {
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Self::Output> {
|
||||
let unset = Arena::set(Arc::clone(&self.arena));
|
||||
if let Some(arena) = self.arena.as_ref() {
|
||||
Arena::set(arena);
|
||||
}
|
||||
let this = self.project();
|
||||
let res = this.inner.poll(cx);
|
||||
drop(unset);
|
||||
res
|
||||
this.inner.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,22 +157,11 @@ pub mod sandboxed {
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
let unset = Arena::set(Arc::clone(&self.arena));
|
||||
let this = self.project();
|
||||
let res = this.inner.poll_next(cx);
|
||||
drop(unset);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UnsetArenaOnDrop(Option<Arc<RwLock<ArenaMap>>>);
|
||||
|
||||
impl Drop for UnsetArenaOnDrop {
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = self.0.take() {
|
||||
MAP.with_borrow_mut(|current_map| *current_map = Some(inner));
|
||||
if let Some(arena) = self.arena.as_ref() {
|
||||
Arena::set(arena);
|
||||
}
|
||||
let this = self.project();
|
||||
this.inner.poll_next(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
136
reactive_graph/src/owner/arena_item.rs
Normal file
136
reactive_graph/src/owner/arena_item.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use super::{
|
||||
arena::{Arena, NodeId},
|
||||
LocalStorage, Storage, SyncStorage, OWNER,
|
||||
};
|
||||
use crate::traits::{Dispose, IsDisposed};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{any::Any, hash::Hash, marker::PhantomData};
|
||||
|
||||
/// A copyable, stable reference for any value, stored on the arena whose ownership is managed by the
|
||||
/// reactive ownership tree.
|
||||
#[derive(Debug)]
|
||||
pub struct ArenaItem<T, S = SyncStorage> {
|
||||
node: NodeId,
|
||||
#[allow(clippy::type_complexity)]
|
||||
ty: PhantomData<fn() -> (SendWrapper<T>, S)>,
|
||||
}
|
||||
|
||||
impl<T, S> Copy for ArenaItem<T, S> {}
|
||||
|
||||
impl<T, S> Clone for ArenaItem<T, S> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> PartialEq for ArenaItem<T, S> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.node == other.node
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Eq for ArenaItem<T, S> {}
|
||||
|
||||
impl<T, S> Hash for ArenaItem<T, S> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.node.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> ArenaItem<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<T>,
|
||||
{
|
||||
/// Stores the given value in the arena allocator.
|
||||
#[track_caller]
|
||||
pub fn new_with_storage(value: T) -> Self {
|
||||
let node = {
|
||||
Arena::with_mut(|arena| {
|
||||
arena.insert(
|
||||
Box::new(S::wrap(value)) as Box<dyn Any + Send + Sync>
|
||||
)
|
||||
})
|
||||
};
|
||||
OWNER.with(|o| {
|
||||
if let Some(owner) = &*o.borrow() {
|
||||
owner.register(node);
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
node,
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Default for ArenaItem<T, S>
|
||||
where
|
||||
T: Default + 'static,
|
||||
S: Storage<T>,
|
||||
{
|
||||
#[track_caller] // Default trait is not annotated with #[track_caller]
|
||||
fn default() -> Self {
|
||||
Self::new_with_storage(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ArenaItem<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
/// Stores the given value in the arena allocator.
|
||||
#[track_caller]
|
||||
pub fn new(value: T) -> Self {
|
||||
ArenaItem::new_with_storage(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ArenaItem<T, LocalStorage>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
/// Stores the given value in the arena allocator.
|
||||
#[track_caller]
|
||||
pub fn new_local(value: T) -> Self {
|
||||
ArenaItem::new_with_storage(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Storage<T>> ArenaItem<T, S> {
|
||||
/// Applies a function to a reference to the stored value and returns the result, or `None` if it has already been disposed.
|
||||
#[track_caller]
|
||||
pub fn try_with_value<U>(&self, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
S::try_with(self.node, fun)
|
||||
}
|
||||
|
||||
/// Applies a function to a mutable reference to the stored value and returns the result, or `None` if it has already been disposed.
|
||||
#[track_caller]
|
||||
pub fn try_update_value<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
S::try_with_mut(self.node, fun)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone, S: Storage<T>> ArenaItem<T, S> {
|
||||
/// Returns a clone of the stored value, or `None` if it has already been disposed.
|
||||
#[track_caller]
|
||||
pub fn try_get_value(&self) -> Option<T> {
|
||||
S::try_with(self.node, Clone::clone)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IsDisposed for ArenaItem<T, S> {
|
||||
fn is_disposed(&self) -> bool {
|
||||
Arena::with(|arena| !arena.contains_key(self.node))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for ArenaItem<T, S> {
|
||||
fn dispose(self) {
|
||||
Arena::with_mut(|arena| arena.remove(self.node));
|
||||
}
|
||||
}
|
||||
@@ -108,6 +108,7 @@ impl Owner {
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::owner::*;
|
||||
/// # let owner = Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # futures::executor::block_on(async move {
|
||||
/// # any_spawner::Executor::init_futures_executor();
|
||||
@@ -140,6 +141,7 @@ impl Owner {
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::owner::*;
|
||||
/// # let owner = Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # futures::executor::block_on(async move {
|
||||
/// # any_spawner::Executor::init_futures_executor();
|
||||
@@ -187,6 +189,7 @@ pub fn provide_context<T: Send + Sync + 'static>(value: T) {
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::owner::*;
|
||||
/// # let owner = Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # futures::executor::block_on(async move {
|
||||
/// # any_spawner::Executor::init_futures_executor();
|
||||
@@ -232,6 +235,7 @@ pub fn use_context<T: Clone + 'static>() -> Option<T> {
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::owner::*;
|
||||
/// # let owner = Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # futures::executor::block_on(async move {
|
||||
/// # any_spawner::Executor::init_futures_executor();
|
||||
@@ -286,6 +290,7 @@ pub fn expect_context<T: Clone + 'static>() -> T {
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::owner::*;
|
||||
/// # let owner = Owner::new(); owner.set();
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # futures::executor::block_on(async move {
|
||||
/// # any_spawner::Executor::init_futures_executor();
|
||||
|
||||
151
reactive_graph/src/owner/storage.rs
Normal file
151
reactive_graph/src/owner/storage.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
use super::arena::{Arena, NodeId};
|
||||
use send_wrapper::SendWrapper;
|
||||
|
||||
/// A trait for borrowing and taking data.
|
||||
pub trait StorageAccess<T> {
|
||||
/// Borrows the value.
|
||||
fn as_borrowed(&self) -> &T;
|
||||
|
||||
/// Takes the value.
|
||||
fn into_taken(self) -> T;
|
||||
}
|
||||
|
||||
impl<T> StorageAccess<T> for T {
|
||||
fn as_borrowed(&self) -> &T {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_taken(self) -> T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StorageAccess<T> for SendWrapper<T> {
|
||||
fn as_borrowed(&self) -> &T {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_taken(self) -> T {
|
||||
self.take()
|
||||
}
|
||||
}
|
||||
|
||||
/// A way of storing a [`ArenaItem`], either as itself or with a wrapper to make it threadsafe.
|
||||
///
|
||||
/// This exists because all items stored in the arena must be `Send + Sync`, but in single-threaded
|
||||
/// environments you might want or need to use thread-unsafe types.
|
||||
pub trait Storage<T>: Send + Sync + 'static {
|
||||
/// The type being stored, once it has been wrapped.
|
||||
type Wrapped: StorageAccess<T> + Send + Sync + 'static;
|
||||
|
||||
/// Adds any needed wrapper to the type.
|
||||
fn wrap(value: T) -> Self::Wrapped;
|
||||
|
||||
/// Applies the given function to the stored value, if it exists and can be accessed from this
|
||||
/// thread.
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U>;
|
||||
|
||||
/// Applies the given function to a mutable reference to the stored value, if it exists and can be accessed from this
|
||||
/// thread.
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U>;
|
||||
|
||||
/// Sets a new value for the stored value. If it has been disposed, returns `Some(T)`.
|
||||
fn try_set(node: NodeId, value: T) -> Option<T>;
|
||||
}
|
||||
|
||||
/// A form of [`Storage`] that stores the type as itself, with no wrapper.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SyncStorage;
|
||||
|
||||
impl<T> Storage<T> for SyncStorage
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
type Wrapped = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn wrap(value: T) -> Self::Wrapped {
|
||||
value
|
||||
}
|
||||
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
Arena::with(|arena| {
|
||||
let m = arena.get(node);
|
||||
m.and_then(|n| n.downcast_ref::<T>()).map(fun)
|
||||
})
|
||||
}
|
||||
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
m.and_then(|n| n.downcast_mut::<T>()).map(fun)
|
||||
})
|
||||
}
|
||||
|
||||
fn try_set(node: NodeId, value: T) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
match m.and_then(|n| n.downcast_mut::<T>()) {
|
||||
Some(inner) => {
|
||||
*inner = value;
|
||||
None
|
||||
}
|
||||
None => Some(value),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A form of [`Storage`] that stores the type with a wrapper that makes it `Send + Sync`, but only
|
||||
/// allows it to be accessed from the thread on which it was created.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct LocalStorage;
|
||||
|
||||
impl<T> Storage<T> for LocalStorage
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Wrapped = SendWrapper<T>;
|
||||
|
||||
fn wrap(value: T) -> Self::Wrapped {
|
||||
SendWrapper::new(value)
|
||||
}
|
||||
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
Arena::with(|arena| {
|
||||
let m = arena.get(node);
|
||||
m.and_then(|n| n.downcast_ref::<SendWrapper<T>>())
|
||||
.map(|inner| fun(inner))
|
||||
})
|
||||
}
|
||||
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
m.and_then(|n| n.downcast_mut::<SendWrapper<T>>())
|
||||
.map(|inner| fun(&mut *inner))
|
||||
})
|
||||
}
|
||||
|
||||
fn try_set(node: NodeId, value: T) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
match m.and_then(|n| n.downcast_mut::<SendWrapper<T>>()) {
|
||||
Some(inner) => {
|
||||
*inner = SendWrapper::new(value);
|
||||
None
|
||||
}
|
||||
None => Some(value),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,162 +1,15 @@
|
||||
use super::{
|
||||
arena::{Arena, NodeId},
|
||||
OWNER,
|
||||
};
|
||||
use super::{ArenaItem, LocalStorage, Storage, SyncStorage};
|
||||
use crate::{
|
||||
signal::guards::{Plain, ReadGuard, UntrackedWriteGuard},
|
||||
traits::{DefinedAt, Dispose, IsDisposed},
|
||||
unwrap_signal,
|
||||
};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{any::Any, hash::Hash, marker::PhantomData, panic::Location};
|
||||
|
||||
/// A trait for borrowing and taking data.
|
||||
pub trait StorageAccess<T> {
|
||||
/// Borrows the value.
|
||||
fn as_borrowed(&self) -> &T;
|
||||
|
||||
/// Takes the value.
|
||||
fn into_taken(self) -> T;
|
||||
}
|
||||
|
||||
impl<T> StorageAccess<T> for T {
|
||||
fn as_borrowed(&self) -> &T {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_taken(self) -> T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StorageAccess<T> for SendWrapper<T> {
|
||||
fn as_borrowed(&self) -> &T {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_taken(self) -> T {
|
||||
self.take()
|
||||
}
|
||||
}
|
||||
|
||||
/// A way of storing a [`StoredValue`], either as itself or with a wrapper to make it threadsafe.
|
||||
///
|
||||
/// This exists because all items stored in the arena must be `Send + Sync`, but in single-threaded
|
||||
/// environments you might want or need to use thread-unsafe types.
|
||||
pub trait Storage<T>: Send + Sync + 'static {
|
||||
/// The type being stored, once it has been wrapped.
|
||||
type Wrapped: StorageAccess<T> + Send + Sync + 'static;
|
||||
|
||||
/// Adds any needed wrapper to the type.
|
||||
fn wrap(value: T) -> Self::Wrapped;
|
||||
|
||||
/// Applies the given function to the stored value, if it exists and can be accessed from this
|
||||
/// thread.
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U>;
|
||||
|
||||
/// Applies the given function to a mutable reference to the stored value, if it exists and can be accessed from this
|
||||
/// thread.
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U>;
|
||||
|
||||
/// Sets a new value for the stored value. If it has been disposed, returns `Some(T)`.
|
||||
fn try_set(node: NodeId, value: T) -> Option<T>;
|
||||
}
|
||||
|
||||
/// A form of [`Storage`] that stores the type as itself, with no wrapper.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SyncStorage;
|
||||
|
||||
impl<T> Storage<T> for SyncStorage
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
type Wrapped = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn wrap(value: T) -> Self::Wrapped {
|
||||
value
|
||||
}
|
||||
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
Arena::with(|arena| {
|
||||
let m = arena.get(node);
|
||||
m.and_then(|n| n.downcast_ref::<T>()).map(fun)
|
||||
})
|
||||
}
|
||||
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
m.and_then(|n| n.downcast_mut::<T>()).map(fun)
|
||||
})
|
||||
}
|
||||
|
||||
fn try_set(node: NodeId, value: T) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
match m.and_then(|n| n.downcast_mut::<T>()) {
|
||||
Some(inner) => {
|
||||
*inner = value;
|
||||
None
|
||||
}
|
||||
None => Some(value),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A form of [`Storage`] that stores the type with a wrapper that makes it `Send + Sync`, but only
|
||||
/// allows it to be accessed from the thread on which it was created.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct LocalStorage;
|
||||
|
||||
impl<T> Storage<T> for LocalStorage
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Wrapped = SendWrapper<T>;
|
||||
|
||||
fn wrap(value: T) -> Self::Wrapped {
|
||||
SendWrapper::new(value)
|
||||
}
|
||||
|
||||
fn try_with<U>(node: NodeId, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
Arena::with(|arena| {
|
||||
let m = arena.get(node);
|
||||
m.and_then(|n| n.downcast_ref::<SendWrapper<T>>())
|
||||
.map(|inner| fun(inner))
|
||||
})
|
||||
}
|
||||
|
||||
fn try_with_mut<U>(
|
||||
node: NodeId,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
m.and_then(|n| n.downcast_mut::<SendWrapper<T>>())
|
||||
.map(|inner| fun(&mut *inner))
|
||||
})
|
||||
}
|
||||
|
||||
fn try_set(node: NodeId, value: T) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(node);
|
||||
match m.and_then(|n| n.downcast_mut::<SendWrapper<T>>()) {
|
||||
Some(inner) => {
|
||||
*inner = SendWrapper::new(value);
|
||||
None
|
||||
}
|
||||
None => Some(value),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
use or_poisoned::OrPoisoned;
|
||||
use std::{
|
||||
hash::Hash,
|
||||
panic::Location,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// A **non-reactive**, `Copy` handle for any value.
|
||||
///
|
||||
@@ -167,8 +20,7 @@ where
|
||||
/// updating it does not notify anything else.
|
||||
#[derive(Debug)]
|
||||
pub struct StoredValue<T, S = SyncStorage> {
|
||||
node: NodeId,
|
||||
ty: PhantomData<(SendWrapper<T>, S)>,
|
||||
value: ArenaItem<Arc<RwLock<T>>, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
}
|
||||
@@ -183,7 +35,7 @@ impl<T, S> Clone for StoredValue<T, S> {
|
||||
|
||||
impl<T, S> PartialEq for StoredValue<T, S> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.node == other.node
|
||||
self.value == other.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +43,7 @@ impl<T, S> Eq for StoredValue<T, S> {}
|
||||
|
||||
impl<T, S> Hash for StoredValue<T, S> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.node.hash(state);
|
||||
self.value.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,27 +63,13 @@ impl<T, S> DefinedAt for StoredValue<T, S> {
|
||||
impl<T, S> StoredValue<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<T>,
|
||||
S: Storage<Arc<RwLock<T>>>,
|
||||
{
|
||||
/// Stores the given value in the arena allocator.
|
||||
#[track_caller]
|
||||
pub fn new_with_storage(value: T) -> Self {
|
||||
let node = {
|
||||
Arena::with_mut(|arena| {
|
||||
arena.insert(
|
||||
Box::new(S::wrap(value)) as Box<dyn Any + Send + Sync>
|
||||
)
|
||||
})
|
||||
};
|
||||
OWNER.with(|o| {
|
||||
if let Some(owner) = &*o.borrow() {
|
||||
owner.register(node);
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
node,
|
||||
ty: PhantomData,
|
||||
value: ArenaItem::new_with_storage(Arc::new(RwLock::new(value))),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
}
|
||||
@@ -241,7 +79,7 @@ where
|
||||
impl<T, S> Default for StoredValue<T, S>
|
||||
where
|
||||
T: Default + 'static,
|
||||
S: Storage<T>,
|
||||
S: Storage<Arc<RwLock<T>>>,
|
||||
{
|
||||
#[track_caller] // Default trait is not annotated with #[track_caller]
|
||||
fn default() -> Self {
|
||||
@@ -271,7 +109,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
|
||||
/// Returns an [`Option`] of applying a function to the value within the [`StoredValue`].
|
||||
///
|
||||
/// If the owner of the reactive node has not been disposed [`Some`] is returned. Calling this
|
||||
@@ -282,7 +120,7 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::traits::Dispose;
|
||||
///
|
||||
/// // Does not implement Clone
|
||||
@@ -315,7 +153,9 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn try_with_value<U>(&self, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
S::try_with(self.node, fun)
|
||||
self.value
|
||||
.try_get_value()
|
||||
.map(|inner| fun(&*inner.read().or_poisoned()))
|
||||
}
|
||||
|
||||
/// Returns the output of applying a function to the value within the [`StoredValue`].
|
||||
@@ -327,7 +167,7 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
///
|
||||
/// // Does not implement Clone
|
||||
/// struct Data {
|
||||
@@ -354,13 +194,53 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
/// Returns a read guard to the stored data, or `None` if the owner of the reactive node has been disposed.
|
||||
#[track_caller]
|
||||
pub fn try_read_value(&self) -> Option<ReadGuard<T, Plain<T>>> {
|
||||
self.value
|
||||
.try_get_value()
|
||||
.and_then(|inner| Plain::try_new(inner).map(ReadGuard::new))
|
||||
}
|
||||
|
||||
/// Returns a read guard to the stored data.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics when called after the owner of the reactive node has been disposed.
|
||||
/// See [`StoredValue::try_read_value`] for a version without panic.
|
||||
#[track_caller]
|
||||
pub fn read_value(&self) -> ReadGuard<T, Plain<T>> {
|
||||
self.try_read_value().unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
/// Returns a write guard to the stored data, or `None` if the owner of the reactive node has been disposed.
|
||||
#[track_caller]
|
||||
pub fn try_write_value(&self) -> Option<UntrackedWriteGuard<T>> {
|
||||
self.value
|
||||
.try_get_value()
|
||||
.and_then(|inner| UntrackedWriteGuard::try_new(inner))
|
||||
}
|
||||
|
||||
/// Returns a write guard to the stored data.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics when called after the owner of the reactive node has been disposed.
|
||||
/// See [`StoredValue::try_write_value`] for a version without panic.
|
||||
#[track_caller]
|
||||
pub fn write_value(&self) -> UntrackedWriteGuard<T> {
|
||||
self.try_write_value().unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
/// Updates the current value by applying the given closure, returning the return value of the
|
||||
/// closure, or `None` if the value has already been disposed.
|
||||
pub fn try_update_value<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
S::try_with_mut(self.node, fun)
|
||||
self.value
|
||||
.try_get_value()
|
||||
.map(|inner| fun(&mut *inner.write().or_poisoned()))
|
||||
}
|
||||
|
||||
/// Updates the value within [`StoredValue`] by applying a function to it.
|
||||
@@ -371,7 +251,7 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
///
|
||||
/// #[derive(Default)] // Does not implement Clone
|
||||
/// struct Data {
|
||||
@@ -415,7 +295,7 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::traits::Dispose;
|
||||
///
|
||||
/// let data = StoredValue::new(String::default());
|
||||
@@ -453,7 +333,13 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
/// assert_eq!(reset().as_deref(), Some(""));
|
||||
/// ```
|
||||
pub fn try_set_value(&self, value: T) -> Option<T> {
|
||||
S::try_set(self.node, value)
|
||||
match self.value.try_get_value() {
|
||||
Some(inner) => {
|
||||
*inner.write().or_poisoned() = value;
|
||||
None
|
||||
}
|
||||
None => Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the value within [`StoredValue`].
|
||||
@@ -467,7 +353,7 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
///
|
||||
/// let data = StoredValue::new(10);
|
||||
///
|
||||
@@ -490,11 +376,11 @@ impl<T, S: Storage<T>> StoredValue<T, S> {
|
||||
|
||||
impl<T, S> IsDisposed for StoredValue<T, S> {
|
||||
fn is_disposed(&self) -> bool {
|
||||
Arena::with(|arena| !arena.contains_key(self.node))
|
||||
self.value.is_disposed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Storage<T>> StoredValue<T, S>
|
||||
impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
@@ -508,7 +394,7 @@ where
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::traits::Dispose;
|
||||
///
|
||||
/// // u8 is practically free to clone.
|
||||
@@ -548,7 +434,7 @@ where
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
///
|
||||
/// // u8 is practically free to clone.
|
||||
/// let data: StoredValue<u8> = StoredValue::new(10);
|
||||
@@ -574,7 +460,7 @@ where
|
||||
|
||||
impl<T, S> Dispose for StoredValue<T, S> {
|
||||
fn dispose(self) {
|
||||
Arena::with_mut(|arena| arena.remove(self.node));
|
||||
self.value.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ pub use write::*;
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let (count, set_count) = arc_signal(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
@@ -82,7 +82,7 @@ pub fn arc_signal<T>(value: T) -> (ArcReadSignal<T>, ArcWriteSignal<T>) {
|
||||
/// as long as a reference to it is alive, see [`arc_signal`].
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let (count, set_count) = signal(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
@@ -142,7 +142,7 @@ pub fn signal_local<T: 'static>(
|
||||
/// as long as a reference to it is alive, see [`arc_signal`].
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let (count, set_count) = create_signal(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
|
||||
@@ -56,14 +56,14 @@ use std::{
|
||||
/// > Each of these has a related `_untracked()` method, which updates the signal
|
||||
/// > without notifying subscribers. Untracked updates are not desirable in most
|
||||
/// > cases, as they cause “tearing” between the signal’s value and its observed
|
||||
/// > value. If you want a non-reactive container, used [`StoredValue`](crate::owner::StoredValue)
|
||||
/// > value. If you want a non-reactive container, used [`ArenaItem`](crate::owner::ArenaItem)
|
||||
/// > instead.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let count = ArcRwSignal::new(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
|
||||
@@ -29,7 +29,7 @@ use std::{
|
||||
/// > Each of these has a related `_untracked()` method, which updates the signal
|
||||
/// > without notifying subscribers. Untracked updates are not desirable in most
|
||||
/// > cases, as they cause “tearing” between the signal’s value and its observed
|
||||
/// > value. If you want a non-reactive container, used [`StoredValue`](crate::owner::StoredValue)
|
||||
/// > value. If you want a non-reactive container, used [`ArenaItem`](crate::owner::ArenaItem)
|
||||
/// > instead.
|
||||
///
|
||||
/// ## Examples
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
graph::SubscriberSet,
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
traits::{DefinedAt, Dispose, IsDisposed, ReadUntracked},
|
||||
unwrap_signal,
|
||||
};
|
||||
@@ -49,7 +49,7 @@ use std::{
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let (count, set_count) = signal(0);
|
||||
///
|
||||
/// // calling .get() clones and returns the value
|
||||
@@ -60,7 +60,7 @@ use std::{
|
||||
pub struct ReadSignal<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: StoredValue<ArcReadSignal<T>, S>,
|
||||
pub(crate) inner: ArenaItem<ArcReadSignal<T>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for ReadSignal<T, S> {
|
||||
@@ -158,7 +158,7 @@ where
|
||||
ReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,7 +172,7 @@ where
|
||||
ReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::guards::{UntrackedWriteGuard, WriteGuard},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
|
||||
@@ -63,13 +63,13 @@ use std::{
|
||||
/// > Each of these has a related `_untracked()` method, which updates the signal
|
||||
/// > without notifying subscribers. Untracked updates are not desirable in most
|
||||
/// > cases, as they cause “tearing” between the signal’s value and its observed
|
||||
/// > value. If you want a non-reactive container, used [`StoredValue`] instead.
|
||||
/// > value. If you want a non-reactive container, used [`ArenaItem`] instead.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let count = ArcRwSignal::new(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
@@ -102,7 +102,7 @@ use std::{
|
||||
pub struct RwSignal<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: StoredValue<ArcRwSignal<T>, S>,
|
||||
inner: ArenaItem<ArcRwSignal<T>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for RwSignal<T, S> {
|
||||
@@ -141,7 +141,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcRwSignal::new(value)),
|
||||
inner: ArenaItem::new_with_storage(ArcRwSignal::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +174,7 @@ where
|
||||
ReadSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(
|
||||
inner: ArenaItem::new_with_storage(
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|inner| inner.read_only())
|
||||
@@ -196,7 +196,7 @@ where
|
||||
WriteSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(
|
||||
inner: ArenaItem::new_with_storage(
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|inner| inner.write_only())
|
||||
@@ -233,7 +233,7 @@ where
|
||||
Some(Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcRwSignal {
|
||||
inner: ArenaItem::new_with_storage(ArcRwSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::clone(&read.value),
|
||||
@@ -365,7 +365,9 @@ where
|
||||
|
||||
#[allow(refining_impl_trait)]
|
||||
fn try_write_untracked(&self) -> Option<UntrackedWriteGuard<Self::Value>> {
|
||||
self.inner.with_value(|n| n.try_write_untracked())
|
||||
self.inner
|
||||
.try_with_value(|n| n.try_write_untracked())
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +380,7 @@ where
|
||||
RwSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,7 +404,7 @@ where
|
||||
RwSignal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value),
|
||||
inner: ArenaItem::new_with_storage(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{subscriber_traits::AsSubscriberSet, ArcTrigger};
|
||||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
owner::StoredValue,
|
||||
owner::ArenaItem,
|
||||
traits::{DefinedAt, Dispose, IsDisposed, Notify},
|
||||
};
|
||||
use std::{
|
||||
@@ -20,7 +20,7 @@ use std::{
|
||||
pub struct Trigger {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: StoredValue<ArcTrigger>,
|
||||
pub(crate) inner: ArenaItem<ArcTrigger>,
|
||||
}
|
||||
|
||||
impl Trigger {
|
||||
@@ -30,7 +30,7 @@ impl Trigger {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new(ArcTrigger::new()),
|
||||
inner: ArenaItem::new(ArcTrigger::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{guards::WriteGuard, ArcWriteSignal};
|
||||
use crate::{
|
||||
owner::{Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, Storage, SyncStorage},
|
||||
traits::{
|
||||
DefinedAt, Dispose, IsDisposed, Notify, UntrackableGuard, Writeable,
|
||||
},
|
||||
@@ -28,11 +28,11 @@ use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
|
||||
/// > Each of these has a related `_untracked()` method, which updates the signal
|
||||
/// > without notifying subscribers. Untracked updates are not desirable in most
|
||||
/// > cases, as they cause “tearing” between the signal’s value and its observed
|
||||
/// > value. If you want a non-reactive container, used [`StoredValue`] instead.
|
||||
/// > value. If you want a non-reactive container, used [`ArenaItem`] instead.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// let (count, set_count) = signal(0);
|
||||
///
|
||||
/// // ✅ calling the setter sets the value
|
||||
@@ -54,7 +54,7 @@ use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
|
||||
pub struct WriteSignal<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
pub(crate) inner: StoredValue<ArcWriteSignal<T>, S>,
|
||||
pub(crate) inner: ArenaItem<ArcWriteSignal<T>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for WriteSignal<T, S> {
|
||||
@@ -145,6 +145,8 @@ where
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
self.inner.with_value(|n| n.try_write_untracked())
|
||||
self.inner
|
||||
.try_with_value(|n| n.try_write_untracked())
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
pub mod read {
|
||||
use crate::{
|
||||
computed::{ArcMemo, Memo},
|
||||
owner::{FromLocal, LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
|
||||
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
|
||||
traits::{DefinedAt, Dispose, Get, With, WithUntracked},
|
||||
untrack, unwrap_signal,
|
||||
@@ -131,7 +131,7 @@ pub mod read {
|
||||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
/// ```rust
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::wrappers::read::ArcSignal;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// let (count, set_count) = arc_signal(2);
|
||||
@@ -279,7 +279,7 @@ pub mod read {
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: StoredValue<SignalTypes<T, S>, S>,
|
||||
inner: ArenaItem<SignalTypes<T, S>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> Dispose for Signal<T, S>
|
||||
@@ -397,7 +397,7 @@ pub mod read {
|
||||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
/// ```rust
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::wrappers::read::Signal;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// let (count, set_count) = signal(2);
|
||||
@@ -425,9 +425,9 @@ pub mod read {
|
||||
};
|
||||
|
||||
Self {
|
||||
inner: StoredValue::new_with_storage(
|
||||
SignalTypes::DerivedSignal(Arc::new(derived_signal)),
|
||||
),
|
||||
inner: ArenaItem::new_with_storage(SignalTypes::DerivedSignal(
|
||||
Arc::new(derived_signal),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
@@ -452,7 +452,7 @@ pub mod read {
|
||||
};
|
||||
|
||||
Self {
|
||||
inner: StoredValue::new_local(SignalTypes::DerivedSignal(
|
||||
inner: ArenaItem::new_local(SignalTypes::DerivedSignal(
|
||||
Arc::new(derived_signal),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -515,7 +515,7 @@ pub mod read {
|
||||
Signal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new(value.inner),
|
||||
inner: ArenaItem::new(value.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -529,7 +529,7 @@ pub mod read {
|
||||
Signal {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_local(value.inner),
|
||||
inner: ArenaItem::new_local(value.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -558,7 +558,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: ReadSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: StoredValue::new(SignalTypes::ReadSignal(value.into())),
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(value.into())),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
@@ -572,7 +572,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: ReadSignal<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
inner: StoredValue::new_local(SignalTypes::ReadSignal(
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(
|
||||
value.into(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -588,7 +588,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: RwSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: StoredValue::new(SignalTypes::ReadSignal(
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(
|
||||
value.read_only().into(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -604,7 +604,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: RwSignal<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
inner: StoredValue::new_local(SignalTypes::ReadSignal(
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(
|
||||
value.read_only().into(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -620,7 +620,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: Memo<T>) -> Self {
|
||||
Self {
|
||||
inner: StoredValue::new(SignalTypes::Memo(value.into())),
|
||||
inner: ArenaItem::new(SignalTypes::Memo(value.into())),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
@@ -634,7 +634,7 @@ pub mod read {
|
||||
#[track_caller]
|
||||
fn from(value: Memo<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
inner: StoredValue::new_local(SignalTypes::Memo(value.into())),
|
||||
inner: ArenaItem::new_local(SignalTypes::Memo(value.into())),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
@@ -647,7 +647,7 @@ pub mod read {
|
||||
/// of the same type. This is especially useful for component properties.
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::wrappers::read::MaybeSignal;
|
||||
/// # use reactive_graph::computed::Memo;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
@@ -907,7 +907,7 @@ pub mod read {
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::wrappers::read::MaybeProp;
|
||||
/// # use reactive_graph::computed::Memo;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
@@ -1246,7 +1246,7 @@ pub mod read {
|
||||
/// Types that abstract over the ability to update a signal.
|
||||
pub mod write {
|
||||
use crate::{
|
||||
owner::{Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, Storage, SyncStorage},
|
||||
signal::{ArcRwSignal, ArcWriteSignal, RwSignal, WriteSignal},
|
||||
traits::Set,
|
||||
};
|
||||
@@ -1282,7 +1282,7 @@ pub mod write {
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::prelude::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||
/// # use reactive_graph::wrappers::write::SignalSetter;
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// let (count, set_count) = signal(2);
|
||||
@@ -1341,7 +1341,7 @@ pub mod write {
|
||||
SignalSetterTypes::Default => {}
|
||||
SignalSetterTypes::Write(w) => w.set(new_value),
|
||||
SignalSetterTypes::Mapped(s) => {
|
||||
s.with_value(|setter| setter(new_value))
|
||||
s.try_with_value(|setter| setter(new_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1371,9 +1371,9 @@ pub mod write {
|
||||
#[track_caller]
|
||||
pub fn map(mapped_setter: impl Fn(T) + Send + Sync + 'static) -> Self {
|
||||
Self {
|
||||
inner: SignalSetterTypes::Mapped(
|
||||
StoredValue::new_with_storage(Box::new(mapped_setter)),
|
||||
),
|
||||
inner: SignalSetterTypes::Mapped(ArenaItem::new_with_storage(
|
||||
Box::new(mapped_setter),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
@@ -1411,7 +1411,7 @@ pub mod write {
|
||||
T: 'static,
|
||||
{
|
||||
Write(WriteSignal<T, S>),
|
||||
Mapped(StoredValue<Box<dyn Fn(T) + Send + Sync>, S>),
|
||||
Mapped(ArenaItem<Box<dyn Fn(T) + Send + Sync>, S>),
|
||||
Default,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use any_spawner::Executor;
|
||||
use reactive_graph::{
|
||||
computed::{ArcAsyncDerived, AsyncDerived},
|
||||
owner::Owner,
|
||||
signal::RwSignal,
|
||||
traits::{Get, Read, Set, With, WithUntracked},
|
||||
};
|
||||
@@ -9,6 +10,8 @@ use std::future::pending;
|
||||
#[tokio::test]
|
||||
async fn arc_async_derived_calculates_eagerly() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let value = ArcAsyncDerived::new(|| async {
|
||||
Executor::tick().await;
|
||||
@@ -21,6 +24,8 @@ async fn arc_async_derived_calculates_eagerly() {
|
||||
#[tokio::test]
|
||||
async fn arc_async_derived_tracks_signal_change() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let signal = RwSignal::new(10);
|
||||
let value = ArcAsyncDerived::new(move || async move {
|
||||
@@ -40,6 +45,8 @@ async fn arc_async_derived_tracks_signal_change() {
|
||||
#[tokio::test]
|
||||
async fn async_derived_calculates_eagerly() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let value = AsyncDerived::new(|| async {
|
||||
Executor::tick().await;
|
||||
@@ -52,6 +59,8 @@ async fn async_derived_calculates_eagerly() {
|
||||
#[tokio::test]
|
||||
async fn async_derived_tracks_signal_change() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let signal = RwSignal::new(10);
|
||||
let value = AsyncDerived::new(move || async move {
|
||||
@@ -71,6 +80,8 @@ async fn async_derived_tracks_signal_change() {
|
||||
#[tokio::test]
|
||||
async fn read_signal_traits_on_arc() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let value = ArcAsyncDerived::new(pending::<()>);
|
||||
assert_eq!(value.read(), None);
|
||||
@@ -82,6 +93,8 @@ async fn read_signal_traits_on_arc() {
|
||||
#[tokio::test]
|
||||
async fn read_signal_traits_on_arena() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let value = AsyncDerived::new(pending::<()>);
|
||||
println!("{:?}", value.read());
|
||||
@@ -94,6 +107,8 @@ async fn read_signal_traits_on_arena() {
|
||||
#[tokio::test]
|
||||
async fn async_derived_with_initial() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let signal1 = RwSignal::new(0);
|
||||
let signal2 = RwSignal::new(0);
|
||||
|
||||
@@ -3,6 +3,7 @@ pub mod imports {
|
||||
pub use any_spawner::Executor;
|
||||
pub use reactive_graph::{
|
||||
effect::{Effect, RenderEffect},
|
||||
owner::Owner,
|
||||
prelude::*,
|
||||
signal::RwSignal,
|
||||
};
|
||||
@@ -19,6 +20,8 @@ async fn render_effect_runs() {
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
let a = RwSignal::new(-1);
|
||||
@@ -54,6 +57,8 @@ async fn effect_runs() {
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -88,6 +93,8 @@ async fn dynamic_dependencies() {
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -156,6 +163,8 @@ async fn recursive_effect_runs_recursively() {
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
let s = RwSignal::new(0);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use reactive_graph::{
|
||||
computed::{ArcMemo, Memo},
|
||||
owner::Owner,
|
||||
prelude::*,
|
||||
signal::RwSignal,
|
||||
wrappers::read::Signal,
|
||||
@@ -29,6 +30,9 @@ pub mod imports {
|
||||
|
||||
#[test]
|
||||
fn memo_calculates_value() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let a = RwSignal::new(1);
|
||||
let b = RwSignal::new(2);
|
||||
let c = RwSignal::new(3);
|
||||
@@ -42,6 +46,9 @@ fn memo_calculates_value() {
|
||||
|
||||
#[test]
|
||||
fn arc_memo_readable() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let a = RwSignal::new(1);
|
||||
let b = RwSignal::new(2);
|
||||
let c = RwSignal::new(3);
|
||||
@@ -52,6 +59,9 @@ fn arc_memo_readable() {
|
||||
|
||||
#[test]
|
||||
fn memo_doesnt_repeat_calculation_per_get() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let calculations = Arc::new(RwLock::new(0));
|
||||
|
||||
let a = RwSignal::new(1);
|
||||
@@ -78,6 +88,9 @@ fn memo_doesnt_repeat_calculation_per_get() {
|
||||
|
||||
#[test]
|
||||
fn nested_memos() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let a = RwSignal::new(0); // 1
|
||||
let b = RwSignal::new(0); // 2
|
||||
let c = Memo::new(move |_| {
|
||||
@@ -111,6 +124,9 @@ fn nested_memos() {
|
||||
|
||||
#[test]
|
||||
fn memo_runs_only_when_inputs_change() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let call_count = Arc::new(RwLock::new(0));
|
||||
let a = RwSignal::new(0);
|
||||
let b = RwSignal::new(0);
|
||||
@@ -150,6 +166,9 @@ fn memo_runs_only_when_inputs_change() {
|
||||
|
||||
#[test]
|
||||
fn diamond_problem() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let name = RwSignal::new("Greg Johnston".to_string());
|
||||
let first = Memo::new(move |_| {
|
||||
println!("calculating first");
|
||||
@@ -187,9 +206,14 @@ fn diamond_problem() {
|
||||
#[cfg(feature = "effects")]
|
||||
#[tokio::test]
|
||||
async fn dynamic_dependencies() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let first = RwSignal::new("Greg");
|
||||
let last = RwSignal::new("Johnston");
|
||||
@@ -264,9 +288,14 @@ async fn dynamic_dependencies() {
|
||||
#[cfg(feature = "effects")]
|
||||
#[tokio::test]
|
||||
async fn render_effect_doesnt_rerun_if_memo_didnt_change() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -307,9 +336,14 @@ async fn render_effect_doesnt_rerun_if_memo_didnt_change() {
|
||||
#[cfg(feature = "effects")]
|
||||
#[tokio::test]
|
||||
async fn effect_doesnt_rerun_if_memo_didnt_change() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -343,9 +377,14 @@ async fn effect_doesnt_rerun_if_memo_didnt_change() {
|
||||
#[cfg(feature = "effects")]
|
||||
#[tokio::test]
|
||||
async fn effect_depending_on_signal_and_memo_doesnt_rerun_unnecessarily() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
use imports::*;
|
||||
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -383,6 +422,9 @@ async fn effect_depending_on_signal_and_memo_doesnt_rerun_unnecessarily() {
|
||||
|
||||
#[test]
|
||||
fn unsync_derived_signal_and_memo() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let a = RwSignal::new_local(Rc::new(1));
|
||||
let b = RwSignal::new(2);
|
||||
let c = RwSignal::new(3);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use reactive_graph::{
|
||||
owner::Owner,
|
||||
signal::{arc_signal, signal, ArcRwSignal, RwSignal},
|
||||
traits::{
|
||||
Get, GetUntracked, Read, Set, Update, UpdateUntracked, With,
|
||||
@@ -54,6 +55,9 @@ fn update_arc_signal() {
|
||||
|
||||
#[test]
|
||||
fn create_rw_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let a = RwSignal::new(0);
|
||||
assert_eq!(a.read(), 0);
|
||||
assert_eq!(a.get(), 0);
|
||||
@@ -63,6 +67,9 @@ fn create_rw_signal() {
|
||||
|
||||
#[test]
|
||||
fn update_rw_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let a = RwSignal::new(1);
|
||||
assert_eq!(a.read(), 1);
|
||||
assert_eq!(a.get(), 1);
|
||||
@@ -76,6 +83,9 @@ fn update_rw_signal() {
|
||||
|
||||
#[test]
|
||||
fn create_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let (a, _) = signal(0);
|
||||
assert_eq!(a.read(), 0);
|
||||
assert_eq!(a.get(), 0);
|
||||
@@ -86,6 +96,9 @@ fn create_signal() {
|
||||
|
||||
#[test]
|
||||
fn update_signal() {
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
let (a, set_a) = signal(1);
|
||||
assert_eq!(a.get(), 1);
|
||||
set_a.update(|n| *n += 1);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#[cfg(feature = "effects")]
|
||||
use any_spawner::Executor;
|
||||
#[cfg(feature = "effects")]
|
||||
use reactive_graph::owner::Owner;
|
||||
#[cfg(feature = "effects")]
|
||||
use reactive_graph::{effect::Effect, prelude::*, signal::RwSignal};
|
||||
#[cfg(feature = "effects")]
|
||||
use std::sync::{Arc, RwLock};
|
||||
@@ -11,6 +13,8 @@ use tokio::task;
|
||||
#[tokio::test]
|
||||
async fn watch_runs() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -71,6 +75,8 @@ async fn watch_runs() {
|
||||
#[tokio::test]
|
||||
async fn watch_runs_immediately() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -118,6 +124,8 @@ async fn watch_runs_immediately() {
|
||||
#[tokio::test]
|
||||
async fn watch_ignores_callback() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
@@ -174,6 +182,8 @@ async fn watch_ignores_callback() {
|
||||
#[tokio::test]
|
||||
async fn deprecated_watch_runs() {
|
||||
_ = Executor::init_tokio();
|
||||
let owner = Owner::new();
|
||||
owner.set();
|
||||
|
||||
task::LocalSet::new()
|
||||
.run_until(async {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores"
|
||||
version = "0.1.0-beta5"
|
||||
version = "0.1.0-gamma"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
use crate::{
|
||||
path::{StorePath, StorePathSegment},
|
||||
AtIndex, AtKeyed, KeyMap, KeyedSubfield, StoreField, Subfield,
|
||||
AtIndex, AtKeyed, KeyMap, KeyedSubfield, StoreField, StoreFieldTrigger,
|
||||
Subfield,
|
||||
};
|
||||
use reactive_graph::{
|
||||
signal::ArcTrigger,
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
},
|
||||
use reactive_graph::traits::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
@@ -23,8 +21,8 @@ where
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
path: StorePath,
|
||||
trigger: ArcTrigger,
|
||||
get_trigger: Arc<dyn Fn(StorePath) -> ArcTrigger + Send + Sync>,
|
||||
trigger: StoreFieldTrigger,
|
||||
get_trigger: Arc<dyn Fn(StorePath) -> StoreFieldTrigger + Send + Sync>,
|
||||
read: Arc<dyn Fn() -> Option<StoreFieldReader<T>> + Send + Sync>,
|
||||
write: Arc<dyn Fn() -> Option<StoreFieldWriter<T>> + Send + Sync>,
|
||||
keys: Arc<dyn Fn() -> Option<KeyMap> + Send + Sync>,
|
||||
@@ -78,9 +76,8 @@ impl<T> StoreField for ArcField<T> {
|
||||
type Value = T;
|
||||
type Reader = StoreFieldReader<T>;
|
||||
type Writer = StoreFieldWriter<T>;
|
||||
type UntrackedWriter = StoreFieldWriter<T>;
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
(self.get_trigger)(path)
|
||||
}
|
||||
|
||||
@@ -96,12 +93,6 @@ impl<T> StoreField for ArcField<T> {
|
||||
(self.write)().map(StoreFieldWriter::new)
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
let mut writer = (self.write)().map(StoreFieldWriter::new)?;
|
||||
writer.untrack();
|
||||
Some(writer)
|
||||
}
|
||||
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
(self.keys)()
|
||||
}
|
||||
@@ -243,13 +234,14 @@ impl<T> DefinedAt for ArcField<T> {
|
||||
|
||||
impl<T> Notify for ArcField<T> {
|
||||
fn notify(&self) {
|
||||
self.trigger.notify();
|
||||
self.trigger.this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Track for ArcField<T> {
|
||||
fn track(&self) {
|
||||
self.trigger.track();
|
||||
self.trigger.this.track();
|
||||
self.trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::{
|
||||
arc_field::{StoreFieldReader, StoreFieldWriter},
|
||||
path::{StorePath, StorePathSegment},
|
||||
ArcField, AtIndex, AtKeyed, KeyMap, KeyedSubfield, StoreField, Subfield,
|
||||
ArcField, AtIndex, AtKeyed, KeyMap, KeyedSubfield, StoreField,
|
||||
StoreFieldTrigger, Subfield,
|
||||
};
|
||||
use reactive_graph::{
|
||||
owner::{Storage, StoredValue, SyncStorage},
|
||||
signal::ArcTrigger,
|
||||
owner::{ArenaItem, Storage, SyncStorage},
|
||||
traits::{DefinedAt, IsDisposed, Notify, ReadUntracked, Track},
|
||||
unwrap_signal,
|
||||
};
|
||||
@@ -17,7 +17,7 @@ where
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: StoredValue<ArcField<T>, S>,
|
||||
inner: ArenaItem<ArcField<T>, S>,
|
||||
}
|
||||
|
||||
impl<T, S> StoreField for Field<T, S>
|
||||
@@ -27,9 +27,8 @@ where
|
||||
type Value = T;
|
||||
type Reader = StoreFieldReader<T>;
|
||||
type Writer = StoreFieldWriter<T>;
|
||||
type UntrackedWriter = StoreFieldWriter<T>;
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|inner| inner.get_trigger(path))
|
||||
@@ -51,12 +50,6 @@ where
|
||||
self.inner.try_get_value().and_then(|inner| inner.writer())
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.and_then(|inner| inner.untracked_writer())
|
||||
}
|
||||
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.try_get_value().and_then(|n| n.keys())
|
||||
}
|
||||
@@ -75,7 +68,7 @@ where
|
||||
Field {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value.into()),
|
||||
inner: ArenaItem::new_with_storage(value.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +86,7 @@ where
|
||||
Field {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value.into()),
|
||||
inner: ArenaItem::new_with_storage(value.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,7 +109,7 @@ where
|
||||
Field {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(value.into()),
|
||||
inner: ArenaItem::new_with_storage(value.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
path::{StorePath, StorePathSegment},
|
||||
store_field::StoreField,
|
||||
KeyMap,
|
||||
KeyMap, StoreFieldTrigger,
|
||||
};
|
||||
use reactive_graph::{
|
||||
signal::{
|
||||
@@ -69,8 +69,6 @@ where
|
||||
type Reader = MappedMutArc<Inner::Reader, Prev::Output>;
|
||||
type Writer =
|
||||
MappedMutArc<WriteGuard<ArcTrigger, Inner::Writer>, Prev::Output>;
|
||||
type UntrackedWriter =
|
||||
MappedMutArc<WriteGuard<ArcTrigger, Inner::Writer>, Prev::Output>;
|
||||
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
|
||||
self.inner
|
||||
@@ -79,7 +77,7 @@ where
|
||||
.chain(iter::once(self.index.into()))
|
||||
}
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner.get_trigger(path)
|
||||
}
|
||||
|
||||
@@ -95,7 +93,7 @@ where
|
||||
|
||||
fn writer(&self) -> Option<Self::Writer> {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
let inner = WriteGuard::new(trigger, self.inner.writer()?);
|
||||
let inner = WriteGuard::new(trigger.children, self.inner.writer()?);
|
||||
let index = self.index;
|
||||
Some(MappedMutArc::new(
|
||||
inner,
|
||||
@@ -104,12 +102,6 @@ where
|
||||
))
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
let mut guard = self.writer()?;
|
||||
guard.untrack();
|
||||
Some(guard)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.keys()
|
||||
@@ -149,7 +141,7 @@ where
|
||||
{
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.notify();
|
||||
trigger.this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +153,8 @@ where
|
||||
{
|
||||
fn track(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +217,8 @@ where
|
||||
fn iter(self) -> StoreFieldIter<Inner, Prev> {
|
||||
// reactively track changes to this field
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
|
||||
// get the current length of the field by accessing slice
|
||||
let len = self.reader().map(|n| n.as_ref().len()).unwrap_or(0);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
path::{StorePath, StorePathSegment},
|
||||
store_field::StoreField,
|
||||
KeyMap,
|
||||
KeyMap, StoreFieldTrigger,
|
||||
};
|
||||
use reactive_graph::{
|
||||
signal::{
|
||||
@@ -96,8 +96,6 @@ where
|
||||
type Value = T;
|
||||
type Reader = Mapped<Inner::Reader, T>;
|
||||
type Writer = MappedMut<WriteGuard<ArcTrigger, Inner::Writer>, T>;
|
||||
type UntrackedWriter =
|
||||
MappedMut<WriteGuard<ArcTrigger, Inner::UntrackedWriter>, T>;
|
||||
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
|
||||
self.inner
|
||||
@@ -106,7 +104,7 @@ where
|
||||
.chain(iter::once(self.path_segment))
|
||||
}
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner.get_trigger(path)
|
||||
}
|
||||
|
||||
@@ -118,16 +116,10 @@ where
|
||||
fn writer(&self) -> Option<Self::Writer> {
|
||||
let path = self.path().into_iter().collect::<StorePath>();
|
||||
let trigger = self.get_trigger(path.clone());
|
||||
let guard = WriteGuard::new(trigger, self.inner.writer()?);
|
||||
let guard = WriteGuard::new(trigger.children, self.inner.writer()?);
|
||||
Some(MappedMut::new(guard, self.read, self.write))
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
let inner = WriteGuard::new(trigger, self.inner.untracked_writer()?);
|
||||
Some(MappedMut::new(inner, self.read, self.write))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.keys()
|
||||
@@ -237,6 +229,7 @@ where
|
||||
// now that the write lock is release, we can get a read lock to refresh this keyed field
|
||||
// based on the new value
|
||||
self.inner.update_keys();
|
||||
self.inner.notify();
|
||||
|
||||
// reactive updates happen on the next tick
|
||||
}
|
||||
@@ -279,7 +272,8 @@ where
|
||||
{
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.notify();
|
||||
trigger.this.notify();
|
||||
trigger.children.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,9 +287,13 @@ where
|
||||
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
|
||||
{
|
||||
fn track(&self) {
|
||||
self.inner.track();
|
||||
let inner = self
|
||||
.inner
|
||||
.get_trigger(self.inner.path().into_iter().collect());
|
||||
inner.this.track();
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,13 +415,6 @@ where
|
||||
T::Output,
|
||||
>,
|
||||
>;
|
||||
type UntrackedWriter = WriteGuard<
|
||||
ArcTrigger,
|
||||
MappedMutArc<
|
||||
<KeyedSubfield<Inner, Prev, K, T> as StoreField>::Writer,
|
||||
T::Output,
|
||||
>,
|
||||
>;
|
||||
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
|
||||
let inner = self.inner.path().into_iter().collect::<StorePath>();
|
||||
@@ -442,7 +433,7 @@ where
|
||||
inner.into_iter().chain(this)
|
||||
}
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner.get_trigger(path)
|
||||
}
|
||||
|
||||
@@ -491,7 +482,7 @@ where
|
||||
.expect("reading from a keyed field that has not yet been created");
|
||||
|
||||
Some(WriteGuard::new(
|
||||
trigger,
|
||||
trigger.children,
|
||||
MappedMutArc::new(
|
||||
inner,
|
||||
move |n| &n[index],
|
||||
@@ -500,12 +491,6 @@ where
|
||||
))
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
let mut guard = self.writer()?;
|
||||
guard.untrack();
|
||||
Some(guard)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.keys()
|
||||
@@ -550,7 +535,8 @@ where
|
||||
{
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.notify();
|
||||
trigger.this.notify();
|
||||
trigger.children.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +552,8 @@ where
|
||||
{
|
||||
fn track(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,7 +642,7 @@ where
|
||||
fn into_iter(self) -> StoreFieldKeyedIter<Inner, Prev, K, T> {
|
||||
// reactively track changes to this field
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
|
||||
// get the current length of the field by accessing slice
|
||||
let reader = self
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
owner::{LocalStorage, Storage, StoredValue, SyncStorage},
|
||||
owner::{ArenaItem, LocalStorage, Storage, SyncStorage},
|
||||
signal::{
|
||||
guards::{Plain, ReadGuard},
|
||||
guards::{Plain, ReadGuard, WriteGuard},
|
||||
ArcTrigger,
|
||||
},
|
||||
traits::{DefinedAt, IsDisposed, Notify, ReadUntracked, Track},
|
||||
traits::{
|
||||
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
|
||||
Writeable,
|
||||
},
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
@@ -13,6 +16,7 @@ use std::{
|
||||
collections::HashMap,
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
ops::DerefMut,
|
||||
panic::Location,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
@@ -33,26 +37,38 @@ pub use iter::*;
|
||||
pub use keyed::*;
|
||||
pub use option::*;
|
||||
pub use patch::*;
|
||||
use path::{StorePath, StorePathSegment};
|
||||
pub use path::{StorePath, StorePathSegment};
|
||||
pub use store_field::{StoreField, Then};
|
||||
pub use subfield::Subfield;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct TriggerMap(FxHashMap<StorePath, ArcTrigger>);
|
||||
struct TriggerMap(FxHashMap<StorePath, StoreFieldTrigger>);
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct StoreFieldTrigger {
|
||||
pub this: ArcTrigger,
|
||||
pub children: ArcTrigger,
|
||||
}
|
||||
|
||||
impl StoreFieldTrigger {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl TriggerMap {
|
||||
fn get_or_insert(&mut self, key: StorePath) -> ArcTrigger {
|
||||
fn get_or_insert(&mut self, key: StorePath) -> StoreFieldTrigger {
|
||||
if let Some(trigger) = self.0.get(&key) {
|
||||
trigger.clone()
|
||||
} else {
|
||||
let new = ArcTrigger::new();
|
||||
let new = StoreFieldTrigger::new();
|
||||
self.0.insert(key, new.clone());
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn remove(&mut self, key: &StorePath) -> Option<ArcTrigger> {
|
||||
fn remove(&mut self, key: &StorePath) -> Option<StoreFieldTrigger> {
|
||||
self.0.remove(key)
|
||||
}
|
||||
}
|
||||
@@ -238,22 +254,46 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Writeable for ArcStore<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
self.writer()
|
||||
.map(|writer| WriteGuard::new(self.clone(), writer))
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
let mut writer = self.writer()?;
|
||||
writer.untrack();
|
||||
Some(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Track for ArcStore<T> {
|
||||
fn track(&self) {
|
||||
self.get_trigger(Default::default()).track();
|
||||
let trigger = self.get_trigger(Default::default());
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Notify for ArcStore<T> {
|
||||
fn notify(&self) {
|
||||
self.get_trigger(self.path().into_iter().collect()).notify();
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.this.notify();
|
||||
trigger.children.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Store<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
inner: StoredValue<ArcStore<T>, S>,
|
||||
inner: ArenaItem<ArcStore<T>, S>,
|
||||
}
|
||||
|
||||
impl<T> Store<T>
|
||||
@@ -264,7 +304,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcStore::new(value)),
|
||||
inner: ArenaItem::new_with_storage(ArcStore::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,7 +317,7 @@ where
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
inner: StoredValue::new_with_storage(ArcStore::new(value)),
|
||||
inner: ArenaItem::new_with_storage(ArcStore::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,7 +375,27 @@ where
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|inner| inner.read_untracked())
|
||||
.and_then(|inner| inner.try_read_untracked())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Writeable for Store<T, S>
|
||||
where
|
||||
T: 'static,
|
||||
S: Storage<ArcStore<T>>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
self.writer().map(|writer| WriteGuard::new(*self, writer))
|
||||
}
|
||||
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||
let mut writer = self.writer()?;
|
||||
writer.untrack();
|
||||
Some(writer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,13 +440,13 @@ mod tests {
|
||||
tokio::time::sleep(std::time::Duration::from_micros(1)).await;
|
||||
}
|
||||
|
||||
#[derive(Debug, Store, Patch)]
|
||||
#[derive(Debug, Store, Patch, Default)]
|
||||
struct Todos {
|
||||
user: String,
|
||||
todos: Vec<Todo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Store, Patch)]
|
||||
#[derive(Debug, Store, Patch, Default)]
|
||||
struct Todo {
|
||||
label: String,
|
||||
completed: bool,
|
||||
@@ -482,9 +542,37 @@ mod tests {
|
||||
tick().await;
|
||||
store.user().update(|name| name.push_str("!!!"));
|
||||
tick().await;
|
||||
// TODO known to be broken, I just need to fix CI for now
|
||||
// the effect reads from `user`, so it should trigger every time
|
||||
//assert_eq!(combined_count.load(Ordering::Relaxed), 1);
|
||||
// the effect reads from `todos`, so it shouldn't trigger every time
|
||||
assert_eq!(combined_count.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn parent_does_notify() {
|
||||
_ = any_spawner::Executor::init_tokio();
|
||||
|
||||
let combined_count = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let store = Store::new(data());
|
||||
|
||||
Effect::new_sync({
|
||||
let combined_count = Arc::clone(&combined_count);
|
||||
move |prev: Option<()>| {
|
||||
if prev.is_none() {
|
||||
println!("first run");
|
||||
} else {
|
||||
println!("next run");
|
||||
}
|
||||
println!("{:?}", *store.todos().read());
|
||||
combined_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
tick().await;
|
||||
tick().await;
|
||||
store.set(Todos::default());
|
||||
tick().await;
|
||||
store.set(data());
|
||||
tick().await;
|
||||
assert_eq!(combined_count.load(Ordering::Relaxed), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -27,12 +27,13 @@ where
|
||||
type Value = T::Value;
|
||||
|
||||
fn patch(&self, new: Self::Value) {
|
||||
let path = StorePath::default();
|
||||
let path = self.path().into_iter().collect::<StorePath>();
|
||||
if let Some(mut writer) = self.writer() {
|
||||
// don't track the writer for the whole store
|
||||
writer.untrack();
|
||||
let mut notify = |path: &StorePath| {
|
||||
self.get_trigger(path.to_owned()).notify();
|
||||
self.get_trigger(path.to_owned()).this.notify();
|
||||
self.get_trigger(path.to_owned()).children.notify();
|
||||
};
|
||||
writer.patch_field(new, &path, &mut notify);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
path::{StorePath, StorePathSegment},
|
||||
ArcStore, KeyMap, Store,
|
||||
ArcStore, KeyMap, Store, StoreFieldTrigger,
|
||||
};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
@@ -26,24 +26,22 @@ pub trait StoreField: Sized {
|
||||
type Value;
|
||||
type Reader: Deref<Target = Self::Value>;
|
||||
type Writer: UntrackableGuard<Target = Self::Value>;
|
||||
type UntrackedWriter: DerefMut<Target = Self::Value>;
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger;
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger;
|
||||
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment>;
|
||||
|
||||
fn track_field(&self) {
|
||||
let path = self.path().into_iter().collect();
|
||||
let trigger = self.get_trigger(path);
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
|
||||
fn reader(&self) -> Option<Self::Reader>;
|
||||
|
||||
fn writer(&self) -> Option<Self::Writer>;
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter>;
|
||||
|
||||
fn keys(&self) -> Option<KeyMap>;
|
||||
|
||||
#[track_caller]
|
||||
@@ -69,9 +67,8 @@ where
|
||||
type Value = T;
|
||||
type Reader = Plain<T>;
|
||||
type Writer = WriteGuard<ArcTrigger, UntrackedWriteGuard<T>>;
|
||||
type UntrackedWriter = UntrackedWriteGuard<T>;
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
let triggers = &self.signals;
|
||||
let trigger = triggers.write().or_poisoned().get_or_insert(path);
|
||||
trigger
|
||||
@@ -87,12 +84,8 @@ where
|
||||
|
||||
fn writer(&self) -> Option<Self::Writer> {
|
||||
let trigger = self.get_trigger(Default::default());
|
||||
let guard = self.untracked_writer()?;
|
||||
Some(WriteGuard::new(trigger, guard))
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
UntrackedWriteGuard::try_new(Arc::clone(&self.value))
|
||||
let guard = UntrackedWriteGuard::try_new(Arc::clone(&self.value))?;
|
||||
Some(WriteGuard::new(trigger.children, guard))
|
||||
}
|
||||
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
@@ -108,9 +101,8 @@ where
|
||||
type Value = T;
|
||||
type Reader = Plain<T>;
|
||||
type Writer = WriteGuard<ArcTrigger, UntrackedWriteGuard<T>>;
|
||||
type UntrackedWriter = UntrackedWriteGuard<T>;
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.map(|n| n.get_trigger(path))
|
||||
@@ -132,12 +124,6 @@ where
|
||||
self.inner.try_get_value().and_then(|n| n.writer())
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
self.inner
|
||||
.try_get_value()
|
||||
.and_then(|n| n.untracked_writer())
|
||||
}
|
||||
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.try_get_value().and_then(|inner| inner.keys())
|
||||
}
|
||||
@@ -182,9 +168,8 @@ where
|
||||
type Value = T;
|
||||
type Reader = Mapped<S::Reader, T>;
|
||||
type Writer = MappedMut<S::Writer, T>;
|
||||
type UntrackedWriter = MappedMut<S::UntrackedWriter, T>;
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner.get_trigger(path)
|
||||
}
|
||||
|
||||
@@ -202,11 +187,6 @@ where
|
||||
Some(MappedMut::new(inner, self.map_fn, self.map_fn_mut))
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
let inner = self.inner.untracked_writer()?;
|
||||
Some(MappedMut::new(inner, self.map_fn, self.map_fn_mut))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn keys(&self) -> Option<KeyMap> {
|
||||
self.inner.keys()
|
||||
@@ -244,7 +224,8 @@ where
|
||||
{
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.notify();
|
||||
trigger.this.notify();
|
||||
trigger.children.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +235,8 @@ where
|
||||
{
|
||||
fn track(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
path::{StorePath, StorePathSegment},
|
||||
store_field::StoreField,
|
||||
KeyMap,
|
||||
KeyMap, StoreFieldTrigger,
|
||||
};
|
||||
use reactive_graph::{
|
||||
signal::{
|
||||
@@ -73,8 +73,6 @@ where
|
||||
type Value = T;
|
||||
type Reader = Mapped<Inner::Reader, T>;
|
||||
type Writer = MappedMut<WriteGuard<ArcTrigger, Inner::Writer>, T>;
|
||||
type UntrackedWriter =
|
||||
MappedMut<WriteGuard<ArcTrigger, Inner::UntrackedWriter>, T>;
|
||||
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
|
||||
self.inner
|
||||
@@ -83,7 +81,7 @@ where
|
||||
.chain(iter::once(self.path_segment))
|
||||
}
|
||||
|
||||
fn get_trigger(&self, path: StorePath) -> ArcTrigger {
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
|
||||
self.inner.get_trigger(path)
|
||||
}
|
||||
|
||||
@@ -94,13 +92,7 @@ where
|
||||
|
||||
fn writer(&self) -> Option<Self::Writer> {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
let inner = WriteGuard::new(trigger, self.inner.writer()?);
|
||||
Some(MappedMut::new(inner, self.read, self.write))
|
||||
}
|
||||
|
||||
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
let inner = WriteGuard::new(trigger, self.inner.untracked_writer()?);
|
||||
let inner = WriteGuard::new(trigger.children, self.inner.writer()?);
|
||||
Some(MappedMut::new(inner, self.read, self.write))
|
||||
}
|
||||
|
||||
@@ -142,7 +134,8 @@ where
|
||||
{
|
||||
fn notify(&self) {
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.notify();
|
||||
trigger.this.notify();
|
||||
trigger.children.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,9 +146,13 @@ where
|
||||
T: 'static,
|
||||
{
|
||||
fn track(&self) {
|
||||
self.inner.track();
|
||||
let inner = self
|
||||
.inner
|
||||
.get_trigger(self.inner.path().into_iter().collect());
|
||||
inner.this.track();
|
||||
let trigger = self.get_trigger(self.path().into_iter().collect());
|
||||
trigger.track();
|
||||
trigger.this.track();
|
||||
trigger.children.track();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reactive_stores_macro"
|
||||
version = "0.1.0-beta5"
|
||||
version = "0.1.0-gamma"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -9,7 +9,7 @@ proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
convert_case = "0.6"
|
||||
proc-macro-error = "1.0"
|
||||
proc-macro-error2 = "2.0"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use convert_case::{Case, Casing};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use proc_macro_error::{abort, abort_call_site, proc_macro_error};
|
||||
use proc_macro_error2::{abort, abort_call_site, proc_macro_error};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream, Parser},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.7.0-beta5"
|
||||
version = "0.7.0-gamma"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -24,11 +24,10 @@ use reactive_graph::{
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{Debug, Display},
|
||||
marker::PhantomData,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use tachys::{renderer::dom::Dom, view::any_view::AnyView};
|
||||
use tachys::view::any_view::AnyView;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RouteChildren<Children>(Children);
|
||||
@@ -211,7 +210,7 @@ pub fn Routes<Defs, FallbackFn, Fallback>(
|
||||
children: RouteChildren<Defs>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
Defs: MatchNestedRoutes<Dom> + Clone + Send + 'static,
|
||||
Defs: MatchNestedRoutes + Clone + Send + 'static,
|
||||
FallbackFn: FnOnce() -> Fallback + Clone + Send + 'static,
|
||||
Fallback: IntoView + 'static,
|
||||
{
|
||||
@@ -243,7 +242,6 @@ where
|
||||
current_url: current_url.clone(),
|
||||
base: base.clone(),
|
||||
fallback: fallback.clone(),
|
||||
rndr: PhantomData,
|
||||
set_is_routing,
|
||||
}
|
||||
}
|
||||
@@ -255,7 +253,7 @@ pub fn FlatRoutes<Defs, FallbackFn, Fallback>(
|
||||
children: RouteChildren<Defs>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
Defs: MatchNestedRoutes<Dom> + Clone + Send + 'static,
|
||||
Defs: MatchNestedRoutes + Clone + Send + 'static,
|
||||
FallbackFn: FnOnce() -> Fallback + Clone + Send + 'static,
|
||||
Fallback: IntoView + 'static,
|
||||
{
|
||||
@@ -301,9 +299,9 @@ pub fn Route<Segments, View>(
|
||||
path: Segments,
|
||||
view: View,
|
||||
#[prop(optional)] ssr: SsrMode,
|
||||
) -> NestedRoute<Segments, (), (), View, Dom>
|
||||
) -> NestedRoute<Segments, (), (), View>
|
||||
where
|
||||
View: ChooseView<Dom>,
|
||||
View: ChooseView,
|
||||
{
|
||||
NestedRoute::new(path, view).ssr_mode(ssr)
|
||||
}
|
||||
@@ -314,9 +312,9 @@ pub fn ParentRoute<Segments, View, Children>(
|
||||
view: View,
|
||||
children: RouteChildren<Children>,
|
||||
#[prop(optional)] ssr: SsrMode,
|
||||
) -> NestedRoute<Segments, Children, (), View, Dom>
|
||||
) -> NestedRoute<Segments, Children, (), View>
|
||||
where
|
||||
View: ChooseView<Dom>,
|
||||
View: ChooseView,
|
||||
{
|
||||
let children = children.into_inner();
|
||||
NestedRoute::new(path, view).ssr_mode(ssr).child(children)
|
||||
@@ -329,7 +327,7 @@ pub fn ProtectedRoute<Segments, ViewFn, View, C, PathFn, P>(
|
||||
condition: C,
|
||||
redirect_path: PathFn,
|
||||
#[prop(optional)] ssr: SsrMode,
|
||||
) -> NestedRoute<Segments, (), (), impl Fn() -> AnyView<Dom> + Send + Clone, Dom>
|
||||
) -> NestedRoute<Segments, (), (), impl Fn() -> AnyView + Send + Clone>
|
||||
where
|
||||
ViewFn: Fn() -> View + Send + Clone + 'static,
|
||||
View: IntoView + 'static,
|
||||
@@ -372,13 +370,7 @@ pub fn ProtectedParentRoute<Segments, ViewFn, View, C, PathFn, P, Children>(
|
||||
redirect_path: PathFn,
|
||||
children: RouteChildren<Children>,
|
||||
#[prop(optional)] ssr: SsrMode,
|
||||
) -> NestedRoute<
|
||||
Segments,
|
||||
Children,
|
||||
(),
|
||||
impl Fn() -> AnyView<Dom> + Send + Clone,
|
||||
Dom,
|
||||
>
|
||||
) -> NestedRoute<Segments, Children, (), impl Fn() -> AnyView + Send + Clone>
|
||||
where
|
||||
ViewFn: Fn() -> View + Send + Clone + 'static,
|
||||
View: IntoView + 'static,
|
||||
|
||||
@@ -21,7 +21,6 @@ use std::{cell::RefCell, iter, mem, rc::Rc};
|
||||
use tachys::{
|
||||
hydration::Cursor,
|
||||
reactive_graph::OwnedView,
|
||||
renderer::Renderer,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
@@ -29,23 +28,22 @@ use tachys::{
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) struct FlatRoutesView<Loc, Defs, FalFn, R> {
|
||||
pub(crate) struct FlatRoutesView<Loc, Defs, FalFn> {
|
||||
pub current_url: ArcRwSignal<Url>,
|
||||
pub location: Option<Loc>,
|
||||
pub routes: Routes<Defs, R>,
|
||||
pub routes: Routes<Defs>,
|
||||
pub fallback: FalFn,
|
||||
pub outer_owner: Owner,
|
||||
pub set_is_routing: Option<SignalSetter<bool>>,
|
||||
}
|
||||
|
||||
pub struct FlatRoutesViewState<Defs, Fal, R>
|
||||
pub struct FlatRoutesViewState<Defs, Fal>
|
||||
where
|
||||
Defs: MatchNestedRoutes<R> + 'static,
|
||||
Fal: Render<R> + 'static,
|
||||
R: Renderer + 'static
|
||||
Defs: MatchNestedRoutes + 'static,
|
||||
Fal: Render + 'static,
|
||||
{
|
||||
#[allow(clippy::type_complexity)]
|
||||
view: <EitherOf3<(), Fal, <Defs::Match as MatchInterface<R>>::View> as Render<R>>::State,
|
||||
view: <EitherOf3<(), Fal, OwnedView<<Defs::Match as MatchInterface>::View>> as Render>::State,
|
||||
id: Option<RouteMatchId>,
|
||||
owner: Owner,
|
||||
params: ArcRwSignal<ParamsMap>,
|
||||
@@ -54,11 +52,10 @@ where
|
||||
matched: ArcRwSignal<String>
|
||||
}
|
||||
|
||||
impl<Defs, Fal, R> Mountable<R> for FlatRoutesViewState<Defs, Fal, R>
|
||||
impl<Defs, Fal> Mountable for FlatRoutesViewState<Defs, Fal>
|
||||
where
|
||||
Defs: MatchNestedRoutes<R> + 'static,
|
||||
Fal: Render<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
Defs: MatchNestedRoutes + 'static,
|
||||
Fal: Render + 'static,
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
self.view.unmount();
|
||||
@@ -66,26 +63,25 @@ where
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
parent: &<R as Renderer>::Element,
|
||||
marker: Option<&<R as Renderer>::Node>,
|
||||
parent: &leptos::tachys::renderer::types::Element,
|
||||
marker: Option<&leptos::tachys::renderer::types::Node>,
|
||||
) {
|
||||
self.view.mount(parent, marker);
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<R>) -> bool {
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
|
||||
self.view.insert_before_this(child)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Loc, Defs, FalFn, Fal, R> Render<R> for FlatRoutesView<Loc, Defs, FalFn, R>
|
||||
impl<Loc, Defs, FalFn, Fal> Render for FlatRoutesView<Loc, Defs, FalFn>
|
||||
where
|
||||
Loc: LocationProvider,
|
||||
Defs: MatchNestedRoutes<R> + 'static,
|
||||
Defs: MatchNestedRoutes + 'static,
|
||||
FalFn: FnOnce() -> Fal + Send,
|
||||
Fal: Render<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
Fal: Render + 'static,
|
||||
{
|
||||
type State = Rc<RefCell<FlatRoutesViewState<Defs, Fal, R>>>;
|
||||
type State = Rc<RefCell<FlatRoutesViewState<Defs, Fal>>>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let FlatRoutesView {
|
||||
@@ -151,7 +147,7 @@ where
|
||||
provide_context(params_memo);
|
||||
provide_context(url);
|
||||
provide_context(Matched(ArcMemo::from(matched)));
|
||||
view.choose().await
|
||||
OwnedView::new(view.choose().await)
|
||||
}
|
||||
})
|
||||
}));
|
||||
@@ -296,7 +292,7 @@ where
|
||||
provide_context(Matched(ArcMemo::from(
|
||||
new_matched,
|
||||
)));
|
||||
let view =
|
||||
let view = OwnedView::new(
|
||||
if let Some(set_is_routing) = set_is_routing {
|
||||
set_is_routing.set(true);
|
||||
let value =
|
||||
@@ -306,7 +302,8 @@ where
|
||||
value
|
||||
} else {
|
||||
view.choose().await
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// only update the route if it's still the current path
|
||||
// i.e., if we've navigated away before this has loaded, do nothing
|
||||
@@ -332,41 +329,37 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Loc, Defs, FalFn, Fal, R> AddAnyAttr<R>
|
||||
for FlatRoutesView<Loc, Defs, FalFn, R>
|
||||
impl<Loc, Defs, FalFn, Fal> AddAnyAttr for FlatRoutesView<Loc, Defs, FalFn>
|
||||
where
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes<R> + Send + 'static,
|
||||
Defs: MatchNestedRoutes + Send + 'static,
|
||||
FalFn: FnOnce() -> Fal + Send,
|
||||
Fal: RenderHtml<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
Fal: RenderHtml + 'static,
|
||||
{
|
||||
type Output<SomeNewAttr: leptos::attr::Attribute<R>> =
|
||||
FlatRoutesView<Loc, Defs, FalFn, R>;
|
||||
type Output<SomeNewAttr: leptos::attr::Attribute> =
|
||||
FlatRoutesView<Loc, Defs, FalFn>;
|
||||
|
||||
fn add_any_attr<NewAttr: leptos::attr::Attribute<R>>(
|
||||
fn add_any_attr<NewAttr: leptos::attr::Attribute>(
|
||||
self,
|
||||
_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<R>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Loc, Defs, FalFn, Fal, R> FlatRoutesView<Loc, Defs, FalFn, R>
|
||||
impl<Loc, Defs, FalFn, Fal> FlatRoutesView<Loc, Defs, FalFn>
|
||||
where
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes<R> + Send + 'static,
|
||||
Defs: MatchNestedRoutes + Send + 'static,
|
||||
FalFn: FnOnce() -> Fal + Send,
|
||||
Fal: RenderHtml<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
Fal: RenderHtml + 'static,
|
||||
{
|
||||
fn choose_ssr(
|
||||
self,
|
||||
) -> OwnedView<Either<Fal, <Defs::Match as MatchInterface<R>>::View>, R>
|
||||
{
|
||||
) -> OwnedView<Either<Fal, <Defs::Match as MatchInterface>::View>> {
|
||||
let current_url = self.current_url.read_untracked();
|
||||
let new_match = self.routes.match_route(current_url.path());
|
||||
let owner = self.outer_owner.child();
|
||||
@@ -411,21 +404,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Loc, Defs, FalFn, Fal, R> RenderHtml<R>
|
||||
for FlatRoutesView<Loc, Defs, FalFn, R>
|
||||
impl<Loc, Defs, FalFn, Fal> RenderHtml for FlatRoutesView<Loc, Defs, FalFn>
|
||||
where
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes<R> + Send + 'static,
|
||||
Defs: MatchNestedRoutes + Send + 'static,
|
||||
FalFn: FnOnce() -> Fal + Send,
|
||||
Fal: RenderHtml<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
Fal: RenderHtml + 'static,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
const MIN_LENGTH: usize = <Either<
|
||||
Fal,
|
||||
<Defs::Match as MatchInterface<R>>::View,
|
||||
> as RenderHtml<R>>::MIN_LENGTH;
|
||||
<Defs::Match as MatchInterface>::View,
|
||||
> as RenderHtml>::MIN_LENGTH;
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
@@ -509,7 +500,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<R>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
// this can be mostly the same as the build() implementation, but with hydrate()
|
||||
@@ -582,7 +573,7 @@ where
|
||||
provide_context(params_memo);
|
||||
provide_context(url);
|
||||
provide_context(Matched(ArcMemo::from(matched)));
|
||||
view.choose().await
|
||||
OwnedView::new(view.choose().await)
|
||||
}
|
||||
})
|
||||
}));
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::{
|
||||
future::Future,
|
||||
mem,
|
||||
};
|
||||
use tachys::{renderer::Renderer, view::RenderHtml};
|
||||
use tachys::view::RenderHtml;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
/// A route that this application can serve.
|
||||
@@ -220,13 +220,11 @@ impl RouteList {
|
||||
static GENERATED: RefCell<Option<RouteList>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
pub fn generate<T, Rndr>(app: impl FnOnce() -> T) -> Option<Self>
|
||||
pub fn generate<T>(app: impl FnOnce() -> T) -> Option<Self>
|
||||
where
|
||||
T: RenderHtml<Rndr>,
|
||||
Rndr: Renderer,
|
||||
Rndr::Node: Clone,
|
||||
Rndr::Element: Clone,
|
||||
T: RenderHtml,
|
||||
{
|
||||
let _resource_guard = leptos::server::SuppressResourceLoad::new();
|
||||
Self::IS_GENERATING.set(true);
|
||||
// run the app once, but throw away the HTML
|
||||
// the router won't actually route, but will fill the listing
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
use either_of::*;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
use tachys::{
|
||||
renderer::Renderer,
|
||||
view::{any_view::AnyView, Render},
|
||||
};
|
||||
use tachys::view::{any_view::AnyView, Render};
|
||||
|
||||
pub trait ChooseView<R>
|
||||
pub trait ChooseView
|
||||
where
|
||||
Self: Send + Clone + 'static,
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
type Output;
|
||||
|
||||
@@ -17,11 +13,10 @@ where
|
||||
fn preload(&self) -> impl Future<Output = ()>;
|
||||
}
|
||||
|
||||
impl<F, View, R> ChooseView<R> for F
|
||||
impl<F, View> ChooseView for F
|
||||
where
|
||||
F: Fn() -> View + Send + Clone + 'static,
|
||||
View: Render<R> + Send,
|
||||
R: Renderer + 'static,
|
||||
View: Render + Send,
|
||||
{
|
||||
type Output = View;
|
||||
|
||||
@@ -32,12 +27,11 @@ where
|
||||
async fn preload(&self) {}
|
||||
}
|
||||
|
||||
impl<T, R> ChooseView<R> for Lazy<T>
|
||||
impl<T> ChooseView for Lazy<T>
|
||||
where
|
||||
T: LazyRoute<R>,
|
||||
R: Renderer + 'static,
|
||||
T: LazyRoute,
|
||||
{
|
||||
type Output = AnyView<R>;
|
||||
type Output = AnyView;
|
||||
|
||||
async fn choose(self) -> Self::Output {
|
||||
T::data().view().await
|
||||
@@ -48,13 +42,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LazyRoute<R>: Send + 'static
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
pub trait LazyRoute: Send + 'static {
|
||||
fn data() -> Self;
|
||||
|
||||
fn view(self) -> impl Future<Output = AnyView<R>>;
|
||||
fn view(self) -> impl Future<Output = AnyView>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -82,10 +73,7 @@ impl<T> Default for Lazy<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> ChooseView<R> for ()
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
impl ChooseView for () {
|
||||
type Output = ();
|
||||
|
||||
async fn choose(self) -> Self::Output {}
|
||||
@@ -93,11 +81,10 @@ where
|
||||
async fn preload(&self) {}
|
||||
}
|
||||
|
||||
impl<A, B, Rndr> ChooseView<Rndr> for Either<A, B>
|
||||
impl<A, B> ChooseView for Either<A, B>
|
||||
where
|
||||
A: ChooseView<Rndr>,
|
||||
B: ChooseView<Rndr>,
|
||||
Rndr: Renderer + 'static,
|
||||
A: ChooseView,
|
||||
B: ChooseView,
|
||||
{
|
||||
type Output = Either<A::Output, B::Output>;
|
||||
|
||||
@@ -118,10 +105,9 @@ where
|
||||
|
||||
macro_rules! tuples {
|
||||
($either:ident => $($ty:ident),*) => {
|
||||
impl<$($ty,)* Rndr> ChooseView<Rndr> for $either<$($ty,)*>
|
||||
impl<$($ty,)*> ChooseView for $either<$($ty,)*>
|
||||
where
|
||||
$($ty: ChooseView<Rndr>,)*
|
||||
Rndr: Renderer + 'static,
|
||||
$($ty: ChooseView,)*
|
||||
{
|
||||
type Output = $either<$($ty::Output,)*>;
|
||||
|
||||
|
||||
@@ -9,21 +9,17 @@ mod vertical;
|
||||
use crate::{static_routes::RegenerationFn, Method, SsrMode};
|
||||
pub use horizontal::*;
|
||||
pub use nested::*;
|
||||
use std::{borrow::Cow, collections::HashSet, marker::PhantomData};
|
||||
use tachys::{
|
||||
renderer::Renderer,
|
||||
view::{Render, RenderHtml},
|
||||
};
|
||||
use std::{borrow::Cow, collections::HashSet};
|
||||
use tachys::view::{Render, RenderHtml};
|
||||
pub use vertical::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Routes<Children, Rndr> {
|
||||
pub struct Routes<Children> {
|
||||
base: Option<Cow<'static, str>>,
|
||||
children: Children,
|
||||
ty: PhantomData<Rndr>,
|
||||
}
|
||||
|
||||
impl<Children, Rndr> Clone for Routes<Children, Rndr>
|
||||
impl<Children> Clone for Routes<Children>
|
||||
where
|
||||
Children: Clone,
|
||||
{
|
||||
@@ -31,17 +27,15 @@ where
|
||||
Self {
|
||||
base: self.base.clone(),
|
||||
children: self.children.clone(),
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Children, Rndr> Routes<Children, Rndr> {
|
||||
impl<Children> Routes<Children> {
|
||||
pub fn new(children: Children) -> Self {
|
||||
Self {
|
||||
base: None,
|
||||
children,
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,15 +46,13 @@ impl<Children, Rndr> Routes<Children, Rndr> {
|
||||
Self {
|
||||
base: Some(base.into()),
|
||||
children,
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Children, Rndr> Routes<Children, Rndr>
|
||||
impl<Children> Routes<Children>
|
||||
where
|
||||
Rndr: Renderer + 'static,
|
||||
Children: MatchNestedRoutes<Rndr>,
|
||||
Children: MatchNestedRoutes,
|
||||
{
|
||||
pub fn match_route(&self, path: &str) -> Option<Children::Match> {
|
||||
let path = match &self.base {
|
||||
@@ -101,12 +93,9 @@ where
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct RouteMatchId(pub(crate) u16);
|
||||
|
||||
pub trait MatchInterface<R>
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
type Child: MatchInterface<R> + MatchParams + 'static;
|
||||
type View: Render<R> + RenderHtml<R> + Send + 'static;
|
||||
pub trait MatchInterface {
|
||||
type Child: MatchInterface + MatchParams + 'static;
|
||||
type View: Render + RenderHtml + Send + 'static;
|
||||
|
||||
fn as_id(&self) -> RouteMatchId;
|
||||
|
||||
@@ -114,7 +103,7 @@ where
|
||||
|
||||
fn into_view_and_child(
|
||||
self,
|
||||
) -> (impl ChooseView<R, Output = Self::View>, Option<Self::Child>);
|
||||
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>);
|
||||
}
|
||||
|
||||
pub trait MatchParams {
|
||||
@@ -123,13 +112,10 @@ pub trait MatchParams {
|
||||
fn to_params(&self) -> Self::Params;
|
||||
}
|
||||
|
||||
pub trait MatchNestedRoutes<R>
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
pub trait MatchNestedRoutes {
|
||||
type Data;
|
||||
type View;
|
||||
type Match: MatchInterface<R> + MatchParams;
|
||||
type Match: MatchInterface + MatchParams;
|
||||
|
||||
fn match_nested<'a>(
|
||||
&'a self,
|
||||
@@ -157,12 +143,11 @@ mod tests {
|
||||
WildcardSegment,
|
||||
};
|
||||
use either_of::Either;
|
||||
use tachys::renderer::dom::Dom;
|
||||
|
||||
#[test]
|
||||
pub fn matches_single_root_route() {
|
||||
let routes =
|
||||
Routes::<_, Dom>::new(NestedRoute::new(StaticSegment("/"), || ()));
|
||||
Routes::<_>::new(NestedRoute::new(StaticSegment("/"), || ()));
|
||||
let matched = routes.match_route("/");
|
||||
assert!(matched.is_some());
|
||||
// this case seems like it should match, but implementing it interferes with
|
||||
@@ -178,7 +163,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn matches_nested_route() {
|
||||
let routes: Routes<_, Dom> =
|
||||
let routes: Routes<_> =
|
||||
Routes::new(NestedRoute::new(StaticSegment(""), || "Home").child(
|
||||
NestedRoute::new(
|
||||
(StaticSegment("author"), StaticSegment("contact")),
|
||||
@@ -200,17 +185,17 @@ mod tests {
|
||||
);
|
||||
|
||||
let matched = routes.match_route("/author/contact").unwrap();
|
||||
assert_eq!(MatchInterface::<Dom>::as_matched(&matched), "");
|
||||
let (_, child) = MatchInterface::<Dom>::into_view_and_child(matched);
|
||||
assert_eq!(MatchInterface::as_matched(&matched), "");
|
||||
let (_, child) = MatchInterface::into_view_and_child(matched);
|
||||
assert_eq!(
|
||||
MatchInterface::<Dom>::as_matched(&child.unwrap()),
|
||||
MatchInterface::as_matched(&child.unwrap()),
|
||||
"/author/contact"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn does_not_match_route_unless_full_param_matches() {
|
||||
let routes = Routes::<_, Dom>::new((
|
||||
let routes = Routes::<_>::new((
|
||||
NestedRoute::new(StaticSegment("/property-api"), || ()),
|
||||
NestedRoute::new(StaticSegment("/property"), || ()),
|
||||
));
|
||||
@@ -220,7 +205,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn does_not_match_incomplete_route() {
|
||||
let routes: Routes<_, Dom> =
|
||||
let routes: Routes<_> =
|
||||
Routes::new(NestedRoute::new(StaticSegment(""), || "Home").child(
|
||||
NestedRoute::new(
|
||||
(StaticSegment("author"), StaticSegment("contact")),
|
||||
@@ -233,7 +218,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn chooses_between_nested_routes() {
|
||||
let routes: Routes<_, Dom> = Routes::new((
|
||||
let routes: Routes<_> = Routes::new((
|
||||
NestedRoute::new(StaticSegment("/"), || ()).child((
|
||||
NestedRoute::new(StaticSegment(""), || ()),
|
||||
NestedRoute::new(StaticSegment("about"), || ()),
|
||||
@@ -287,7 +272,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn arbitrary_nested_routes() {
|
||||
let routes: Routes<_, Dom> = Routes::new_with_base(
|
||||
let routes: Routes<_> = Routes::new_with_base(
|
||||
(
|
||||
NestedRoute::new(StaticSegment("/"), || ()).child((
|
||||
NestedRoute::new(StaticSegment("/"), || ()),
|
||||
|
||||
@@ -8,32 +8,27 @@ use either_of::Either;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashSet,
|
||||
marker::PhantomData,
|
||||
sync::atomic::{AtomicU16, Ordering},
|
||||
};
|
||||
use tachys::{
|
||||
renderer::Renderer,
|
||||
view::{Render, RenderHtml},
|
||||
};
|
||||
use tachys::view::{Render, RenderHtml};
|
||||
|
||||
mod tuples;
|
||||
|
||||
static ROUTE_ID: AtomicU16 = AtomicU16::new(1);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct NestedRoute<Segments, Children, Data, View, R> {
|
||||
pub struct NestedRoute<Segments, Children, Data, View> {
|
||||
id: u16,
|
||||
segments: Segments,
|
||||
children: Option<Children>,
|
||||
data: Data,
|
||||
view: View,
|
||||
rndr: PhantomData<R>,
|
||||
methods: HashSet<Method>,
|
||||
ssr_mode: SsrMode,
|
||||
}
|
||||
|
||||
impl<Segments, Children, Data, View, R> Clone
|
||||
for NestedRoute<Segments, Children, Data, View, R>
|
||||
impl<Segments, Children, Data, View> Clone
|
||||
for NestedRoute<Segments, Children, Data, View>
|
||||
where
|
||||
Segments: Clone,
|
||||
Children: Clone,
|
||||
@@ -47,18 +42,16 @@ where
|
||||
children: self.children.clone(),
|
||||
data: self.data.clone(),
|
||||
view: self.view.clone(),
|
||||
rndr: PhantomData,
|
||||
methods: self.methods.clone(),
|
||||
ssr_mode: self.ssr_mode.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Segments, View, R> NestedRoute<Segments, (), (), View, R> {
|
||||
impl<Segments, View> NestedRoute<Segments, (), (), View> {
|
||||
pub fn new(path: Segments, view: View) -> Self
|
||||
where
|
||||
View: ChooseView<R>,
|
||||
R: Renderer + 'static,
|
||||
View: ChooseView,
|
||||
{
|
||||
Self {
|
||||
id: ROUTE_ID.fetch_add(1, Ordering::Relaxed),
|
||||
@@ -66,24 +59,22 @@ impl<Segments, View, R> NestedRoute<Segments, (), (), View, R> {
|
||||
children: None,
|
||||
data: (),
|
||||
view,
|
||||
rndr: PhantomData,
|
||||
methods: [Method::Get].into(),
|
||||
ssr_mode: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Segments, Data, View, R> NestedRoute<Segments, (), Data, View, R> {
|
||||
impl<Segments, Data, View> NestedRoute<Segments, (), Data, View> {
|
||||
pub fn child<Children>(
|
||||
self,
|
||||
child: Children,
|
||||
) -> NestedRoute<Segments, Children, Data, View, R> {
|
||||
) -> NestedRoute<Segments, Children, Data, View> {
|
||||
let Self {
|
||||
id,
|
||||
segments,
|
||||
data,
|
||||
view,
|
||||
rndr,
|
||||
ssr_mode,
|
||||
methods,
|
||||
..
|
||||
@@ -96,7 +87,6 @@ impl<Segments, Data, View, R> NestedRoute<Segments, (), Data, View, R> {
|
||||
view,
|
||||
ssr_mode,
|
||||
methods,
|
||||
rndr,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,13 +136,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<ParamsIter, Child, View, Rndr> MatchInterface<Rndr>
|
||||
impl<ParamsIter, Child, View> MatchInterface
|
||||
for NestedMatch<ParamsIter, Child, View>
|
||||
where
|
||||
Rndr: Renderer + 'static,
|
||||
Child: MatchInterface<Rndr> + MatchParams + 'static,
|
||||
View: ChooseView<Rndr>,
|
||||
View::Output: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
|
||||
Child: MatchInterface + MatchParams + 'static,
|
||||
View: ChooseView,
|
||||
View::Output: Render + RenderHtml + Send + 'static,
|
||||
{
|
||||
type Child = Child;
|
||||
type View = View::Output;
|
||||
@@ -167,28 +156,24 @@ where
|
||||
|
||||
fn into_view_and_child(
|
||||
self,
|
||||
) -> (
|
||||
impl ChooseView<Rndr, Output = Self::View>,
|
||||
Option<Self::Child>,
|
||||
) {
|
||||
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>) {
|
||||
(self.view_fn, self.child)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Segments, Children, Data, View, Rndr> MatchNestedRoutes<Rndr>
|
||||
for NestedRoute<Segments, Children, Data, View, Rndr>
|
||||
impl<Segments, Children, Data, View> MatchNestedRoutes
|
||||
for NestedRoute<Segments, Children, Data, View>
|
||||
where
|
||||
Self: 'static,
|
||||
Rndr: Renderer + 'static,
|
||||
Segments: PossibleRouteMatch + std::fmt::Debug,
|
||||
<<Segments as PossibleRouteMatch>::ParamsIter as IntoIterator>::IntoIter: Clone,
|
||||
Children: MatchNestedRoutes<Rndr>,
|
||||
<<<Children as MatchNestedRoutes<Rndr>>::Match as MatchParams>::Params as IntoIterator>::IntoIter: Clone,
|
||||
Children: MatchNestedRoutes,
|
||||
<<<Children as MatchNestedRoutes>::Match as MatchParams>::Params as IntoIterator>::IntoIter: Clone,
|
||||
Children::Match: MatchParams,
|
||||
Children: 'static,
|
||||
<Children::Match as MatchParams>::Params: Clone,
|
||||
View: ChooseView<Rndr> + Clone,
|
||||
View::Output: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
|
||||
View: ChooseView + Clone,
|
||||
View::Output: Render + RenderHtml + Send + 'static,
|
||||
{
|
||||
type Data = Data;
|
||||
type View = View::Output;
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::{ChooseView, GeneratedRouteData, MatchParams};
|
||||
use core::iter;
|
||||
use either_of::*;
|
||||
use std::borrow::Cow;
|
||||
use tachys::renderer::Renderer;
|
||||
|
||||
impl MatchParams for () {
|
||||
type Params = iter::Empty<(Cow<'static, str>, String)>;
|
||||
@@ -13,10 +12,7 @@ impl MatchParams for () {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rndr> MatchInterface<Rndr> for ()
|
||||
where
|
||||
Rndr: Renderer + 'static,
|
||||
{
|
||||
impl MatchInterface for () {
|
||||
type Child = ();
|
||||
type View = ();
|
||||
|
||||
@@ -30,18 +26,12 @@ where
|
||||
|
||||
fn into_view_and_child(
|
||||
self,
|
||||
) -> (
|
||||
impl ChooseView<Rndr, Output = Self::View>,
|
||||
Option<Self::Child>,
|
||||
) {
|
||||
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>) {
|
||||
((), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rndr> MatchNestedRoutes<Rndr> for ()
|
||||
where
|
||||
Rndr: Renderer + 'static,
|
||||
{
|
||||
impl MatchNestedRoutes for () {
|
||||
type Data = ();
|
||||
type View = ();
|
||||
type Match = ();
|
||||
@@ -74,10 +64,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, Rndr> MatchInterface<Rndr> for (A,)
|
||||
impl<A> MatchInterface for (A,)
|
||||
where
|
||||
A: MatchInterface<Rndr> + 'static,
|
||||
Rndr: Renderer + 'static,
|
||||
A: MatchInterface + 'static,
|
||||
{
|
||||
type Child = A::Child;
|
||||
type View = A::View;
|
||||
@@ -92,18 +81,14 @@ where
|
||||
|
||||
fn into_view_and_child(
|
||||
self,
|
||||
) -> (
|
||||
impl ChooseView<Rndr, Output = Self::View>,
|
||||
Option<Self::Child>,
|
||||
) {
|
||||
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>) {
|
||||
self.0.into_view_and_child()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, Rndr> MatchNestedRoutes<Rndr> for (A,)
|
||||
impl<A> MatchNestedRoutes for (A,)
|
||||
where
|
||||
A: MatchNestedRoutes<Rndr> + 'static,
|
||||
Rndr: Renderer + 'static,
|
||||
A: MatchNestedRoutes + 'static,
|
||||
{
|
||||
type Data = A::Data;
|
||||
type View = A::View;
|
||||
@@ -141,11 +126,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Rndr> MatchInterface<Rndr> for Either<A, B>
|
||||
impl<A, B> MatchInterface for Either<A, B>
|
||||
where
|
||||
Rndr: Renderer + 'static,
|
||||
A: MatchInterface<Rndr>,
|
||||
B: MatchInterface<Rndr>,
|
||||
A: MatchInterface,
|
||||
B: MatchInterface,
|
||||
{
|
||||
type Child = Either<A::Child, B::Child>;
|
||||
type View = Either<A::View, B::View>;
|
||||
@@ -166,10 +150,7 @@ where
|
||||
|
||||
fn into_view_and_child(
|
||||
self,
|
||||
) -> (
|
||||
impl ChooseView<Rndr, Output = Self::View>,
|
||||
Option<Self::Child>,
|
||||
) {
|
||||
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>) {
|
||||
match self {
|
||||
Either::Left(i) => {
|
||||
let (view, child) = i.into_view_and_child();
|
||||
@@ -183,11 +164,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, Rndr> MatchNestedRoutes<Rndr> for (A, B)
|
||||
impl<A, B> MatchNestedRoutes for (A, B)
|
||||
where
|
||||
A: MatchNestedRoutes<Rndr>,
|
||||
B: MatchNestedRoutes<Rndr>,
|
||||
Rndr: Renderer + 'static,
|
||||
A: MatchNestedRoutes,
|
||||
B: MatchNestedRoutes,
|
||||
{
|
||||
type Data = (A::Data, B::Data);
|
||||
type View = Either<A::View, B::View>;
|
||||
@@ -251,10 +231,9 @@ macro_rules! tuples {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rndr, $($ty,)*> MatchInterface<Rndr> for $either <$($ty,)*>
|
||||
impl<$($ty,)*> MatchInterface for $either <$($ty,)*>
|
||||
where
|
||||
Rndr: Renderer + 'static,
|
||||
$($ty: MatchInterface<Rndr> + 'static),*,
|
||||
$($ty: MatchInterface + 'static),*,
|
||||
{
|
||||
type Child = $either<$($ty::Child,)*>;
|
||||
type View = $either<$($ty::View,)*>;
|
||||
@@ -274,7 +253,7 @@ macro_rules! tuples {
|
||||
fn into_view_and_child(
|
||||
self,
|
||||
) -> (
|
||||
impl ChooseView<Rndr, Output = Self::View>,
|
||||
impl ChooseView<Output = Self::View>,
|
||||
Option<Self::Child>,
|
||||
) {
|
||||
match self {
|
||||
@@ -286,10 +265,9 @@ macro_rules! tuples {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rndr, $($ty),*> MatchNestedRoutes<Rndr> for ($($ty,)*)
|
||||
impl<$($ty),*> MatchNestedRoutes for ($($ty,)*)
|
||||
where
|
||||
Rndr: Renderer + 'static,
|
||||
$($ty: MatchNestedRoutes<Rndr> + 'static),*,
|
||||
$($ty: MatchNestedRoutes + 'static),*,
|
||||
{
|
||||
type Data = ($($ty::Data,)*);
|
||||
type View = $either<$($ty::View,)*>;
|
||||
|
||||
@@ -23,9 +23,7 @@ use std::{
|
||||
cell::RefCell,
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
iter,
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
iter, mem,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
@@ -33,7 +31,6 @@ use std::{
|
||||
use tachys::{
|
||||
hydration::Cursor,
|
||||
reactive_graph::{OwnedView, Suspend},
|
||||
renderer::Renderer,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr,
|
||||
@@ -43,42 +40,38 @@ use tachys::{
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) struct NestedRoutesView<Loc, Defs, FalFn, R> {
|
||||
pub(crate) struct NestedRoutesView<Loc, Defs, FalFn> {
|
||||
pub location: Option<Loc>,
|
||||
pub routes: Routes<Defs, R>,
|
||||
pub routes: Routes<Defs>,
|
||||
pub outer_owner: Owner,
|
||||
pub current_url: ArcRwSignal<Url>,
|
||||
pub base: Option<Oco<'static, str>>,
|
||||
pub fallback: FalFn,
|
||||
#[allow(unused)] // TODO
|
||||
pub set_is_routing: Option<SignalSetter<bool>>,
|
||||
pub rndr: PhantomData<R>,
|
||||
}
|
||||
|
||||
pub struct NestedRouteViewState<Fal, R>
|
||||
pub struct NestedRouteViewState<Fal>
|
||||
where
|
||||
Fal: Render<R>,
|
||||
R: Renderer + 'static,
|
||||
Fal: Render,
|
||||
{
|
||||
path: String,
|
||||
current_url: ArcRwSignal<Url>,
|
||||
outlets: Vec<RouteContext<R>>,
|
||||
outlets: Vec<RouteContext>,
|
||||
// TODO loading fallback
|
||||
#[allow(clippy::type_complexity)]
|
||||
view: Rc<RefCell<EitherOf3State<(), Fal, AnyView<R>, R>>>,
|
||||
view: Rc<RefCell<EitherOf3State<(), Fal, AnyView>>>,
|
||||
}
|
||||
|
||||
impl<Loc, Defs, FalFn, Fal, R> Render<R>
|
||||
for NestedRoutesView<Loc, Defs, FalFn, R>
|
||||
impl<Loc, Defs, FalFn, Fal> Render for NestedRoutesView<Loc, Defs, FalFn>
|
||||
where
|
||||
Loc: LocationProvider,
|
||||
Defs: MatchNestedRoutes<R>,
|
||||
Defs: MatchNestedRoutes,
|
||||
FalFn: FnOnce() -> Fal,
|
||||
Fal: Render<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
Fal: Render + 'static,
|
||||
{
|
||||
// TODO support fallback while loading
|
||||
type State = NestedRouteViewState<Fal, R>;
|
||||
type State = NestedRouteViewState<Fal>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let NestedRoutesView {
|
||||
@@ -112,11 +105,7 @@ where
|
||||
&outer_owner,
|
||||
);
|
||||
drop(url);
|
||||
outer_owner.with(|| {
|
||||
EitherOf3::C(
|
||||
Outlet(OutletProps::builder().build()).into_any(),
|
||||
)
|
||||
})
|
||||
outer_owner.with(|| EitherOf3::C(Outlet().into_any()))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -161,7 +150,7 @@ where
|
||||
|
||||
match new_match {
|
||||
None => {
|
||||
EitherOf3::<(), Fal, AnyView<R>>::B((self.fallback)())
|
||||
EitherOf3::<(), Fal, AnyView>::B((self.fallback)())
|
||||
.rebuild(&mut state.view.borrow_mut());
|
||||
state.outlets.clear();
|
||||
}
|
||||
@@ -191,10 +180,8 @@ where
|
||||
// if it was on the fallback, show the view instead
|
||||
if matches!(state.view.borrow().state, EitherOf3::B(_)) {
|
||||
self.outer_owner.with(|| {
|
||||
EitherOf3::<(), Fal, AnyView<R>>::C(
|
||||
Outlet(OutletProps::builder().build()).into_any(),
|
||||
)
|
||||
.rebuild(&mut *state.view.borrow_mut());
|
||||
EitherOf3::<(), Fal, AnyView>::C(Outlet().into_any())
|
||||
.rebuild(&mut *state.view.borrow_mut());
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -206,37 +193,33 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Loc, Defs, Fal, FalFn, R> AddAnyAttr<R>
|
||||
for NestedRoutesView<Loc, Defs, FalFn, R>
|
||||
impl<Loc, Defs, Fal, FalFn> AddAnyAttr for NestedRoutesView<Loc, Defs, FalFn>
|
||||
where
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes<R> + Send,
|
||||
Defs: MatchNestedRoutes + Send,
|
||||
FalFn: FnOnce() -> Fal + Send,
|
||||
Fal: RenderHtml<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
Fal: RenderHtml + 'static,
|
||||
{
|
||||
type Output<SomeNewAttr: leptos::attr::Attribute<R>> =
|
||||
NestedRoutesView<Loc, Defs, FalFn, R>;
|
||||
type Output<SomeNewAttr: leptos::attr::Attribute> =
|
||||
NestedRoutesView<Loc, Defs, FalFn>;
|
||||
|
||||
fn add_any_attr<NewAttr: leptos::attr::Attribute<R>>(
|
||||
fn add_any_attr<NewAttr: leptos::attr::Attribute>(
|
||||
self,
|
||||
_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<R>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Loc, Defs, FalFn, Fal, R> RenderHtml<R>
|
||||
for NestedRoutesView<Loc, Defs, FalFn, R>
|
||||
impl<Loc, Defs, FalFn, Fal> RenderHtml for NestedRoutesView<Loc, Defs, FalFn>
|
||||
where
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes<R> + Send,
|
||||
Defs: MatchNestedRoutes + Send,
|
||||
FalFn: FnOnce() -> Fal + Send,
|
||||
Fal: RenderHtml<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
Fal: RenderHtml + 'static,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
@@ -330,11 +313,7 @@ where
|
||||
.now_or_never()
|
||||
.expect("async routes not supported in SSR");
|
||||
|
||||
outer_owner.with(|| {
|
||||
Either::Right(
|
||||
Outlet(OutletProps::builder().build()).into_any(),
|
||||
)
|
||||
})
|
||||
outer_owner.with(|| Either::Right(Outlet().into_any()))
|
||||
}
|
||||
};
|
||||
view.to_html_with_buf(buf, position, escape, mark_branches);
|
||||
@@ -381,11 +360,7 @@ where
|
||||
.now_or_never()
|
||||
.expect("async routes not supported in SSR");
|
||||
|
||||
outer_owner.with(|| {
|
||||
Either::Right(
|
||||
Outlet(OutletProps::builder().build()).into_any(),
|
||||
)
|
||||
})
|
||||
outer_owner.with(|| Either::Right(Outlet().into_any()))
|
||||
}
|
||||
};
|
||||
view.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||
@@ -398,7 +373,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<R>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let NestedRoutesView {
|
||||
@@ -436,11 +411,7 @@ where
|
||||
join_all(mem::take(&mut loaders))
|
||||
.now_or_never()
|
||||
.expect("async routes not supported in SSR");
|
||||
outer_owner.with(|| {
|
||||
EitherOf3::C(
|
||||
Outlet(OutletProps::builder().build()).into_any(),
|
||||
)
|
||||
})
|
||||
outer_owner.with(|| EitherOf3::C(Outlet().into_any()))
|
||||
}
|
||||
}
|
||||
.hydrate::<FROM_SERVER>(cursor, position),
|
||||
@@ -455,15 +426,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
type OutletViewFn<R> = Box<
|
||||
dyn Fn() -> Suspend<Pin<Box<dyn Future<Output = AnyView<R>> + Send>>>
|
||||
+ Send,
|
||||
type OutletViewFn = Box<
|
||||
dyn Fn() -> Suspend<Pin<Box<dyn Future<Output = AnyView> + Send>>> + Send,
|
||||
>;
|
||||
|
||||
pub(crate) struct RouteContext<R>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
pub(crate) struct RouteContext {
|
||||
id: RouteMatchId,
|
||||
trigger: ArcTrigger,
|
||||
url: ArcRwSignal<Url>,
|
||||
@@ -471,10 +438,10 @@ where
|
||||
owner: Owner,
|
||||
pub matched: ArcRwSignal<String>,
|
||||
base: Option<Oco<'static, str>>,
|
||||
view_fn: Arc<Mutex<OutletViewFn<R>>>,
|
||||
view_fn: Arc<Mutex<OutletViewFn>>,
|
||||
}
|
||||
|
||||
impl<R: Renderer> Debug for RouteContext<R> {
|
||||
impl Debug for RouteContext {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RouteContext")
|
||||
.field("id", &self.id)
|
||||
@@ -488,19 +455,13 @@ impl<R: Renderer> Debug for RouteContext<R> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RouteContext<R>
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
impl RouteContext {
|
||||
fn provide_contexts(&self) {
|
||||
provide_context(self.clone());
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Clone for RouteContext<R>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
impl Clone for RouteContext {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
url: self.url.clone(),
|
||||
@@ -515,16 +476,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
trait AddNestedRoute<R>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
trait AddNestedRoute {
|
||||
fn build_nested_route(
|
||||
self,
|
||||
url: &Url,
|
||||
base: Option<Oco<'static, str>>,
|
||||
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||
outlets: &mut Vec<RouteContext<R>>,
|
||||
outlets: &mut Vec<RouteContext>,
|
||||
parent: &Owner,
|
||||
);
|
||||
|
||||
@@ -534,22 +492,21 @@ where
|
||||
base: Option<Oco<'static, str>>,
|
||||
items: &mut usize,
|
||||
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||
outlets: &mut Vec<RouteContext<R>>,
|
||||
outlets: &mut Vec<RouteContext>,
|
||||
parent: &Owner,
|
||||
);
|
||||
}
|
||||
|
||||
impl<Match, R> AddNestedRoute<R> for Match
|
||||
impl<Match> AddNestedRoute for Match
|
||||
where
|
||||
Match: MatchInterface<R> + MatchParams,
|
||||
R: Renderer + 'static,
|
||||
Match: MatchInterface + MatchParams,
|
||||
{
|
||||
fn build_nested_route(
|
||||
self,
|
||||
url: &Url,
|
||||
base: Option<Oco<'static, str>>,
|
||||
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||
outlets: &mut Vec<RouteContext<R>>,
|
||||
outlets: &mut Vec<RouteContext>,
|
||||
parent: &Owner,
|
||||
) {
|
||||
let orig_url = url;
|
||||
@@ -650,7 +607,7 @@ where
|
||||
OwnedView::new(view).into_any()
|
||||
})
|
||||
as Pin<
|
||||
Box<dyn Future<Output = AnyView<R>> + Send>,
|
||||
Box<dyn Future<Output = AnyView> + Send>,
|
||||
>)
|
||||
})
|
||||
});
|
||||
@@ -678,7 +635,7 @@ where
|
||||
base: Option<Oco<'static, str>>,
|
||||
items: &mut usize,
|
||||
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||
outlets: &mut Vec<RouteContext<R>>,
|
||||
outlets: &mut Vec<RouteContext>,
|
||||
parent: &Owner,
|
||||
) {
|
||||
let (parent_params, parent_matches): (Vec<_>, Vec<_>) = outlets
|
||||
@@ -833,32 +790,33 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fal, R> Mountable<R> for NestedRouteViewState<Fal, R>
|
||||
impl<Fal> Mountable for NestedRouteViewState<Fal>
|
||||
where
|
||||
Fal: Render<R>,
|
||||
R: Renderer,
|
||||
Fal: Render,
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
self.view.unmount();
|
||||
}
|
||||
|
||||
fn mount(&mut self, parent: &R::Element, marker: Option<&R::Node>) {
|
||||
fn mount(
|
||||
&mut self,
|
||||
parent: &leptos::tachys::renderer::types::Element,
|
||||
marker: Option<&leptos::tachys::renderer::types::Node>,
|
||||
) {
|
||||
self.view.mount(parent, marker);
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<R>) -> bool {
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
|
||||
self.view.insert_before_this(child)
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Outlet<R>(#[prop(optional)] rndr: PhantomData<R>) -> impl RenderHtml<R>
|
||||
pub fn Outlet() -> impl RenderHtml
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
_ = rndr;
|
||||
move || {
|
||||
let ctx = use_context::<RouteContext<R>>()
|
||||
let ctx = use_context::<RouteContext>()
|
||||
.expect("<Outlet/> used without RouteContext being provided.");
|
||||
let RouteContext {
|
||||
trigger, view_fn, ..
|
||||
|
||||
@@ -26,7 +26,7 @@ pub fn ReactiveRouter<Rndr, Loc, DefFn, Defs, FallbackFn, Fallback>(
|
||||
mut location: Loc,
|
||||
routes: DefFn,
|
||||
fallback: FallbackFn,
|
||||
) -> impl RenderHtml<Rndr>
|
||||
) -> impl RenderHtml
|
||||
where
|
||||
DefFn: Fn() -> Defs + 'static,
|
||||
Defs: 'static,
|
||||
@@ -35,10 +35,9 @@ where
|
||||
Rndr::Element: Clone,
|
||||
Rndr::Node: Clone,
|
||||
FallbackFn: Fn() -> Fallback + Clone + 'static,
|
||||
Fallback: Render<Rndr> + 'static,
|
||||
Fallback: Render + 'static,
|
||||
Router<Rndr, Loc, Defs, FallbackFn>: FallbackOrView,
|
||||
<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output:
|
||||
RenderHtml<Rndr>,
|
||||
<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output: RenderHtml,
|
||||
{
|
||||
// create a reactive URL signal that will drive the router view
|
||||
let url = ArcRwSignal::new(location.try_to_url().unwrap_or_default());
|
||||
@@ -75,7 +74,7 @@ where
|
||||
fal: PhantomData<Fallback>,
|
||||
}
|
||||
|
||||
impl<Rndr, Loc, Defs, FallbackFn, Fallback> Render<Rndr>
|
||||
impl<Rndr, Loc, Defs, FallbackFn, Fallback> Render
|
||||
for ReactiveRouterInner<Rndr, Loc, Defs, FallbackFn, Fallback>
|
||||
where
|
||||
Loc: Location,
|
||||
@@ -83,10 +82,9 @@ where
|
||||
Rndr::Element: Clone,
|
||||
Rndr::Node: Clone,
|
||||
FallbackFn: Fn() -> Fallback,
|
||||
Fallback: Render<Rndr>,
|
||||
Fallback: Render,
|
||||
Router<Rndr, Loc, Defs, FallbackFn>: FallbackOrView,
|
||||
<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output:
|
||||
Render<Rndr>,
|
||||
<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output: Render,
|
||||
{
|
||||
type State =
|
||||
ReactiveRouterInnerState<Rndr, Loc, Defs, FallbackFn, Fallback>;
|
||||
@@ -112,7 +110,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rndr, Loc, Defs, FallbackFn, Fallback> RenderHtml<Rndr>
|
||||
impl<Rndr, Loc, Defs, FallbackFn, Fallback> RenderHtml
|
||||
for ReactiveRouterInner<Rndr, Loc, Defs, FallbackFn, Fallback>
|
||||
where
|
||||
Loc: Location,
|
||||
@@ -120,18 +118,18 @@ where
|
||||
Rndr::Element: Clone,
|
||||
Rndr::Node: Clone,
|
||||
FallbackFn: Fn() -> Fallback,
|
||||
Fallback: Render<Rndr>,
|
||||
Fallback: Render,
|
||||
Router<Rndr, Loc, Defs, FallbackFn>: FallbackOrView,
|
||||
<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output:
|
||||
RenderHtml<Rndr>,
|
||||
<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output: RenderHtml,
|
||||
{
|
||||
const MIN_LENGTH: usize = <<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output as RenderHtml<Rndr>>::MIN_LENGTH;
|
||||
const MIN_LENGTH: usize = <<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output as RenderHtml>::MIN_LENGTH;
|
||||
|
||||
fn to_html_with_buf(
|
||||
self,
|
||||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool, mark_branches: bool
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
// if this is being run on the server for the first time, generating all possible routes
|
||||
if RouteList::is_generating() {
|
||||
@@ -156,7 +154,8 @@ where
|
||||
self,
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool, mark_branches: bool
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -168,7 +167,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Rndr>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let (prev_id, inner) = self.inner.fallback_or_view();
|
||||
@@ -186,21 +185,20 @@ where
|
||||
struct ReactiveRouterInnerState<Rndr, Loc, Defs, FallbackFn, Fallback>
|
||||
where
|
||||
Router<Rndr, Loc, Defs, FallbackFn>: FallbackOrView,
|
||||
<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output: Render<Rndr>,
|
||||
<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output: Render,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
owner: Owner,
|
||||
prev_id: &'static str,
|
||||
inner: <<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output as Render<Rndr>>::State,
|
||||
inner: <<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output as Render>::State,
|
||||
fal: PhantomData<Fallback>,
|
||||
}
|
||||
|
||||
impl<Rndr, Loc, Defs, FallbackFn, Fallback> Mountable<Rndr>
|
||||
impl<Rndr, Loc, Defs, FallbackFn, Fallback> Mountable
|
||||
for ReactiveRouterInnerState<Rndr, Loc, Defs, FallbackFn, Fallback>
|
||||
where
|
||||
Router<Rndr, Loc, Defs, FallbackFn>: FallbackOrView,
|
||||
<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output:
|
||||
Render<Rndr>,
|
||||
<Router<Rndr, Loc, Defs, FallbackFn> as FallbackOrView>::Output: Render,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
@@ -209,13 +207,13 @@ where
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
parent: &<Rndr as Renderer>::Element,
|
||||
marker: Option<&<Rndr as Renderer>::Node>,
|
||||
parent: &leptos::tachys::renderer::types::Element,
|
||||
marker: Option<&leptos::tachys::renderer::types::Node>,
|
||||
) {
|
||||
self.inner.mount(parent, marker);
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<Rndr>) -> bool {
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
|
||||
self.inner.insert_before_this(child)
|
||||
}
|
||||
}
|
||||
@@ -248,12 +246,12 @@ impl ReactiveMatchedRoute {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reactive_route<ViewFn, View, Rndr>(
|
||||
pub fn reactive_route<ViewFn, View>(
|
||||
view_fn: ViewFn,
|
||||
) -> impl Fn(MatchedRoute) -> ReactiveRoute<ViewFn, View, Rndr>
|
||||
) -> impl Fn(MatchedRoute) -> ReactiveRoute<ViewFn, View>
|
||||
where
|
||||
ViewFn: Fn(&ReactiveMatchedRoute) -> View + Clone,
|
||||
View: Render<Rndr>,
|
||||
View: Render,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
move |matched| ReactiveRoute {
|
||||
@@ -263,21 +261,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReactiveRoute<ViewFn, View, Rndr>
|
||||
pub struct ReactiveRoute<ViewFn, View>
|
||||
where
|
||||
ViewFn: Fn(&ReactiveMatchedRoute) -> View,
|
||||
View: Render<Rndr>,
|
||||
View: Render,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
view_fn: ViewFn,
|
||||
matched: MatchedRoute,
|
||||
ty: PhantomData<Rndr>,
|
||||
ty: PhantomData,
|
||||
}
|
||||
|
||||
impl<ViewFn, View, Rndr> Render<Rndr> for ReactiveRoute<ViewFn, View, Rndr>
|
||||
impl<ViewFn, View> Render for ReactiveRoute<ViewFn, View>
|
||||
where
|
||||
ViewFn: Fn(&ReactiveMatchedRoute) -> View,
|
||||
View: Render<Rndr>,
|
||||
View: Render,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type State = ReactiveRouteState<View::State>;
|
||||
@@ -310,10 +308,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<ViewFn, View, Rndr> RenderHtml<Rndr> for ReactiveRoute<ViewFn, View, Rndr>
|
||||
impl<ViewFn, View> RenderHtml for ReactiveRoute<ViewFn, View>
|
||||
where
|
||||
ViewFn: Fn(&ReactiveMatchedRoute) -> View,
|
||||
View: RenderHtml<Rndr>,
|
||||
View: RenderHtml,
|
||||
Rndr: Renderer,
|
||||
Rndr::Node: Clone,
|
||||
Rndr::Element: Clone,
|
||||
@@ -324,7 +322,8 @@ where
|
||||
self,
|
||||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
escape: bool, mark_branches: bool
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) {
|
||||
let MatchedRoute {
|
||||
search_params,
|
||||
@@ -345,7 +344,8 @@ where
|
||||
self,
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool, mark_branches: bool
|
||||
escape: bool,
|
||||
mark_branches: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -367,7 +367,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Rndr>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let MatchedRoute {
|
||||
@@ -401,10 +401,9 @@ impl<State> Drop for ReactiveRouteState<State> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> Mountable<R> for ReactiveRouteState<T>
|
||||
impl<T> Mountable for ReactiveRouteState<T>
|
||||
where
|
||||
T: Mountable<R>,
|
||||
R: Renderer,
|
||||
T: Mountable,
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
self.view_state.unmount();
|
||||
@@ -418,7 +417,7 @@ where
|
||||
self.view_state.mount(parent, marker);
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<R>) -> bool {
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
|
||||
self.view_state.insert_before_this(child)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ use tachys::{
|
||||
pub struct Router<Rndr, Loc, Children, FallbackFn> {
|
||||
base: Option<Cow<'static, str>>,
|
||||
location: PhantomData<Loc>,
|
||||
pub routes: Routes<Children, Rndr>,
|
||||
pub routes: Routes<Children>,
|
||||
fallback: FallbackFn,
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ where
|
||||
FallbackFn: Fn() -> Fallback,
|
||||
{
|
||||
pub fn new(
|
||||
routes: Routes<Children, Rndr>,
|
||||
routes: Routes<Children>,
|
||||
fallback: FallbackFn,
|
||||
) -> Router<Rndr, Loc, Children, FallbackFn> {
|
||||
Self {
|
||||
@@ -73,7 +73,7 @@ where
|
||||
|
||||
pub fn new_with_base(
|
||||
base: impl Into<Cow<'static, str>>,
|
||||
routes: Routes<Children, Rndr>,
|
||||
routes: Routes<Children>,
|
||||
fallback: FallbackFn,
|
||||
) -> Router<Rndr, Loc, Children, FallbackFn> {
|
||||
Self {
|
||||
@@ -98,28 +98,28 @@ where
|
||||
|
||||
pub struct
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
|
||||
{
|
||||
pub params: ArcMemo<Params>,
|
||||
pub outlet: Outlet<R>,
|
||||
pub outlet: Outlet,
|
||||
}
|
||||
|
||||
impl<Rndr, Loc, FallbackFn, Fallback, Children> Render<Rndr>
|
||||
impl<Rndr, Loc, FallbackFn, Fallback, Children> Render
|
||||
for Router<Rndr, Loc, Children, FallbackFn>
|
||||
where
|
||||
Loc: Location,
|
||||
FallbackFn: Fn() -> Fallback + 'static,
|
||||
Fallback: Render<Rndr>,
|
||||
Children: MatchNestedRoutes<Rndr> + 'static,
|
||||
Fallback: Render,
|
||||
Children: MatchNestedRoutes + 'static,
|
||||
Fallback::State: 'static,
|
||||
Rndr: Renderer + 'static,
|
||||
Children::Match: std::fmt::Debug,
|
||||
<Children::Match as MatchInterface<Rndr>>::Child: std::fmt::Debug,
|
||||
<Children::Match as MatchInterface>::Child: std::fmt::Debug,
|
||||
{
|
||||
type State = RenderEffect<
|
||||
EitherState<
|
||||
<NestedRouteView<Children::Match, Rndr> as Render<Rndr>>::State,
|
||||
<Fallback as Render<Rndr>>::State,
|
||||
<NestedRouteView<Children::Match> as Render>::State,
|
||||
<Fallback as Render>::State,
|
||||
Rndr,
|
||||
>,
|
||||
>;
|
||||
@@ -158,7 +158,7 @@ where
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Either::<NestedRouteView<Children::Match, Rndr>, _>::Right(
|
||||
Either::<NestedRouteView<Children::Match>, _>::Right(
|
||||
(self.fallback)(),
|
||||
)
|
||||
.rebuild(&mut prev);
|
||||
@@ -180,21 +180,21 @@ where
|
||||
fn rebuild(self, state: &mut Self::State) {}
|
||||
}
|
||||
|
||||
impl<Rndr, Loc, FallbackFn, Fallback, Children> RenderHtml<Rndr>
|
||||
impl<Rndr, Loc, FallbackFn, Fallback, Children> RenderHtml
|
||||
for Router<Rndr, Loc, Children, FallbackFn>
|
||||
where
|
||||
Loc: Location + Send,
|
||||
FallbackFn: Fn() -> Fallback + Send + 'static,
|
||||
Fallback: RenderHtml<Rndr>,
|
||||
Children: MatchNestedRoutes<Rndr> + Send + 'static,
|
||||
Children::View: RenderHtml<Rndr>,
|
||||
/*View: Render<Rndr> + IntoAny<Rndr> + 'static,
|
||||
Fallback: RenderHtml,
|
||||
Children: MatchNestedRoutes + Send + 'static,
|
||||
Children::View: RenderHtml,
|
||||
/*View: Render + IntoAny + 'static,
|
||||
View::State: 'static,*/
|
||||
Fallback: RenderHtml<Rndr>,
|
||||
Fallback: RenderHtml,
|
||||
Fallback::State: 'static,
|
||||
Rndr: Renderer + 'static,
|
||||
Children::Match: std::fmt::Debug,
|
||||
<Children::Match as MatchInterface<Rndr>>::Child: std::fmt::Debug,
|
||||
<Children::Match as MatchInterface>::Child: std::fmt::Debug,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
@@ -294,7 +294,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Rndr>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let location = Loc::new().unwrap(); // TODO
|
||||
@@ -332,7 +332,7 @@ where
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Either::<NestedRouteView<Children::Match, Rndr>, _>::Right(
|
||||
Either::<NestedRouteView<Children::Match>, _>::Right(
|
||||
(self.fallback)(),
|
||||
)
|
||||
.rebuild(&mut prev);
|
||||
@@ -359,20 +359,20 @@ where
|
||||
|
||||
pub struct NestedRouteView<Matcher, R>
|
||||
where
|
||||
Matcher: MatchInterface<R>,
|
||||
R: Renderer + 'static,
|
||||
Matcher: MatchInterface,
|
||||
|
||||
{
|
||||
id: RouteMatchId,
|
||||
owner: Owner,
|
||||
params: ArcRwSignal<Params>,
|
||||
outlets: VecDeque<Outlet<R>>,
|
||||
outlets: VecDeque<Outlet>,
|
||||
view: Matcher::View,
|
||||
ty: PhantomData<(Matcher, R)>,
|
||||
}
|
||||
|
||||
impl<Matcher, Rndr> NestedRouteView<Matcher, Rndr>
|
||||
impl<Matcher> NestedRouteView<Matcher>
|
||||
where
|
||||
Matcher: MatchInterface<Rndr> + MatchParams,
|
||||
Matcher: MatchInterface + MatchParams,
|
||||
Matcher::Child: 'static,
|
||||
Matcher::View: 'static,
|
||||
Rndr: Renderer + 'static,
|
||||
@@ -414,7 +414,7 @@ where
|
||||
pub fn new_hydrate(
|
||||
outer_owner: &Owner,
|
||||
route_match: Matcher,
|
||||
cursor: &Cursor<Rndr>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self {
|
||||
// keep track of all outlets, for diffing
|
||||
@@ -459,26 +459,26 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NestedRouteState<Matcher, Rndr>
|
||||
pub struct NestedRouteState<Matcher>
|
||||
where
|
||||
Matcher: MatchInterface<Rndr>,
|
||||
Matcher: MatchInterface,
|
||||
Rndr: Renderer + 'static,
|
||||
{
|
||||
id: RouteMatchId,
|
||||
owner: Owner,
|
||||
params: ArcRwSignal<Params>,
|
||||
view: <Matcher::View as Render<Rndr>>::State,
|
||||
outlets: VecDeque<Outlet<Rndr>>,
|
||||
view: <Matcher::View as Render>::State,
|
||||
outlets: VecDeque<Outlet>,
|
||||
}
|
||||
|
||||
fn get_inner_view<Match, R>(
|
||||
outlets: &mut VecDeque<Outlet<R>>,
|
||||
outlets: &mut VecDeque<Outlet>,
|
||||
parent: &Owner,
|
||||
route_match: Match,
|
||||
) -> Outlet<R>
|
||||
) -> Outlet
|
||||
where
|
||||
Match: MatchInterface<R> + MatchParams,
|
||||
R: Renderer + 'static,
|
||||
Match: MatchInterface + MatchParams,
|
||||
|
||||
{
|
||||
let owner = parent.child();
|
||||
let id = route_match.as_id();
|
||||
@@ -503,7 +503,7 @@ where
|
||||
})
|
||||
.into_any(),
|
||||
))
|
||||
}) as Box<dyn FnOnce() -> RwLock<Option<AnyView<R>>>>
|
||||
}) as Box<dyn FnOnce() -> RwLock<Option<AnyView>>>
|
||||
}));
|
||||
let inner = Arc::new(RwLock::new(OutletStateInner {
|
||||
html_len: {
|
||||
@@ -525,15 +525,15 @@ where
|
||||
}
|
||||
|
||||
fn get_inner_view_hydrate<Match, R>(
|
||||
outlets: &mut VecDeque<Outlet<R>>,
|
||||
outlets: &mut VecDeque<Outlet>,
|
||||
parent: &Owner,
|
||||
route_match: Match,
|
||||
cursor: &Cursor<R>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Outlet<R>
|
||||
) -> Outlet
|
||||
where
|
||||
Match: MatchInterface<R> + MatchParams,
|
||||
R: Renderer + 'static,
|
||||
Match: MatchInterface + MatchParams,
|
||||
|
||||
{
|
||||
let owner = parent.child();
|
||||
let id = route_match.as_id();
|
||||
@@ -558,7 +558,7 @@ where
|
||||
})
|
||||
.into_any(),
|
||||
))
|
||||
}) as Box<dyn FnOnce() -> RwLock<Option<AnyView<R>>>>
|
||||
}) as Box<dyn FnOnce() -> RwLock<Option<AnyView>>>
|
||||
}));
|
||||
let inner = Arc::new(RwLock::new(OutletStateInner {
|
||||
html_len: Box::new({
|
||||
@@ -585,18 +585,18 @@ where
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Outlet<R>
|
||||
pub struct Outlet
|
||||
where
|
||||
R: Renderer + Send + 'static,
|
||||
{
|
||||
id: RouteMatchId,
|
||||
owner: Owner,
|
||||
params: ArcRwSignal<Params>,
|
||||
rndr: PhantomData<R>,
|
||||
inner: OutletInner<R>,
|
||||
rndr: PhantomData,
|
||||
inner: OutletInner,
|
||||
}
|
||||
|
||||
pub enum OutletInner<R> where R: Renderer + 'static {
|
||||
pub enum OutletInner where R: Renderer + 'static {
|
||||
Server {
|
||||
html_len: Box<dyn Fn() -> usize + Send + Sync>,
|
||||
view: Box<
|
||||
@@ -608,30 +608,30 @@ pub enum OutletInner<R> where R: Renderer + 'static {
|
||||
html_len: Box<dyn Fn() -> usize + Send + Sync>,
|
||||
view: Arc<
|
||||
Lazy<
|
||||
RwLock<Option<AnyView<R>>>,
|
||||
Box<dyn FnOnce() -> RwLock<Option<AnyView<R>>> + Send + Sync>,
|
||||
RwLock<Option<AnyView>>,
|
||||
Box<dyn FnOnce() -> RwLock<Option<AnyView>> + Send + Sync>,
|
||||
>,
|
||||
>,
|
||||
state: Lazy<
|
||||
SendWrapper<AnyViewState<R>>,
|
||||
Box<dyn FnOnce() -> SendWrapper<AnyViewState<R>> + Send + Sync>,
|
||||
SendWrapper<AnyViewState>,
|
||||
Box<dyn FnOnce() -> SendWrapper<AnyViewState> + Send + Sync>,
|
||||
>,
|
||||
*/
|
||||
|
||||
impl<R: Renderer> Debug for OutletInner<R> {
|
||||
impl<R: Renderer> Debug for OutletInner {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("OutletInner").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Default for OutletInner<R>
|
||||
impl Default for OutletInner
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
|
||||
{
|
||||
fn default() -> Self {
|
||||
let view =
|
||||
Arc::new(Lazy::new(Box::new(|| RwLock::new(Some(().into_any())))
|
||||
as Box<dyn FnOnce() -> RwLock<Option<AnyView<R>>>>));
|
||||
as Box<dyn FnOnce() -> RwLock<Option<AnyView>>>));
|
||||
Self {
|
||||
html_len: Box::new(|| 0),
|
||||
view,
|
||||
@@ -640,9 +640,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Clone for Outlet<R>
|
||||
impl Clone for Outlet
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
@@ -655,9 +655,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Default for Outlet<R>
|
||||
impl Default for Outlet
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -669,11 +669,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Render<R> for Outlet<R>
|
||||
impl Render for Outlet
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
|
||||
{
|
||||
type State = Outlet<R>;
|
||||
type State = Outlet;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
self
|
||||
@@ -684,9 +684,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RenderHtml<R> for Outlet<R>
|
||||
impl RenderHtml for Outlet
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
@@ -725,7 +725,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<R>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
todo!()
|
||||
@@ -743,38 +743,38 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/*pub struct OutletStateInner<R>
|
||||
/*pub struct OutletStateInner
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
|
||||
{
|
||||
html_len: Box<dyn Fn() -> usize + Send + Sync>,
|
||||
view: Arc<
|
||||
Lazy<
|
||||
RwLock<Option<AnyView<R>>>,
|
||||
Box<dyn FnOnce() -> RwLock<Option<AnyView<R>>> + Send + Sync>,
|
||||
RwLock<Option<AnyView>>,
|
||||
Box<dyn FnOnce() -> RwLock<Option<AnyView>> + Send + Sync>,
|
||||
>,
|
||||
>,
|
||||
state: Lazy<
|
||||
SendWrapper<AnyViewState<R>>,
|
||||
Box<dyn FnOnce() -> SendWrapper<AnyViewState<R>> + Send + Sync>,
|
||||
SendWrapper<AnyViewState>,
|
||||
Box<dyn FnOnce() -> SendWrapper<AnyViewState> + Send + Sync>,
|
||||
>,
|
||||
}
|
||||
|
||||
|
||||
impl<R: Renderer> Debug for OutletStateInner<R> {
|
||||
impl<R: Renderer> Debug for OutletStateInner {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("OutletStateInner").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Default for OutletStateInner<R>
|
||||
impl Default for OutletStateInner
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
|
||||
{
|
||||
fn default() -> Self {
|
||||
let view =
|
||||
Arc::new(Lazy::new(Box::new(|| RwLock::new(Some(().into_any())))
|
||||
as Box<dyn FnOnce() -> RwLock<Option<AnyView<R>>>>));
|
||||
as Box<dyn FnOnce() -> RwLock<Option<AnyView>>>));
|
||||
Self {
|
||||
html_len: Box::new(|| 0),
|
||||
view,
|
||||
@@ -784,9 +784,9 @@ where
|
||||
}
|
||||
*/
|
||||
|
||||
impl<R> Mountable<R> for Outlet<R>
|
||||
impl Mountable for Outlet
|
||||
where
|
||||
R: Renderer + 'static,
|
||||
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
todo!()
|
||||
@@ -802,7 +802,7 @@ where
|
||||
}
|
||||
|
||||
fn insert_before_this(&self,
|
||||
child: &mut dyn Mountable<R>,
|
||||
child: &mut dyn Mountable,
|
||||
) -> bool {
|
||||
/*self.inner
|
||||
.write()
|
||||
@@ -818,8 +818,8 @@ fn rebuild_nested<Match, R>(
|
||||
prev: &mut NestedRouteState<Match, R>,
|
||||
new_match: Match,
|
||||
) where
|
||||
Match: MatchInterface<R> + MatchParams + std::fmt::Debug,
|
||||
R: Renderer + 'static,
|
||||
Match: MatchInterface + MatchParams + std::fmt::Debug,
|
||||
|
||||
{
|
||||
let mut items = 0;
|
||||
let NestedRouteState {
|
||||
@@ -846,11 +846,11 @@ fn rebuild_nested<Match, R>(
|
||||
|
||||
fn rebuild_inner<Match, R>(
|
||||
items: &mut usize,
|
||||
outlets: &mut VecDeque<Outlet<R>>,
|
||||
outlets: &mut VecDeque<Outlet>,
|
||||
route_match: Match,
|
||||
) where
|
||||
Match: MatchInterface<R> + MatchParams,
|
||||
R: Renderer + 'static,
|
||||
Match: MatchInterface + MatchParams,
|
||||
|
||||
{
|
||||
*items += 1;
|
||||
|
||||
@@ -910,11 +910,11 @@ fn rebuild_inner<Match, R>(
|
||||
}
|
||||
}
|
||||
|
||||
impl<Matcher, R> Render<R> for NestedRouteView<Matcher, R>
|
||||
impl<Matcher, R> Render for NestedRouteView<Matcher, R>
|
||||
where
|
||||
Matcher: MatchInterface<R>,
|
||||
Matcher: MatchInterface,
|
||||
Matcher::View: Sized + 'static,
|
||||
R: Renderer + 'static,
|
||||
|
||||
{
|
||||
type State = NestedRouteState<Matcher, R>;
|
||||
|
||||
@@ -953,11 +953,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Matcher, R> RenderHtml<R> for NestedRouteView<Matcher, R>
|
||||
impl<Matcher, R> RenderHtml for NestedRouteView<Matcher, R>
|
||||
where
|
||||
Matcher: MatchInterface<R> + Send,
|
||||
Matcher: MatchInterface + Send,
|
||||
Matcher::View: Sized + 'static,
|
||||
R: Renderer + 'static,
|
||||
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
@@ -988,7 +988,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<R>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let NestedRouteView {
|
||||
@@ -1009,10 +1009,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Matcher, R> Mountable<R> for NestedRouteState<Matcher, R>
|
||||
impl<Matcher, R> Mountable for NestedRouteState<Matcher, R>
|
||||
where
|
||||
Matcher: MatchInterface<R>,
|
||||
R: Renderer + 'static,
|
||||
Matcher: MatchInterface,
|
||||
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
self.view.unmount();
|
||||
@@ -1023,43 +1023,43 @@ where
|
||||
}
|
||||
|
||||
fn insert_before_this(&self,
|
||||
child: &mut dyn Mountable<R>,
|
||||
child: &mut dyn Mountable,
|
||||
) -> bool {
|
||||
self.view.insert_before_this(child)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rndr, Loc, FallbackFn, Fallback, Children, View> AddAnyAttr<Rndr>
|
||||
impl<Rndr, Loc, FallbackFn, Fallback, Children, View> AddAnyAttr
|
||||
for Router<Rndr, Loc, Children, FallbackFn>
|
||||
where
|
||||
Loc: Location,
|
||||
FallbackFn: Fn() -> Fallback,
|
||||
Fallback: Render<Rndr>,
|
||||
Children: MatchNestedRoutes<Rndr>,
|
||||
<<Children as MatchNestedRoutes<Rndr>>::Match as MatchInterface<
|
||||
Fallback: Render,
|
||||
Children: MatchNestedRoutes,
|
||||
<<Children as MatchNestedRoutes>::Match as MatchInterface<
|
||||
Rndr,
|
||||
>>::View: ChooseView<Rndr, Output = View>,
|
||||
Rndr: Renderer + 'static,
|
||||
Router<Rndr, Loc, Children, FallbackFn>: RenderHtml<Rndr>,
|
||||
Router<Rndr, Loc, Children, FallbackFn>: RenderHtml,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Rndr>> = Self;
|
||||
type Output<SomeNewAttr: Attribute> = Self;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
self
|
||||
}
|
||||
|
||||
fn add_any_attr_by_ref<NewAttr: Attribute<Rndr>>(
|
||||
fn add_any_attr_by_ref<NewAttr: Attribute>(
|
||||
self,
|
||||
attr: &NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
Self::Output<NewAttr>: RenderHtml,
|
||||
{
|
||||
self
|
||||
}
|
||||
@@ -1069,7 +1069,7 @@ where
|
||||
pub struct FlatRouter<Rndr, Loc, Children, FallbackFn> {
|
||||
base: Option<Cow<'static, str>>,
|
||||
location: PhantomData<Loc>,
|
||||
pub routes: Routes<Children, Rndr>,
|
||||
pub routes: Routes<Children>,
|
||||
fallback: FallbackFn,
|
||||
}
|
||||
|
||||
@@ -1081,7 +1081,7 @@ where
|
||||
FallbackFn: Fn() -> Fallback,
|
||||
{
|
||||
pub fn new(
|
||||
routes: Routes<Children, Rndr>,
|
||||
routes: Routes<Children>,
|
||||
fallback: FallbackFn,
|
||||
) -> FlatRouter<Rndr, Loc, Children, FallbackFn> {
|
||||
Self {
|
||||
@@ -1094,7 +1094,7 @@ where
|
||||
|
||||
pub fn new_with_base(
|
||||
base: impl Into<Cow<'static, str>>,
|
||||
routes: Routes<Children, Rndr>,
|
||||
routes: Routes<Children>,
|
||||
fallback: FallbackFn,
|
||||
) -> FlatRouter<Rndr, Loc, Children, FallbackFn> {
|
||||
Self {
|
||||
@@ -1105,23 +1105,23 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Rndr, Loc, FallbackFn, Fallback, Children> Render<Rndr>
|
||||
impl<Rndr, Loc, FallbackFn, Fallback, Children> Render
|
||||
for FlatRouter<Rndr, Loc, Children, FallbackFn>
|
||||
where
|
||||
Loc: Location,
|
||||
FallbackFn: Fn() -> Fallback + 'static,
|
||||
Fallback: Render<Rndr>,
|
||||
Children: MatchNestedRoutes<Rndr> + 'static,
|
||||
Fallback: Render,
|
||||
Children: MatchNestedRoutes + 'static,
|
||||
Fallback::State: 'static,
|
||||
Rndr: Renderer + 'static,
|
||||
{
|
||||
type State =
|
||||
RenderEffect<
|
||||
EitherState<
|
||||
<<Children::Match as MatchInterface<Rndr>>::View as Render<
|
||||
<<Children::Match as MatchInterface>::View as Render<
|
||||
Rndr,
|
||||
>>::State,
|
||||
<Fallback as Render<Rndr>>::State,
|
||||
<Fallback as Render>::State,
|
||||
Rndr,
|
||||
>,
|
||||
>;
|
||||
@@ -1170,7 +1170,7 @@ where
|
||||
let view = outer_owner.with(|| view.choose());
|
||||
Either::Left::<_, Fallback>(view).rebuild(&mut prev);
|
||||
} else {
|
||||
Either::<<Children::Match as MatchInterface<Rndr>>::View, _>::Right((self.fallback)()).rebuild(&mut prev);
|
||||
Either::<<Children::Match as MatchInterface>::View, _>::Right((self.fallback)()).rebuild(&mut prev);
|
||||
}
|
||||
prev
|
||||
} else {
|
||||
@@ -1209,20 +1209,20 @@ where
|
||||
fn rebuild(self, state: &mut Self::State) {}
|
||||
}
|
||||
|
||||
impl<Rndr, Loc, FallbackFn, Fallback, Children> RenderHtml<Rndr>
|
||||
impl<Rndr, Loc, FallbackFn, Fallback, Children> RenderHtml
|
||||
for FlatRouter<Rndr, Loc, Children, FallbackFn>
|
||||
where
|
||||
Loc: Location + Send,
|
||||
FallbackFn: Fn() -> Fallback + Send + 'static,
|
||||
Fallback: RenderHtml<Rndr>,
|
||||
Children: MatchNestedRoutes<Rndr> + Send + 'static,
|
||||
Fallback: RenderHtml,
|
||||
Children: MatchNestedRoutes + Send + 'static,
|
||||
Fallback::State: 'static,
|
||||
Rndr: Renderer + 'static,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
const MIN_LENGTH: usize =
|
||||
<Children::Match as MatchInterface<Rndr>>::View::MIN_LENGTH;
|
||||
<Children::Match as MatchInterface>::View::MIN_LENGTH;
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
@@ -1357,7 +1357,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Rndr>,
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let location = Loc::new().unwrap(); // TODO
|
||||
@@ -1405,7 +1405,7 @@ where
|
||||
let view = outer_owner.with(|| view.choose());
|
||||
Either::Left::<_, Fallback>(view).rebuild(&mut prev);
|
||||
} else {
|
||||
Either::<<Children::Match as MatchInterface<Rndr>>::View, _>::Right((self.fallback)()).rebuild(&mut prev);
|
||||
Either::<<Children::Match as MatchInterface>::View, _>::Right((self.fallback)()).rebuild(&mut prev);
|
||||
}
|
||||
prev
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router_macro"
|
||||
version = "0.7.0-beta5"
|
||||
version = "0.7.0-gamma"
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -13,7 +13,7 @@ edition.workspace = true
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro-error = { version = "1.0", default-features = false }
|
||||
proc-macro-error2 = { version = "2.0", default-features = false }
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use proc_macro::{TokenStream, TokenTree};
|
||||
use proc_macro2::Span;
|
||||
use proc_macro_error::abort;
|
||||
use proc_macro_error2::abort;
|
||||
use quote::{quote, ToTokens};
|
||||
|
||||
const RFC3986_UNRESERVED: [char; 4] = ['-', '.', '_', '~'];
|
||||
@@ -25,7 +25,7 @@ const RFC3986_PCHAR_OTHER: [char; 1] = ['@'];
|
||||
///
|
||||
/// assert_eq!(path, output);
|
||||
/// ```
|
||||
#[proc_macro_error::proc_macro_error]
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
#[proc_macro]
|
||||
pub fn path(tokens: TokenStream) -> TokenStream {
|
||||
let mut parser = SegmentParser::new(tokens);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tachys"
|
||||
version = "0.1.0-beta5"
|
||||
version = "0.1.0-gamma"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
@@ -1,78 +1,68 @@
|
||||
use super::{Attribute, NextAttribute};
|
||||
use crate::renderer::Renderer;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
};
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
/// A type-erased container for any [`Attribute`].
|
||||
pub struct AnyAttribute<R: Renderer> {
|
||||
pub struct AnyAttribute {
|
||||
type_id: TypeId,
|
||||
html_len: usize,
|
||||
value: Box<dyn Any + Send>,
|
||||
#[cfg(feature = "ssr")]
|
||||
to_html:
|
||||
fn(Box<dyn Any>, &mut String, &mut String, &mut String, &mut String),
|
||||
build: fn(Box<dyn Any>, el: &R::Element) -> AnyAttributeState<R>,
|
||||
rebuild: fn(TypeId, Box<dyn Any>, &mut AnyAttributeState<R>),
|
||||
build: fn(
|
||||
Box<dyn Any>,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> AnyAttributeState,
|
||||
rebuild: fn(TypeId, Box<dyn Any>, &mut AnyAttributeState),
|
||||
#[cfg(feature = "hydrate")]
|
||||
hydrate_from_server: fn(Box<dyn Any>, &R::Element) -> AnyAttributeState<R>,
|
||||
hydrate_from_server:
|
||||
fn(Box<dyn Any>, &crate::renderer::types::Element) -> AnyAttributeState,
|
||||
#[cfg(feature = "hydrate")]
|
||||
hydrate_from_template:
|
||||
fn(Box<dyn Any>, &R::Element) -> AnyAttributeState<R>,
|
||||
fn(Box<dyn Any>, &crate::renderer::types::Element) -> AnyAttributeState,
|
||||
#[cfg(feature = "ssr")]
|
||||
#[allow(clippy::type_complexity)]
|
||||
resolve: fn(
|
||||
Box<dyn Any>,
|
||||
) -> Pin<Box<dyn Future<Output = AnyAttribute<R>> + Send>>,
|
||||
resolve:
|
||||
fn(Box<dyn Any>) -> Pin<Box<dyn Future<Output = AnyAttribute> + Send>>,
|
||||
#[cfg(feature = "ssr")]
|
||||
dry_resolve: fn(&mut Box<dyn Any + Send>),
|
||||
}
|
||||
|
||||
impl<R> Debug for AnyAttribute<R>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
impl Debug for AnyAttribute {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AnyAttribute").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// View state for [`AnyAttribute`].
|
||||
pub struct AnyAttributeState<R>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
pub struct AnyAttributeState {
|
||||
type_id: TypeId,
|
||||
state: Box<dyn Any>,
|
||||
el: R::Element,
|
||||
rndr: PhantomData<R>,
|
||||
el: crate::renderer::types::Element,
|
||||
}
|
||||
|
||||
/// Converts an [`Attribute`] into [`AnyAttribute`].
|
||||
pub trait IntoAnyAttribute<R>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
pub trait IntoAnyAttribute {
|
||||
/// Wraps the given attribute.
|
||||
fn into_any_attr(self) -> AnyAttribute<R>;
|
||||
fn into_any_attr(self) -> AnyAttribute;
|
||||
}
|
||||
|
||||
impl<T, R> IntoAnyAttribute<R> for T
|
||||
impl<T> IntoAnyAttribute for T
|
||||
where
|
||||
Self: Send,
|
||||
T: Attribute<R> + 'static,
|
||||
T: Attribute + 'static,
|
||||
T::State: 'static,
|
||||
R: Renderer + 'static,
|
||||
R::Element: Clone,
|
||||
crate::renderer::types::Element: Clone,
|
||||
{
|
||||
// inlining allows the compiler to remove the unused functions
|
||||
// i.e., doesn't ship HTML-generating code that isn't used
|
||||
#[inline(always)]
|
||||
fn into_any_attr(self) -> AnyAttribute<R> {
|
||||
fn into_any_attr(self) -> AnyAttribute {
|
||||
let html_len = self.html_len();
|
||||
|
||||
let value = Box::new(self) as Box<dyn Any + Send>;
|
||||
@@ -88,7 +78,8 @@ where
|
||||
.expect("AnyAttribute::to_html could not be downcast");
|
||||
value.to_html(buf, class, style, inner_html);
|
||||
};
|
||||
let build = |value: Box<dyn Any>, el: &R::Element| {
|
||||
let build = |value: Box<dyn Any>,
|
||||
el: &crate::renderer::types::Element| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyAttribute::build couldn't downcast");
|
||||
@@ -98,40 +89,39 @@ where
|
||||
type_id: TypeId::of::<T>(),
|
||||
state,
|
||||
el: el.clone(),
|
||||
rndr: PhantomData,
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "hydrate")]
|
||||
let hydrate_from_server = |value: Box<dyn Any>, el: &R::Element| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyAttribute::hydrate_from_server couldn't downcast");
|
||||
let state = Box::new(value.hydrate::<true>(el));
|
||||
let hydrate_from_server =
|
||||
|value: Box<dyn Any>, el: &crate::renderer::types::Element| {
|
||||
let value = value.downcast::<T>().expect(
|
||||
"AnyAttribute::hydrate_from_server couldn't downcast",
|
||||
);
|
||||
let state = Box::new(value.hydrate::<true>(el));
|
||||
|
||||
AnyAttributeState {
|
||||
type_id: TypeId::of::<T>(),
|
||||
state,
|
||||
el: el.clone(),
|
||||
rndr: PhantomData,
|
||||
}
|
||||
};
|
||||
AnyAttributeState {
|
||||
type_id: TypeId::of::<T>(),
|
||||
state,
|
||||
el: el.clone(),
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "hydrate")]
|
||||
let hydrate_from_template = |value: Box<dyn Any>, el: &R::Element| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyAttribute::hydrate_from_server couldn't downcast");
|
||||
let state = Box::new(value.hydrate::<true>(el));
|
||||
let hydrate_from_template =
|
||||
|value: Box<dyn Any>, el: &crate::renderer::types::Element| {
|
||||
let value = value.downcast::<T>().expect(
|
||||
"AnyAttribute::hydrate_from_server couldn't downcast",
|
||||
);
|
||||
let state = Box::new(value.hydrate::<true>(el));
|
||||
|
||||
AnyAttributeState {
|
||||
type_id: TypeId::of::<T>(),
|
||||
state,
|
||||
el: el.clone(),
|
||||
rndr: PhantomData,
|
||||
}
|
||||
};
|
||||
AnyAttributeState {
|
||||
type_id: TypeId::of::<T>(),
|
||||
state,
|
||||
el: el.clone(),
|
||||
}
|
||||
};
|
||||
let rebuild = |new_type_id: TypeId,
|
||||
value: Box<dyn Any>,
|
||||
state: &mut AnyAttributeState<R>| {
|
||||
state: &mut AnyAttributeState| {
|
||||
let value = value
|
||||
.downcast::<T>()
|
||||
.expect("AnyAttribute::rebuild couldn't downcast value");
|
||||
@@ -160,7 +150,7 @@ where
|
||||
.downcast::<T>()
|
||||
.expect("AnyView::resolve could not be downcast");
|
||||
Box::pin(async move { value.resolve().await.into_any_attr() })
|
||||
as Pin<Box<dyn Future<Output = AnyAttribute<R>> + Send>>
|
||||
as Pin<Box<dyn Future<Output = AnyAttribute> + Send>>
|
||||
};
|
||||
AnyAttribute {
|
||||
type_id: TypeId::of::<T>(),
|
||||
@@ -182,13 +172,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> NextAttribute<R> for AnyAttribute<R>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
type Output<NewAttr: Attribute<R>> = (Self, NewAttr);
|
||||
impl NextAttribute for AnyAttribute {
|
||||
type Output<NewAttr: Attribute> = (Self, NewAttr);
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<R>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
@@ -196,14 +183,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Attribute<R> for AnyAttribute<R>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
impl Attribute for AnyAttribute {
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
type AsyncOutput = AnyAttribute<R>;
|
||||
type State = AnyAttributeState<R>;
|
||||
type AsyncOutput = AnyAttribute;
|
||||
type State = AnyAttributeState;
|
||||
type Cloneable = ();
|
||||
type CloneableOwned = ();
|
||||
|
||||
@@ -232,7 +216,7 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &<R as Renderer>::Element,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
#[cfg(feature = "hydrate")]
|
||||
if FROM_SERVER {
|
||||
@@ -250,7 +234,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn build(self, el: &<R as Renderer>::Element) -> Self::State {
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
(self.build)(self.value, el)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
use crate::{
|
||||
html::{
|
||||
attribute::{Attr, *},
|
||||
element::{CreateElement, ElementType, HtmlElement},
|
||||
element::{ElementType, HtmlElement},
|
||||
},
|
||||
renderer::Rndr,
|
||||
view::{add_attr::AddAnyAttr, RenderHtml},
|
||||
};
|
||||
|
||||
/// Applies ARIA attributes to an HTML element.
|
||||
pub trait AriaAttributes<Rndr, V>
|
||||
where
|
||||
Self: Sized + AddAnyAttr<Rndr>,
|
||||
V: AttributeValue<Rndr>,
|
||||
Rndr: Renderer,
|
||||
Self: Sized + AddAnyAttr,
|
||||
V: AttributeValue,
|
||||
{
|
||||
/// Identifies the currently active descendant of a composite widget.
|
||||
fn aria_activedescendant(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaActivedescendant, V, Rndr>>
|
||||
{
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaActivedescendant, V>> {
|
||||
self.add_any_attr(aria_activedescendant(value))
|
||||
}
|
||||
|
||||
@@ -26,7 +25,7 @@ where
|
||||
fn aria_atomic(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaAtomic, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaAtomic, V>> {
|
||||
self.add_any_attr(aria_atomic(value))
|
||||
}
|
||||
|
||||
@@ -34,8 +33,7 @@ where
|
||||
fn aria_autocomplete(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaAutocomplete, V, Rndr>>
|
||||
{
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaAutocomplete, V>> {
|
||||
self.add_any_attr(aria_autocomplete(value))
|
||||
}
|
||||
|
||||
@@ -43,7 +41,7 @@ where
|
||||
fn aria_busy(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaBusy, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaBusy, V>> {
|
||||
self.add_any_attr(aria_busy(value))
|
||||
}
|
||||
|
||||
@@ -51,7 +49,7 @@ where
|
||||
fn aria_checked(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaChecked, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaChecked, V>> {
|
||||
self.add_any_attr(aria_checked(value))
|
||||
}
|
||||
|
||||
@@ -59,7 +57,7 @@ where
|
||||
fn aria_colcount(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaColcount, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaColcount, V>> {
|
||||
self.add_any_attr(aria_colcount(value))
|
||||
}
|
||||
|
||||
@@ -67,7 +65,7 @@ where
|
||||
fn aria_colindex(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaColindex, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaColindex, V>> {
|
||||
self.add_any_attr(aria_colindex(value))
|
||||
}
|
||||
|
||||
@@ -75,7 +73,7 @@ where
|
||||
fn aria_colspan(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaColspan, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaColspan, V>> {
|
||||
self.add_any_attr(aria_colspan(value))
|
||||
}
|
||||
|
||||
@@ -83,7 +81,7 @@ where
|
||||
fn aria_controls(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaControls, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaControls, V>> {
|
||||
self.add_any_attr(aria_controls(value))
|
||||
}
|
||||
|
||||
@@ -91,7 +89,7 @@ where
|
||||
fn aria_current(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaCurrent, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaCurrent, V>> {
|
||||
self.add_any_attr(aria_current(value))
|
||||
}
|
||||
|
||||
@@ -99,8 +97,7 @@ where
|
||||
fn aria_describedby(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaDescribedby, V, Rndr>>
|
||||
{
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaDescribedby, V>> {
|
||||
self.add_any_attr(aria_describedby(value))
|
||||
}
|
||||
|
||||
@@ -108,8 +105,7 @@ where
|
||||
fn aria_description(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaDescription, V, Rndr>>
|
||||
{
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaDescription, V>> {
|
||||
self.add_any_attr(aria_description(value))
|
||||
}
|
||||
|
||||
@@ -117,7 +113,7 @@ where
|
||||
fn aria_details(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaDetails, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaDetails, V>> {
|
||||
self.add_any_attr(aria_details(value))
|
||||
}
|
||||
|
||||
@@ -125,7 +121,7 @@ where
|
||||
fn aria_disabled(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaDisabled, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaDisabled, V>> {
|
||||
self.add_any_attr(aria_disabled(value))
|
||||
}
|
||||
|
||||
@@ -133,7 +129,7 @@ where
|
||||
fn aria_dropeffect(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaDropeffect, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaDropeffect, V>> {
|
||||
self.add_any_attr(aria_dropeffect(value))
|
||||
}
|
||||
|
||||
@@ -141,8 +137,7 @@ where
|
||||
fn aria_errormessage(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaErrormessage, V, Rndr>>
|
||||
{
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaErrormessage, V>> {
|
||||
self.add_any_attr(aria_errormessage(value))
|
||||
}
|
||||
|
||||
@@ -150,7 +145,7 @@ where
|
||||
fn aria_expanded(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaExpanded, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaExpanded, V>> {
|
||||
self.add_any_attr(aria_expanded(value))
|
||||
}
|
||||
|
||||
@@ -158,7 +153,7 @@ where
|
||||
fn aria_flowto(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaFlowto, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaFlowto, V>> {
|
||||
self.add_any_attr(aria_flowto(value))
|
||||
}
|
||||
|
||||
@@ -166,7 +161,7 @@ where
|
||||
fn aria_grabbed(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaGrabbed, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaGrabbed, V>> {
|
||||
self.add_any_attr(aria_grabbed(value))
|
||||
}
|
||||
|
||||
@@ -174,7 +169,7 @@ where
|
||||
fn aria_haspopup(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaHaspopup, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaHaspopup, V>> {
|
||||
self.add_any_attr(aria_haspopup(value))
|
||||
}
|
||||
|
||||
@@ -182,7 +177,7 @@ where
|
||||
fn aria_hidden(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaHidden, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaHidden, V>> {
|
||||
self.add_any_attr(aria_hidden(value))
|
||||
}
|
||||
|
||||
@@ -190,7 +185,7 @@ where
|
||||
fn aria_invalid(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaInvalid, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaInvalid, V>> {
|
||||
self.add_any_attr(aria_invalid(value))
|
||||
}
|
||||
|
||||
@@ -198,8 +193,7 @@ where
|
||||
fn aria_keyshortcuts(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaKeyshortcuts, V, Rndr>>
|
||||
{
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaKeyshortcuts, V>> {
|
||||
self.add_any_attr(aria_keyshortcuts(value))
|
||||
}
|
||||
|
||||
@@ -207,7 +201,7 @@ where
|
||||
fn aria_label(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaLabel, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaLabel, V>> {
|
||||
self.add_any_attr(aria_label(value))
|
||||
}
|
||||
|
||||
@@ -215,7 +209,7 @@ where
|
||||
fn aria_labelledby(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaLabelledby, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaLabelledby, V>> {
|
||||
self.add_any_attr(aria_labelledby(value))
|
||||
}
|
||||
|
||||
@@ -223,7 +217,7 @@ where
|
||||
fn aria_live(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaLive, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaLive, V>> {
|
||||
self.add_any_attr(aria_live(value))
|
||||
}
|
||||
|
||||
@@ -231,7 +225,7 @@ where
|
||||
fn aria_modal(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaModal, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaModal, V>> {
|
||||
self.add_any_attr(aria_modal(value))
|
||||
}
|
||||
|
||||
@@ -239,7 +233,7 @@ where
|
||||
fn aria_multiline(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaMultiline, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaMultiline, V>> {
|
||||
self.add_any_attr(aria_multiline(value))
|
||||
}
|
||||
|
||||
@@ -247,8 +241,7 @@ where
|
||||
fn aria_multiselectable(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaMultiselectable, V, Rndr>>
|
||||
{
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaMultiselectable, V>> {
|
||||
self.add_any_attr(aria_multiselectable(value))
|
||||
}
|
||||
|
||||
@@ -256,8 +249,7 @@ where
|
||||
fn aria_orientation(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaOrientation, V, Rndr>>
|
||||
{
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaOrientation, V>> {
|
||||
self.add_any_attr(aria_orientation(value))
|
||||
}
|
||||
|
||||
@@ -265,7 +257,7 @@ where
|
||||
fn aria_owns(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaOwns, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaOwns, V>> {
|
||||
self.add_any_attr(aria_owns(value))
|
||||
}
|
||||
|
||||
@@ -273,8 +265,7 @@ where
|
||||
fn aria_placeholder(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaPlaceholder, V, Rndr>>
|
||||
{
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaPlaceholder, V>> {
|
||||
self.add_any_attr(aria_placeholder(value))
|
||||
}
|
||||
|
||||
@@ -282,7 +273,7 @@ where
|
||||
fn aria_posinset(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaPosinset, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaPosinset, V>> {
|
||||
self.add_any_attr(aria_posinset(value))
|
||||
}
|
||||
|
||||
@@ -290,7 +281,7 @@ where
|
||||
fn aria_pressed(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaPressed, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaPressed, V>> {
|
||||
self.add_any_attr(aria_pressed(value))
|
||||
}
|
||||
|
||||
@@ -298,7 +289,7 @@ where
|
||||
fn aria_readonly(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaReadonly, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaReadonly, V>> {
|
||||
self.add_any_attr(aria_readonly(value))
|
||||
}
|
||||
|
||||
@@ -306,7 +297,7 @@ where
|
||||
fn aria_relevant(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaRelevant, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaRelevant, V>> {
|
||||
self.add_any_attr(aria_relevant(value))
|
||||
}
|
||||
|
||||
@@ -314,7 +305,7 @@ where
|
||||
fn aria_required(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaRequired, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaRequired, V>> {
|
||||
self.add_any_attr(aria_required(value))
|
||||
}
|
||||
|
||||
@@ -322,8 +313,7 @@ where
|
||||
fn aria_roledescription(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaRoledescription, V, Rndr>>
|
||||
{
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaRoledescription, V>> {
|
||||
self.add_any_attr(aria_roledescription(value))
|
||||
}
|
||||
|
||||
@@ -331,7 +321,7 @@ where
|
||||
fn aria_rowcount(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaRowcount, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaRowcount, V>> {
|
||||
self.add_any_attr(aria_rowcount(value))
|
||||
}
|
||||
|
||||
@@ -339,7 +329,7 @@ where
|
||||
fn aria_rowindex(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaRowindex, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaRowindex, V>> {
|
||||
self.add_any_attr(aria_rowindex(value))
|
||||
}
|
||||
|
||||
@@ -347,7 +337,7 @@ where
|
||||
fn aria_rowspan(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaRowspan, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaRowspan, V>> {
|
||||
self.add_any_attr(aria_rowspan(value))
|
||||
}
|
||||
|
||||
@@ -355,7 +345,7 @@ where
|
||||
fn aria_selected(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaSelected, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaSelected, V>> {
|
||||
self.add_any_attr(aria_selected(value))
|
||||
}
|
||||
|
||||
@@ -363,7 +353,7 @@ where
|
||||
fn aria_setsize(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaSetsize, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaSetsize, V>> {
|
||||
self.add_any_attr(aria_setsize(value))
|
||||
}
|
||||
|
||||
@@ -371,7 +361,7 @@ where
|
||||
fn aria_sort(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaSort, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaSort, V>> {
|
||||
self.add_any_attr(aria_sort(value))
|
||||
}
|
||||
|
||||
@@ -379,7 +369,7 @@ where
|
||||
fn aria_valuemax(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaValuemax, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaValuemax, V>> {
|
||||
self.add_any_attr(aria_valuemax(value))
|
||||
}
|
||||
|
||||
@@ -387,7 +377,7 @@ where
|
||||
fn aria_valuemin(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaValuemin, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaValuemin, V>> {
|
||||
self.add_any_attr(aria_valuemin(value))
|
||||
}
|
||||
|
||||
@@ -395,7 +385,7 @@ where
|
||||
fn aria_valuenow(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaValuenow, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaValuenow, V>> {
|
||||
self.add_any_attr(aria_valuenow(value))
|
||||
}
|
||||
|
||||
@@ -403,18 +393,16 @@ where
|
||||
fn aria_valuetext(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<AriaValuetext, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<AriaValuetext, V>> {
|
||||
self.add_any_attr(aria_valuetext(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<El, At, Ch, Rndr, V> AriaAttributes<Rndr, V>
|
||||
for HtmlElement<El, At, Ch, Rndr>
|
||||
impl<El, At, Ch, V> AriaAttributes<Rndr, V> for HtmlElement<El, At, Ch>
|
||||
where
|
||||
El: ElementType + CreateElement<Rndr> + Send,
|
||||
At: Attribute<Rndr> + Send,
|
||||
Ch: RenderHtml<Rndr> + Send,
|
||||
V: AttributeValue<Rndr>,
|
||||
Rndr: Renderer,
|
||||
El: ElementType + Send,
|
||||
At: Attribute + Send,
|
||||
Ch: RenderHtml + Send,
|
||||
V: AttributeValue,
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,65 +1,54 @@
|
||||
use super::NextAttribute;
|
||||
use crate::{
|
||||
html::attribute::{Attribute, AttributeValue},
|
||||
renderer::DomRenderer,
|
||||
view::{add_attr::AddAnyAttr, Position, ToTemplate},
|
||||
};
|
||||
use std::{borrow::Cow, marker::PhantomData, sync::Arc};
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
/// Adds a custom attribute with any key-value combintion.
|
||||
#[inline(always)]
|
||||
pub fn custom_attribute<K, V, R>(key: K, value: V) -> CustomAttr<K, V, R>
|
||||
pub fn custom_attribute<K, V>(key: K, value: V) -> CustomAttr<K, V>
|
||||
where
|
||||
K: CustomAttributeKey,
|
||||
V: AttributeValue<R>,
|
||||
R: DomRenderer,
|
||||
V: AttributeValue,
|
||||
{
|
||||
CustomAttr {
|
||||
key,
|
||||
value,
|
||||
rndr: PhantomData,
|
||||
}
|
||||
CustomAttr { key, value }
|
||||
}
|
||||
|
||||
/// A custom attribute with any key-value combination.
|
||||
#[derive(Debug)]
|
||||
pub struct CustomAttr<K, V, R>
|
||||
pub struct CustomAttr<K, V>
|
||||
where
|
||||
K: CustomAttributeKey,
|
||||
V: AttributeValue<R>,
|
||||
R: DomRenderer,
|
||||
V: AttributeValue,
|
||||
{
|
||||
key: K,
|
||||
value: V,
|
||||
rndr: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<K, V, R> Clone for CustomAttr<K, V, R>
|
||||
impl<K, V> Clone for CustomAttr<K, V>
|
||||
where
|
||||
K: CustomAttributeKey,
|
||||
V: AttributeValue<R> + Clone,
|
||||
R: DomRenderer,
|
||||
V: AttributeValue + Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
key: self.key.clone(),
|
||||
value: self.value.clone(),
|
||||
rndr: self.rndr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, R> Attribute<R> for CustomAttr<K, V, R>
|
||||
impl<K, V> Attribute for CustomAttr<K, V>
|
||||
where
|
||||
K: CustomAttributeKey,
|
||||
V: AttributeValue<R>,
|
||||
R: DomRenderer,
|
||||
V: AttributeValue,
|
||||
{
|
||||
const MIN_LENGTH: usize = 0;
|
||||
type AsyncOutput = CustomAttr<K, V::AsyncOutput, R>;
|
||||
type AsyncOutput = CustomAttr<K, V::AsyncOutput>;
|
||||
type State = V::State;
|
||||
type Cloneable = CustomAttr<K, V::Cloneable, R>;
|
||||
type CloneableOwned = CustomAttr<K, V::CloneableOwned, R>;
|
||||
type Cloneable = CustomAttr<K, V::Cloneable>;
|
||||
type CloneableOwned = CustomAttr<K, V::CloneableOwned>;
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
self.key.as_ref().len() + 3 + self.value.html_len()
|
||||
@@ -75,7 +64,10 @@ where
|
||||
self.value.to_html(self.key.as_ref(), buf);
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State {
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
if !K::KEY.is_empty() {
|
||||
self.value.hydrate::<FROM_SERVER>(self.key.as_ref(), el)
|
||||
} else {
|
||||
@@ -83,7 +75,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn build(self, el: &R::Element) -> Self::State {
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
self.value.build(el, self.key.as_ref())
|
||||
}
|
||||
|
||||
@@ -95,7 +87,6 @@ where
|
||||
CustomAttr {
|
||||
key: self.key,
|
||||
value: self.value.into_cloneable(),
|
||||
rndr: self.rndr,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +94,6 @@ where
|
||||
CustomAttr {
|
||||
key: self.key,
|
||||
value: self.value.into_cloneable_owned(),
|
||||
rndr: self.rndr,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,20 +105,18 @@ where
|
||||
CustomAttr {
|
||||
key: self.key,
|
||||
value: self.value.resolve().await,
|
||||
rndr: self.rndr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, R> NextAttribute<R> for CustomAttr<K, V, R>
|
||||
impl<K, V> NextAttribute for CustomAttr<K, V>
|
||||
where
|
||||
K: CustomAttributeKey,
|
||||
V: AttributeValue<R>,
|
||||
R: DomRenderer,
|
||||
V: AttributeValue,
|
||||
{
|
||||
type Output<NewAttr: Attribute<R>> = (Self, NewAttr);
|
||||
type Output<NewAttr: Attribute> = (Self, NewAttr);
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<R>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
@@ -136,11 +124,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, R> ToTemplate for CustomAttr<K, V, R>
|
||||
impl<K, V> ToTemplate for CustomAttr<K, V>
|
||||
where
|
||||
K: CustomAttributeKey,
|
||||
V: AttributeValue<R>,
|
||||
R: DomRenderer,
|
||||
V: AttributeValue,
|
||||
{
|
||||
fn to_template(
|
||||
buf: &mut String,
|
||||
@@ -186,28 +173,27 @@ impl<const K: &'static str> CustomAttributeKey
|
||||
}
|
||||
|
||||
/// Adds a custom attribute to an element.
|
||||
pub trait CustomAttribute<K, V, Rndr>
|
||||
pub trait CustomAttribute<K, V>
|
||||
where
|
||||
K: CustomAttributeKey,
|
||||
V: AttributeValue<Rndr>,
|
||||
Rndr: DomRenderer,
|
||||
Self: Sized + AddAnyAttr<Rndr>,
|
||||
V: AttributeValue,
|
||||
|
||||
Self: Sized + AddAnyAttr,
|
||||
{
|
||||
/// Adds an HTML attribute by key and value.
|
||||
fn attr(
|
||||
self,
|
||||
key: K,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<CustomAttr<K, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<CustomAttr<K, V>> {
|
||||
self.add_any_attr(custom_attribute(key, value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, K, V, Rndr> CustomAttribute<K, V, Rndr> for T
|
||||
impl<T, K, V> CustomAttribute<K, V> for T
|
||||
where
|
||||
T: AddAnyAttr<Rndr>,
|
||||
T: AddAnyAttr,
|
||||
K: CustomAttributeKey,
|
||||
V: AttributeValue<Rndr>,
|
||||
Rndr: DomRenderer,
|
||||
V: AttributeValue,
|
||||
{
|
||||
}
|
||||
|
||||
@@ -3,22 +3,20 @@ use crate::{
|
||||
html::{
|
||||
attribute::*,
|
||||
class::{class, Class, IntoClass},
|
||||
element::{CreateElement, ElementType, HasElementType, HtmlElement},
|
||||
element::{ElementType, HasElementType, HtmlElement},
|
||||
event::{on, on_target, EventDescriptor, On, Targeted},
|
||||
property::{prop, IntoProperty, Property},
|
||||
style::{style, IntoStyle, Style},
|
||||
},
|
||||
prelude::RenderHtml,
|
||||
renderer::DomRenderer,
|
||||
view::add_attr::AddAnyAttr,
|
||||
};
|
||||
use core::convert::From;
|
||||
|
||||
/// Adds an attribute that modifies the `class`.
|
||||
pub trait ClassAttribute<C, Rndr>
|
||||
pub trait ClassAttribute<C>
|
||||
where
|
||||
C: IntoClass<Rndr>,
|
||||
Rndr: DomRenderer,
|
||||
C: IntoClass,
|
||||
{
|
||||
/// The type of the element with the new attribute added.
|
||||
type Output;
|
||||
@@ -27,16 +25,14 @@ where
|
||||
fn class(self, value: C) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<E, At, Ch, C, Rndr> ClassAttribute<C, Rndr>
|
||||
for HtmlElement<E, At, Ch, Rndr>
|
||||
impl<E, At, Ch, C> ClassAttribute<C> for HtmlElement<E, At, Ch>
|
||||
where
|
||||
E: ElementType + CreateElement<Rndr> + Send,
|
||||
At: Attribute<Rndr> + Send,
|
||||
Ch: RenderHtml<Rndr> + Send,
|
||||
C: IntoClass<Rndr>,
|
||||
Rndr: DomRenderer,
|
||||
E: ElementType + Send,
|
||||
At: Attribute + Send,
|
||||
Ch: RenderHtml + Send,
|
||||
C: IntoClass,
|
||||
{
|
||||
type Output = <Self as AddAnyAttr<Rndr>>::Output<Class<C, Rndr>>;
|
||||
type Output = <Self as AddAnyAttr>::Output<Class<C>>;
|
||||
|
||||
fn class(self, value: C) -> Self::Output {
|
||||
self.add_any_attr(class(value))
|
||||
@@ -44,10 +40,9 @@ where
|
||||
}
|
||||
|
||||
/// Adds an attribute that modifies the DOM properties.
|
||||
pub trait PropAttribute<K, P, Rndr>
|
||||
pub trait PropAttribute<K, P>
|
||||
where
|
||||
P: IntoProperty<Rndr>,
|
||||
Rndr: DomRenderer,
|
||||
P: IntoProperty,
|
||||
{
|
||||
/// The type of the element with the new attribute added.
|
||||
type Output;
|
||||
@@ -56,17 +51,15 @@ where
|
||||
fn prop(self, key: K, value: P) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<E, At, Ch, K, P, Rndr> PropAttribute<K, P, Rndr>
|
||||
for HtmlElement<E, At, Ch, Rndr>
|
||||
impl<E, At, Ch, K, P> PropAttribute<K, P> for HtmlElement<E, At, Ch>
|
||||
where
|
||||
E: ElementType + CreateElement<Rndr> + Send,
|
||||
At: Attribute<Rndr> + Send,
|
||||
Ch: RenderHtml<Rndr> + Send,
|
||||
E: ElementType + Send,
|
||||
At: Attribute + Send,
|
||||
Ch: RenderHtml + Send,
|
||||
K: AsRef<str> + Send,
|
||||
P: IntoProperty<Rndr>,
|
||||
Rndr: DomRenderer,
|
||||
P: IntoProperty,
|
||||
{
|
||||
type Output = <Self as AddAnyAttr<Rndr>>::Output<Property<K, P, Rndr>>;
|
||||
type Output = <Self as AddAnyAttr>::Output<Property<K, P>>;
|
||||
|
||||
fn prop(self, key: K, value: P) -> Self::Output {
|
||||
self.add_any_attr(prop(key, value))
|
||||
@@ -74,10 +67,9 @@ where
|
||||
}
|
||||
|
||||
/// Adds an attribute that modifies the CSS styles.
|
||||
pub trait StyleAttribute<S, Rndr>
|
||||
pub trait StyleAttribute<S>
|
||||
where
|
||||
S: IntoStyle<Rndr>,
|
||||
Rndr: DomRenderer,
|
||||
S: IntoStyle,
|
||||
{
|
||||
/// The type of the element with the new attribute added.
|
||||
type Output;
|
||||
@@ -86,16 +78,14 @@ where
|
||||
fn style(self, value: S) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<E, At, Ch, S, Rndr> StyleAttribute<S, Rndr>
|
||||
for HtmlElement<E, At, Ch, Rndr>
|
||||
impl<E, At, Ch, S> StyleAttribute<S> for HtmlElement<E, At, Ch>
|
||||
where
|
||||
E: ElementType + CreateElement<Rndr> + Send,
|
||||
At: Attribute<Rndr> + Send,
|
||||
Ch: RenderHtml<Rndr> + Send,
|
||||
S: IntoStyle<Rndr>,
|
||||
Rndr: DomRenderer,
|
||||
E: ElementType + Send,
|
||||
At: Attribute + Send,
|
||||
Ch: RenderHtml + Send,
|
||||
S: IntoStyle,
|
||||
{
|
||||
type Output = <Self as AddAnyAttr<Rndr>>::Output<Style<S, Rndr>>;
|
||||
type Output = <Self as AddAnyAttr>::Output<Style<S>>;
|
||||
|
||||
fn style(self, value: S) -> Self::Output {
|
||||
self.add_any_attr(style(value))
|
||||
@@ -103,7 +93,7 @@ where
|
||||
}
|
||||
|
||||
/// Adds an event listener to an element definition.
|
||||
pub trait OnAttribute<E, F, Rndr> {
|
||||
pub trait OnAttribute<E, F> {
|
||||
/// The type of the element with the event listener added.
|
||||
type Output;
|
||||
|
||||
@@ -111,19 +101,17 @@ pub trait OnAttribute<E, F, Rndr> {
|
||||
fn on(self, event: E, cb: F) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<El, At, Ch, E, F, Rndr> OnAttribute<E, F, Rndr>
|
||||
for HtmlElement<El, At, Ch, Rndr>
|
||||
impl<El, At, Ch, E, F> OnAttribute<E, F> for HtmlElement<El, At, Ch>
|
||||
where
|
||||
El: ElementType + CreateElement<Rndr> + Send,
|
||||
At: Attribute<Rndr> + Send,
|
||||
Ch: RenderHtml<Rndr> + Send,
|
||||
El: ElementType + Send,
|
||||
At: Attribute + Send,
|
||||
Ch: RenderHtml + Send,
|
||||
E: EventDescriptor + Send + 'static,
|
||||
E::EventType: 'static,
|
||||
E::EventType: From<Rndr::Event>,
|
||||
E::EventType: From<crate::renderer::types::Event>,
|
||||
F: FnMut(E::EventType) + 'static,
|
||||
Rndr: DomRenderer,
|
||||
{
|
||||
type Output = <Self as AddAnyAttr<Rndr>>::Output<On<E, F, Rndr>>;
|
||||
type Output = <Self as AddAnyAttr>::Output<On<E, F>>;
|
||||
|
||||
fn on(self, event: E, cb: F) -> Self::Output {
|
||||
self.add_any_attr(on(event, cb))
|
||||
@@ -131,7 +119,7 @@ where
|
||||
}
|
||||
|
||||
/// Adds an event listener with a typed target to an element definition.
|
||||
pub trait OnTargetAttribute<E, F, T, Rndr> {
|
||||
pub trait OnTargetAttribute<E, F, T> {
|
||||
/// The type of the element with the new attribute added.
|
||||
type Output;
|
||||
|
||||
@@ -139,43 +127,36 @@ pub trait OnTargetAttribute<E, F, T, Rndr> {
|
||||
fn on_target(self, event: E, cb: F) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<El, At, Ch, E, F, Rndr> OnTargetAttribute<E, F, Self, Rndr>
|
||||
for HtmlElement<El, At, Ch, Rndr>
|
||||
impl<El, At, Ch, E, F> OnTargetAttribute<E, F, Self> for HtmlElement<El, At, Ch>
|
||||
where
|
||||
El: ElementType + CreateElement<Rndr> + Send,
|
||||
At: Attribute<Rndr> + Send,
|
||||
Ch: RenderHtml<Rndr> + Send,
|
||||
El: ElementType + Send,
|
||||
At: Attribute + Send,
|
||||
Ch: RenderHtml + Send,
|
||||
E: EventDescriptor + Send + 'static,
|
||||
E::EventType: 'static,
|
||||
E::EventType: From<Rndr::Event>,
|
||||
F: FnMut(
|
||||
Targeted<E::EventType, <Self as HasElementType>::ElementType, Rndr>,
|
||||
) + 'static,
|
||||
Rndr: DomRenderer,
|
||||
E::EventType: From<crate::renderer::types::Event>,
|
||||
F: FnMut(Targeted<E::EventType, <Self as HasElementType>::ElementType>)
|
||||
+ 'static,
|
||||
{
|
||||
type Output = <Self as AddAnyAttr<Rndr>>::Output<
|
||||
On<E, Box<dyn FnMut(E::EventType)>, Rndr>,
|
||||
>;
|
||||
type Output =
|
||||
<Self as AddAnyAttr>::Output<On<E, Box<dyn FnMut(E::EventType)>>>;
|
||||
|
||||
fn on_target(self, event: E, cb: F) -> Self::Output {
|
||||
self.add_any_attr(
|
||||
on_target::<E, HtmlElement<El, At, Ch, Rndr>, Rndr, F>(event, cb),
|
||||
)
|
||||
self.add_any_attr(on_target::<E, HtmlElement<El, At, Ch>, F>(event, cb))
|
||||
}
|
||||
}
|
||||
|
||||
/// Global attributes can be added to any HTML element.
|
||||
pub trait GlobalAttributes<Rndr, V>
|
||||
pub trait GlobalAttributes<V>
|
||||
where
|
||||
Self: Sized + AddAnyAttr<Rndr>,
|
||||
V: AttributeValue<Rndr>,
|
||||
Rndr: Renderer,
|
||||
Self: Sized + AddAnyAttr,
|
||||
V: AttributeValue,
|
||||
{
|
||||
/// The `accesskey` global attribute provides a hint for generating a keyboard shortcut for the current element.
|
||||
fn accesskey(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Accesskey, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Accesskey, V>> {
|
||||
self.add_any_attr(accesskey(value))
|
||||
}
|
||||
|
||||
@@ -183,7 +164,7 @@ where
|
||||
fn autocapitalize(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Autocapitalize, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Autocapitalize, V>> {
|
||||
self.add_any_attr(autocapitalize(value))
|
||||
}
|
||||
|
||||
@@ -191,7 +172,7 @@ where
|
||||
fn autofocus(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Autofocus, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Autofocus, V>> {
|
||||
self.add_any_attr(autofocus(value))
|
||||
}
|
||||
|
||||
@@ -199,16 +180,12 @@ where
|
||||
fn contenteditable(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Contenteditable, V, Rndr>>
|
||||
{
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Contenteditable, V>> {
|
||||
self.add_any_attr(contenteditable(value))
|
||||
}
|
||||
|
||||
/// The `dir` global attribute is an enumerated attribute indicating the directionality of the element's text.
|
||||
fn dir(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Dir, V, Rndr>> {
|
||||
fn dir(self, value: V) -> <Self as AddAnyAttr>::Output<Attr<Dir, V>> {
|
||||
self.add_any_attr(dir(value))
|
||||
}
|
||||
|
||||
@@ -216,7 +193,7 @@ where
|
||||
fn draggable(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Draggable, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Draggable, V>> {
|
||||
self.add_any_attr(draggable(value))
|
||||
}
|
||||
|
||||
@@ -224,31 +201,22 @@ where
|
||||
fn enterkeyhint(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Enterkeyhint, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Enterkeyhint, V>> {
|
||||
self.add_any_attr(enterkeyhint(value))
|
||||
}
|
||||
|
||||
/// The `hidden` global attribute is a Boolean attribute indicating that the element is not yet, or is no longer, relevant.
|
||||
fn hidden(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Hidden, V, Rndr>> {
|
||||
fn hidden(self, value: V) -> <Self as AddAnyAttr>::Output<Attr<Hidden, V>> {
|
||||
self.add_any_attr(hidden(value))
|
||||
}
|
||||
|
||||
/// The `id` global attribute defines a unique identifier (ID) which must be unique in the whole document.
|
||||
fn id(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Id, V, Rndr>> {
|
||||
fn id(self, value: V) -> <Self as AddAnyAttr>::Output<Attr<Id, V>> {
|
||||
self.add_any_attr(id(value))
|
||||
}
|
||||
|
||||
/// The `inert` global attribute is a Boolean attribute that makes an element behave inertly.
|
||||
fn inert(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Inert, V, Rndr>> {
|
||||
fn inert(self, value: V) -> <Self as AddAnyAttr>::Output<Attr<Inert, V>> {
|
||||
self.add_any_attr(inert(value))
|
||||
}
|
||||
|
||||
@@ -256,23 +224,17 @@ where
|
||||
fn inputmode(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Inputmode, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Inputmode, V>> {
|
||||
self.add_any_attr(inputmode(value))
|
||||
}
|
||||
|
||||
/// The `is` global attribute allows you to specify that a standard HTML element should behave like a custom built-in element.
|
||||
fn is(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Is, V, Rndr>> {
|
||||
fn is(self, value: V) -> <Self as AddAnyAttr>::Output<Attr<Is, V>> {
|
||||
self.add_any_attr(is(value))
|
||||
}
|
||||
|
||||
/// The `itemid` global attribute is used to specify the unique, global identifier of an item.
|
||||
fn itemid(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Itemid, V, Rndr>> {
|
||||
fn itemid(self, value: V) -> <Self as AddAnyAttr>::Output<Attr<Itemid, V>> {
|
||||
self.add_any_attr(itemid(value))
|
||||
}
|
||||
|
||||
@@ -280,7 +242,7 @@ where
|
||||
fn itemprop(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Itemprop, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Itemprop, V>> {
|
||||
self.add_any_attr(itemprop(value))
|
||||
}
|
||||
|
||||
@@ -288,7 +250,7 @@ where
|
||||
fn itemref(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Itemref, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Itemref, V>> {
|
||||
self.add_any_attr(itemref(value))
|
||||
}
|
||||
|
||||
@@ -296,7 +258,7 @@ where
|
||||
fn itemscope(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Itemscope, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Itemscope, V>> {
|
||||
self.add_any_attr(itemscope(value))
|
||||
}
|
||||
|
||||
@@ -304,31 +266,22 @@ where
|
||||
fn itemtype(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Itemtype, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Itemtype, V>> {
|
||||
self.add_any_attr(itemtype(value))
|
||||
}
|
||||
|
||||
/// The `lang` global attribute helps define the language of an element.
|
||||
fn lang(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Lang, V, Rndr>> {
|
||||
fn lang(self, value: V) -> <Self as AddAnyAttr>::Output<Attr<Lang, V>> {
|
||||
self.add_any_attr(lang(value))
|
||||
}
|
||||
|
||||
/// The `nonce` global attribute is used to specify a cryptographic nonce.
|
||||
fn nonce(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Nonce, V, Rndr>> {
|
||||
fn nonce(self, value: V) -> <Self as AddAnyAttr>::Output<Attr<Nonce, V>> {
|
||||
self.add_any_attr(nonce(value))
|
||||
}
|
||||
|
||||
/// The `part` global attribute identifies the element as a part of a component.
|
||||
fn part(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Part, V, Rndr>> {
|
||||
fn part(self, value: V) -> <Self as AddAnyAttr>::Output<Attr<Part, V>> {
|
||||
self.add_any_attr(part(value))
|
||||
}
|
||||
|
||||
@@ -336,23 +289,17 @@ where
|
||||
fn popover(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Popover, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Popover, V>> {
|
||||
self.add_any_attr(popover(value))
|
||||
}
|
||||
|
||||
/// The `role` global attribute defines the role of an element in ARIA.
|
||||
fn role(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Role, V, Rndr>> {
|
||||
fn role(self, value: V) -> <Self as AddAnyAttr>::Output<Attr<Role, V>> {
|
||||
self.add_any_attr(role(value))
|
||||
}
|
||||
|
||||
/// The `slot` global attribute assigns a slot in a shadow DOM.
|
||||
fn slot(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Slot, V, Rndr>> {
|
||||
fn slot(self, value: V) -> <Self as AddAnyAttr>::Output<Attr<Slot, V>> {
|
||||
self.add_any_attr(slot(value))
|
||||
}
|
||||
|
||||
@@ -360,7 +307,7 @@ where
|
||||
fn spellcheck(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Spellcheck, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Spellcheck, V>> {
|
||||
self.add_any_attr(spellcheck(value))
|
||||
}
|
||||
|
||||
@@ -368,15 +315,12 @@ where
|
||||
fn tabindex(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Tabindex, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Tabindex, V>> {
|
||||
self.add_any_attr(tabindex(value))
|
||||
}
|
||||
|
||||
/// The `title` global attribute contains text representing advisory information.
|
||||
fn title(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Title, V, Rndr>> {
|
||||
fn title(self, value: V) -> <Self as AddAnyAttr>::Output<Attr<Title, V>> {
|
||||
self.add_any_attr(title(value))
|
||||
}
|
||||
|
||||
@@ -384,7 +328,7 @@ where
|
||||
fn translate(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Translate, V, Rndr>> {
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Translate, V>> {
|
||||
self.add_any_attr(translate(value))
|
||||
}
|
||||
|
||||
@@ -392,20 +336,17 @@ where
|
||||
fn virtualkeyboardpolicy(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<Virtualkeyboardpolicy, V, Rndr>>
|
||||
{
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<Virtualkeyboardpolicy, V>> {
|
||||
self.add_any_attr(virtualkeyboardpolicy(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<El, At, Ch, Rndr, V> GlobalAttributes<Rndr, V>
|
||||
for HtmlElement<El, At, Ch, Rndr>
|
||||
impl<El, At, Ch, V> GlobalAttributes<V> for HtmlElement<El, At, Ch>
|
||||
where
|
||||
El: ElementType + CreateElement<Rndr> + Send,
|
||||
At: Attribute<Rndr> + Send,
|
||||
Ch: RenderHtml<Rndr> + Send,
|
||||
V: AttributeValue<Rndr>,
|
||||
Rndr: Renderer,
|
||||
El: ElementType + Send,
|
||||
At: Attribute + Send,
|
||||
Ch: RenderHtml + Send,
|
||||
V: AttributeValue,
|
||||
{
|
||||
}
|
||||
|
||||
@@ -418,7 +359,7 @@ macro_rules! on_definitions {
|
||||
fn $key(
|
||||
self,
|
||||
value: V,
|
||||
) -> <Self as AddAnyAttr<Rndr>>::Output<Attr<[<$key:camel>], V, Rndr>>
|
||||
) -> <Self as AddAnyAttr>::Output<Attr<[<$key:camel>], V>>
|
||||
{
|
||||
self.add_any_attr($key(value))
|
||||
}
|
||||
@@ -428,11 +369,10 @@ macro_rules! on_definitions {
|
||||
}
|
||||
|
||||
/// Provides methods for HTML event listener attributes.
|
||||
pub trait GlobalOnAttributes<Rndr, V>
|
||||
pub trait GlobalOnAttributes<V>
|
||||
where
|
||||
Self: Sized + AddAnyAttr<Rndr>,
|
||||
V: AttributeValue<Rndr>,
|
||||
Rndr: Renderer,
|
||||
Self: Sized + AddAnyAttr,
|
||||
V: AttributeValue,
|
||||
{
|
||||
on_definitions! {
|
||||
/// The `onabort` attribute specifies the event handler for the abort event.
|
||||
@@ -575,13 +515,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<El, At, Ch, Rndr, V> GlobalOnAttributes<Rndr, V>
|
||||
for HtmlElement<El, At, Ch, Rndr>
|
||||
impl<El, At, Ch, V> GlobalOnAttributes<V> for HtmlElement<El, At, Ch>
|
||||
where
|
||||
El: ElementType + CreateElement<Rndr> + Send,
|
||||
At: Attribute<Rndr> + Send,
|
||||
Ch: RenderHtml<Rndr> + Send,
|
||||
V: AttributeValue<Rndr>,
|
||||
Rndr: Renderer,
|
||||
El: ElementType + Send,
|
||||
At: Attribute + Send,
|
||||
Ch: RenderHtml + Send,
|
||||
V: AttributeValue,
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::{Attr, AttributeValue};
|
||||
use crate::renderer::Renderer;
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// An HTML attribute key.
|
||||
pub trait AttributeKey: Clone + Send + 'static {
|
||||
@@ -14,11 +13,11 @@ macro_rules! attributes {
|
||||
$(
|
||||
#[$meta]
|
||||
#[track_caller]
|
||||
pub fn $key<V, Rndr>(value: V) -> Attr<[<$key:camel>], V, Rndr>
|
||||
where V: AttributeValue<Rndr>,
|
||||
Rndr: Renderer
|
||||
pub fn $key<V>(value: V) -> Attr<[<$key:camel>], V>
|
||||
where V: AttributeValue,
|
||||
|
||||
{
|
||||
Attr([<$key:camel>], value, PhantomData)
|
||||
Attr([<$key:camel>], value)
|
||||
}
|
||||
|
||||
#[$meta]
|
||||
|
||||
@@ -8,28 +8,26 @@ pub mod custom;
|
||||
pub mod global;
|
||||
mod key;
|
||||
mod value;
|
||||
use crate::{
|
||||
renderer::Renderer,
|
||||
view::{Position, ToTemplate},
|
||||
};
|
||||
|
||||
use crate::view::{Position, ToTemplate};
|
||||
pub use key::*;
|
||||
use std::{fmt::Debug, future::Future, marker::PhantomData};
|
||||
use std::{fmt::Debug, future::Future};
|
||||
pub use value::*;
|
||||
|
||||
/// Defines an attribute: anything that can modify an element.
|
||||
pub trait Attribute<R: Renderer>: NextAttribute<R> + Send {
|
||||
pub trait Attribute: NextAttribute + Send {
|
||||
/// The minimum length of this attribute in HTML.
|
||||
const MIN_LENGTH: usize;
|
||||
|
||||
/// The state that should be retained between building and rebuilding.
|
||||
type State;
|
||||
/// The type once all async data have loaded.
|
||||
type AsyncOutput: Attribute<R>;
|
||||
type AsyncOutput: Attribute;
|
||||
/// An equivalent to this attribute that can be cloned to be shared across elements.
|
||||
type Cloneable: Attribute<R> + Clone;
|
||||
type Cloneable: Attribute + Clone;
|
||||
/// An equivalent to this attribute that can be cloned to be shared across elements, and
|
||||
/// captures no references shorter than `'static`.
|
||||
type CloneableOwned: Attribute<R> + Clone + 'static;
|
||||
type CloneableOwned: Attribute + Clone + 'static;
|
||||
|
||||
/// An approximation of the actual length of this attribute in HTML.
|
||||
fn html_len(&self) -> usize;
|
||||
@@ -49,10 +47,13 @@ pub trait Attribute<R: Renderer>: NextAttribute<R> + Send {
|
||||
|
||||
/// Adds interactivity as necessary, given DOM nodes that were created from HTML that has
|
||||
/// either been rendered on the server, or cloned for a `<template>`.
|
||||
fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State;
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State;
|
||||
|
||||
/// Adds this attribute to the element during client-side rendering.
|
||||
fn build(self, el: &R::Element) -> Self::State;
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State;
|
||||
|
||||
/// Applies a new value for the attribute.
|
||||
fn rebuild(self, state: &mut Self::State);
|
||||
@@ -75,21 +76,18 @@ pub trait Attribute<R: Renderer>: NextAttribute<R> + Send {
|
||||
/// Adds another attribute to this one, returning a new attribute.
|
||||
///
|
||||
/// This is typically achieved by creating or extending a tuple of attributes.
|
||||
pub trait NextAttribute<R: Renderer> {
|
||||
pub trait NextAttribute {
|
||||
/// The type of the new, combined attribute.
|
||||
type Output<NewAttr: Attribute<R>>: Attribute<R>;
|
||||
type Output<NewAttr: Attribute>: Attribute;
|
||||
|
||||
/// Adds a new attribute.
|
||||
fn add_any_attr<NewAttr: Attribute<R>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>;
|
||||
}
|
||||
|
||||
impl<R> Attribute<R> for ()
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
impl Attribute for () {
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
type State = ();
|
||||
@@ -110,10 +108,13 @@ where
|
||||
) {
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(self, _el: &R::Element) -> Self::State {
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
_el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
}
|
||||
|
||||
fn build(self, _el: &R::Element) -> Self::State {}
|
||||
fn build(self, _el: &crate::renderer::types::Element) -> Self::State {}
|
||||
|
||||
fn rebuild(self, _state: &mut Self::State) {}
|
||||
|
||||
@@ -130,13 +131,10 @@ where
|
||||
async fn resolve(self) -> Self::AsyncOutput {}
|
||||
}
|
||||
|
||||
impl<R> NextAttribute<R> for ()
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
type Output<NewAttr: Attribute<R>> = (NewAttr,);
|
||||
impl NextAttribute for () {
|
||||
type Output<NewAttr: Attribute> = (NewAttr,);
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<R>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
@@ -146,28 +144,25 @@ where
|
||||
|
||||
/// An attribute with a key and value.
|
||||
#[derive(Debug)]
|
||||
pub struct Attr<K, V, R>(pub K, pub V, pub PhantomData<R>)
|
||||
pub struct Attr<K, V>(pub K, pub V)
|
||||
where
|
||||
K: AttributeKey,
|
||||
V: AttributeValue<R>,
|
||||
R: Renderer;
|
||||
V: AttributeValue;
|
||||
|
||||
impl<K, V, R> Clone for Attr<K, V, R>
|
||||
impl<K, V> Clone for Attr<K, V>
|
||||
where
|
||||
K: AttributeKey,
|
||||
V: AttributeValue<R> + Clone,
|
||||
R: Renderer,
|
||||
V: AttributeValue + Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone(), self.1.clone(), PhantomData)
|
||||
Self(self.0.clone(), self.1.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, R> ToTemplate for Attr<K, V, R>
|
||||
impl<K, V> ToTemplate for Attr<K, V>
|
||||
where
|
||||
K: AttributeKey,
|
||||
V: AttributeValue<R>,
|
||||
R: Renderer,
|
||||
V: AttributeValue,
|
||||
{
|
||||
fn to_template(
|
||||
buf: &mut String,
|
||||
@@ -180,18 +175,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, R> Attribute<R> for Attr<K, V, R>
|
||||
impl<K, V> Attribute for Attr<K, V>
|
||||
where
|
||||
K: AttributeKey + Send,
|
||||
V: AttributeValue<R> + Send,
|
||||
R: Renderer,
|
||||
V: AttributeValue + Send,
|
||||
{
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
type State = V::State;
|
||||
type AsyncOutput = Attr<K, V::AsyncOutput, R>;
|
||||
type Cloneable = Attr<K, V::Cloneable, R>;
|
||||
type CloneableOwned = Attr<K, V::CloneableOwned, R>;
|
||||
type AsyncOutput = Attr<K, V::AsyncOutput>;
|
||||
type Cloneable = Attr<K, V::Cloneable>;
|
||||
type CloneableOwned = Attr<K, V::CloneableOwned>;
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
K::KEY.len() + 3 + self.1.html_len()
|
||||
@@ -207,11 +201,14 @@ where
|
||||
self.1.to_html(K::KEY, buf);
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State {
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
self.1.hydrate::<FROM_SERVER>(K::KEY, el)
|
||||
}
|
||||
|
||||
fn build(self, el: &R::Element) -> Self::State {
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
V::build(self.1, el, K::KEY)
|
||||
}
|
||||
|
||||
@@ -220,11 +217,11 @@ where
|
||||
}
|
||||
|
||||
fn into_cloneable(self) -> Self::Cloneable {
|
||||
Attr(self.0, self.1.into_cloneable(), PhantomData)
|
||||
Attr(self.0, self.1.into_cloneable())
|
||||
}
|
||||
|
||||
fn into_cloneable_owned(self) -> Self::CloneableOwned {
|
||||
Attr(self.0, self.1.into_cloneable_owned(), PhantomData)
|
||||
Attr(self.0, self.1.into_cloneable_owned())
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
@@ -232,19 +229,18 @@ where
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
Attr(self.0, self.1.resolve().await, PhantomData)
|
||||
Attr(self.0, self.1.resolve().await)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, R> NextAttribute<R> for Attr<K, V, R>
|
||||
impl<K, V> NextAttribute for Attr<K, V>
|
||||
where
|
||||
K: AttributeKey,
|
||||
V: AttributeValue<R>,
|
||||
R: Renderer,
|
||||
V: AttributeValue,
|
||||
{
|
||||
type Output<NewAttr: Attribute<R>> = (Self, NewAttr);
|
||||
type Output<NewAttr: Attribute> = (Self, NewAttr);
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<R>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
@@ -254,11 +250,11 @@ where
|
||||
|
||||
macro_rules! impl_attr_for_tuples {
|
||||
($first:ident, $($ty:ident),* $(,)?) => {
|
||||
impl<$first, $($ty),*, Rndr> Attribute<Rndr> for ($first, $($ty,)*)
|
||||
impl<$first, $($ty),*> Attribute for ($first, $($ty,)*)
|
||||
where
|
||||
$first: Attribute<Rndr>,
|
||||
$($ty: Attribute<Rndr>),*,
|
||||
Rndr: Renderer
|
||||
$first: Attribute,
|
||||
$($ty: Attribute),*,
|
||||
|
||||
{
|
||||
const MIN_LENGTH: usize = $first::MIN_LENGTH $(+ $ty::MIN_LENGTH)*;
|
||||
|
||||
@@ -280,7 +276,7 @@ macro_rules! impl_attr_for_tuples {
|
||||
$($ty.to_html(buf, class, style, inner_html));*
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(self, el: &Rndr::Element) -> Self::State {
|
||||
fn hydrate<const FROM_SERVER: bool>(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
#[allow(non_snake_case)]
|
||||
let ($first, $($ty,)* ) = self;
|
||||
(
|
||||
@@ -289,7 +285,7 @@ macro_rules! impl_attr_for_tuples {
|
||||
)
|
||||
}
|
||||
|
||||
fn build(self, el: &Rndr::Element) -> Self::State {
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
#[allow(non_snake_case)]
|
||||
let ($first, $($ty,)*) = self;
|
||||
(
|
||||
@@ -342,15 +338,15 @@ macro_rules! impl_attr_for_tuples {
|
||||
}
|
||||
}
|
||||
|
||||
impl<$first, $($ty),*, Rndr> NextAttribute<Rndr> for ($first, $($ty,)*)
|
||||
impl<$first, $($ty),*> NextAttribute for ($first, $($ty,)*)
|
||||
where
|
||||
$first: Attribute<Rndr>,
|
||||
$($ty: Attribute<Rndr>),*,
|
||||
Rndr: Renderer
|
||||
{
|
||||
type Output<NewAttr: Attribute<Rndr>> = ($first, $($ty,)* NewAttr);
|
||||
$first: Attribute,
|
||||
$($ty: Attribute),*,
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
{
|
||||
type Output<NewAttr: Attribute> = ($first, $($ty,)* NewAttr);
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
@@ -364,11 +360,11 @@ macro_rules! impl_attr_for_tuples {
|
||||
|
||||
macro_rules! impl_attr_for_tuples_truncate_additional {
|
||||
($first:ident, $($ty:ident),* $(,)?) => {
|
||||
impl<$first, $($ty),*, Rndr> Attribute<Rndr> for ($first, $($ty,)*)
|
||||
impl<$first, $($ty),*> Attribute for ($first, $($ty,)*)
|
||||
where
|
||||
$first: Attribute<Rndr>,
|
||||
$($ty: Attribute<Rndr>),*,
|
||||
Rndr: Renderer
|
||||
$first: Attribute,
|
||||
$($ty: Attribute),*,
|
||||
|
||||
{
|
||||
const MIN_LENGTH: usize = $first::MIN_LENGTH $(+ $ty::MIN_LENGTH)*;
|
||||
|
||||
@@ -390,7 +386,7 @@ macro_rules! impl_attr_for_tuples_truncate_additional {
|
||||
$($ty.to_html(buf, class, style, inner_html));*
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(self, el: &Rndr::Element) -> Self::State {
|
||||
fn hydrate<const FROM_SERVER: bool>(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
#[allow(non_snake_case)]
|
||||
let ($first, $($ty,)* ) = self;
|
||||
(
|
||||
@@ -399,7 +395,7 @@ macro_rules! impl_attr_for_tuples_truncate_additional {
|
||||
)
|
||||
}
|
||||
|
||||
fn build(self, el: &Rndr::Element) -> Self::State {
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
#[allow(non_snake_case)]
|
||||
let ($first, $($ty,)*) = self;
|
||||
(
|
||||
@@ -452,15 +448,15 @@ macro_rules! impl_attr_for_tuples_truncate_additional {
|
||||
}
|
||||
}
|
||||
|
||||
impl<$first, $($ty),*, Rndr> NextAttribute<Rndr> for ($first, $($ty,)*)
|
||||
impl<$first, $($ty),*> NextAttribute for ($first, $($ty,)*)
|
||||
where
|
||||
$first: Attribute<Rndr>,
|
||||
$($ty: Attribute<Rndr>),*,
|
||||
Rndr: Renderer
|
||||
{
|
||||
type Output<NewAttr: Attribute<Rndr>> = ($first, $($ty,)*);
|
||||
$first: Attribute,
|
||||
$($ty: Attribute),*,
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
{
|
||||
type Output<NewAttr: Attribute> = ($first, $($ty,)*);
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
_new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
@@ -471,10 +467,9 @@ macro_rules! impl_attr_for_tuples_truncate_additional {
|
||||
};
|
||||
}
|
||||
|
||||
impl<A, Rndr> Attribute<Rndr> for (A,)
|
||||
impl<A> Attribute for (A,)
|
||||
where
|
||||
A: Attribute<Rndr>,
|
||||
Rndr: Renderer,
|
||||
A: Attribute,
|
||||
{
|
||||
const MIN_LENGTH: usize = A::MIN_LENGTH;
|
||||
|
||||
@@ -499,12 +494,12 @@ where
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &Rndr::Element,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
self.0.hydrate::<FROM_SERVER>(el)
|
||||
}
|
||||
|
||||
fn build(self, el: &Rndr::Element) -> Self::State {
|
||||
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
|
||||
self.0.build(el)
|
||||
}
|
||||
|
||||
@@ -529,14 +524,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, Rndr> NextAttribute<Rndr> for (A,)
|
||||
impl<A> NextAttribute for (A,)
|
||||
where
|
||||
A: Attribute<Rndr>,
|
||||
Rndr: Renderer,
|
||||
A: Attribute,
|
||||
{
|
||||
type Output<NewAttr: Attribute<Rndr>> = (A, NewAttr);
|
||||
type Output<NewAttr: Attribute> = (A, NewAttr);
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
fn add_any_attr<NewAttr: Attribute>(
|
||||
self,
|
||||
new_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::renderer::Renderer;
|
||||
use crate::renderer::Rndr;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
future::Future,
|
||||
@@ -12,12 +12,12 @@ use std::{
|
||||
};
|
||||
|
||||
/// A possible value for an HTML attribute.
|
||||
pub trait AttributeValue<R: Renderer>: Send {
|
||||
pub trait AttributeValue: Send {
|
||||
/// The state that should be retained between building and rebuilding.
|
||||
type State;
|
||||
|
||||
/// The type once all async data have loaded.
|
||||
type AsyncOutput: AttributeValue<R>;
|
||||
type AsyncOutput: AttributeValue;
|
||||
|
||||
/// A version of the value that can be cloned. This can be the same type, or a
|
||||
/// reference-counted type. Generally speaking, this does *not* need to refer to the same data,
|
||||
@@ -25,12 +25,12 @@ pub trait AttributeValue<R: Renderer>: Send {
|
||||
/// probably make it reference-counted (so that a `FnMut()` continues mutating the same
|
||||
/// closure), but making a `String` cloneable does not necessarily need to make it an
|
||||
/// `Arc<str>`, as two different clones of a `String` will still have the same value.
|
||||
type Cloneable: AttributeValue<R> + Clone;
|
||||
type Cloneable: AttributeValue + Clone;
|
||||
|
||||
/// A cloneable type that is also `'static`. This is used for spreading across types when the
|
||||
/// spreadable attribute needs to be owned. In some cases (`&'a str` to `Arc<str>`, etc.) the owned
|
||||
/// cloneable type has worse performance than the cloneable type, so they are separate.
|
||||
type CloneableOwned: AttributeValue<R> + Clone + 'static;
|
||||
type CloneableOwned: AttributeValue + Clone + 'static;
|
||||
|
||||
/// An approximation of the actual length of this attribute in HTML.
|
||||
fn html_len(&self) -> usize;
|
||||
@@ -46,11 +46,15 @@ pub trait AttributeValue<R: Renderer>: Send {
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
key: &str,
|
||||
el: &R::Element,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State;
|
||||
|
||||
/// Adds this attribute to the element during client-side rendering.
|
||||
fn build(self, el: &R::Element, key: &str) -> Self::State;
|
||||
fn build(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
key: &str,
|
||||
) -> Self::State;
|
||||
|
||||
/// Applies a new value for the attribute.
|
||||
fn rebuild(self, key: &str, state: &mut Self::State);
|
||||
@@ -70,10 +74,7 @@ pub trait AttributeValue<R: Renderer>: Send {
|
||||
fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send;
|
||||
}
|
||||
|
||||
impl<R> AttributeValue<R> for ()
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
impl AttributeValue for () {
|
||||
type State = ();
|
||||
type AsyncOutput = ();
|
||||
type Cloneable = ();
|
||||
@@ -87,9 +88,19 @@ where
|
||||
|
||||
fn to_template(_key: &str, _buf: &mut String) {}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(self, _key: &str, _el: &R::Element) {}
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
_key: &str,
|
||||
_el: &crate::renderer::types::Element,
|
||||
) {
|
||||
}
|
||||
|
||||
fn build(self, _el: &R::Element, _key: &str) -> Self::State {}
|
||||
fn build(
|
||||
self,
|
||||
_el: &crate::renderer::types::Element,
|
||||
_key: &str,
|
||||
) -> Self::State {
|
||||
}
|
||||
|
||||
fn rebuild(self, _key: &str, _state: &mut Self::State) {}
|
||||
|
||||
@@ -106,11 +117,8 @@ where
|
||||
async fn resolve(self) {}
|
||||
}
|
||||
|
||||
impl<'a, R> AttributeValue<R> for &'a str
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
type State = (R::Element, &'a str);
|
||||
impl<'a> AttributeValue for &'a str {
|
||||
type State = (crate::renderer::types::Element, &'a str);
|
||||
type AsyncOutput = &'a str;
|
||||
type Cloneable = &'a str;
|
||||
type CloneableOwned = Arc<str>;
|
||||
@@ -132,25 +140,29 @@ where
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
key: &str,
|
||||
el: &R::Element,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
// if we're actually hydrating from SSRed HTML, we don't need to set the attribute
|
||||
// if we're hydrating from a CSR-cloned <template>, we do need to set non-StaticAttr attributes
|
||||
if !FROM_SERVER {
|
||||
R::set_attribute(el, key, self);
|
||||
Rndr::set_attribute(el, key, self);
|
||||
}
|
||||
(el.clone(), self)
|
||||
}
|
||||
|
||||
fn build(self, el: &R::Element, key: &str) -> Self::State {
|
||||
R::set_attribute(el, key, self);
|
||||
fn build(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
key: &str,
|
||||
) -> Self::State {
|
||||
Rndr::set_attribute(el, key, self);
|
||||
(el.to_owned(), self)
|
||||
}
|
||||
|
||||
fn rebuild(self, key: &str, state: &mut Self::State) {
|
||||
let (el, prev_value) = state;
|
||||
if self != *prev_value {
|
||||
R::set_attribute(el, key, self);
|
||||
Rndr::set_attribute(el, key, self);
|
||||
}
|
||||
*prev_value = self;
|
||||
}
|
||||
@@ -171,10 +183,8 @@ where
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<R, const V: &'static str> AttributeValue<R>
|
||||
impl<const V: &'static str> AttributeValue
|
||||
for crate::view::static_types::Static<V>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
type State = ();
|
||||
@@ -186,7 +196,7 @@ where
|
||||
}
|
||||
|
||||
fn to_html(self, key: &str, buf: &mut String) {
|
||||
<&str as AttributeValue<R>>::to_html(V, key, buf);
|
||||
<&str as AttributeValue>::to_html(V, key, buf);
|
||||
}
|
||||
|
||||
fn to_template(key: &str, buf: &mut String) {
|
||||
@@ -200,12 +210,16 @@ where
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
_key: &str,
|
||||
_el: &R::Element,
|
||||
_el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
}
|
||||
|
||||
fn build(self, el: &R::Element, key: &str) -> Self::State {
|
||||
<&str as AttributeValue<R>>::build(V, el, key);
|
||||
fn build(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
key: &str,
|
||||
) -> Self::State {
|
||||
<&str as AttributeValue>::build(V, el, key);
|
||||
}
|
||||
|
||||
fn rebuild(self, _key: &str, _state: &mut Self::State) {}
|
||||
@@ -225,12 +239,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R> AttributeValue<R> for &'a String
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
impl<'a> AttributeValue for &'a String {
|
||||
type AsyncOutput = Self;
|
||||
type State = (R::Element, &'a String);
|
||||
type State = (crate::renderer::types::Element, &'a String);
|
||||
type Cloneable = Self;
|
||||
type CloneableOwned = Arc<str>;
|
||||
|
||||
@@ -239,7 +250,7 @@ where
|
||||
}
|
||||
|
||||
fn to_html(self, key: &str, buf: &mut String) {
|
||||
<&str as AttributeValue<R>>::to_html(self.as_str(), key, buf);
|
||||
<&str as AttributeValue>::to_html(self.as_str(), key, buf);
|
||||
}
|
||||
|
||||
fn to_template(_key: &str, _buf: &mut String) {}
|
||||
@@ -247,9 +258,9 @@ where
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
key: &str,
|
||||
el: &R::Element,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
let (el, _) = <&str as AttributeValue<R>>::hydrate::<FROM_SERVER>(
|
||||
let (el, _) = <&str as AttributeValue>::hydrate::<FROM_SERVER>(
|
||||
self.as_str(),
|
||||
key,
|
||||
el,
|
||||
@@ -257,15 +268,19 @@ where
|
||||
(el, self)
|
||||
}
|
||||
|
||||
fn build(self, el: &R::Element, key: &str) -> Self::State {
|
||||
R::set_attribute(el, key, self);
|
||||
fn build(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
key: &str,
|
||||
) -> Self::State {
|
||||
Rndr::set_attribute(el, key, self);
|
||||
(el.clone(), self)
|
||||
}
|
||||
|
||||
fn rebuild(self, key: &str, state: &mut Self::State) {
|
||||
let (el, prev_value) = state;
|
||||
if self != *prev_value {
|
||||
R::set_attribute(el, key, self);
|
||||
Rndr::set_attribute(el, key, self);
|
||||
}
|
||||
*prev_value = self;
|
||||
}
|
||||
@@ -285,12 +300,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> AttributeValue<R> for String
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
impl AttributeValue for String {
|
||||
type AsyncOutput = Self;
|
||||
type State = (R::Element, String);
|
||||
type State = (crate::renderer::types::Element, String);
|
||||
type Cloneable = Arc<str>;
|
||||
type CloneableOwned = Arc<str>;
|
||||
|
||||
@@ -299,7 +311,7 @@ where
|
||||
}
|
||||
|
||||
fn to_html(self, key: &str, buf: &mut String) {
|
||||
<&str as AttributeValue<R>>::to_html(self.as_str(), key, buf);
|
||||
<&str as AttributeValue>::to_html(self.as_str(), key, buf);
|
||||
}
|
||||
|
||||
fn to_template(_key: &str, _buf: &mut String) {}
|
||||
@@ -307,9 +319,9 @@ where
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
key: &str,
|
||||
el: &R::Element,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
let (el, _) = <&str as AttributeValue<R>>::hydrate::<FROM_SERVER>(
|
||||
let (el, _) = <&str as AttributeValue>::hydrate::<FROM_SERVER>(
|
||||
self.as_str(),
|
||||
key,
|
||||
el,
|
||||
@@ -317,15 +329,19 @@ where
|
||||
(el, self)
|
||||
}
|
||||
|
||||
fn build(self, el: &R::Element, key: &str) -> Self::State {
|
||||
R::set_attribute(el, key, &self);
|
||||
fn build(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
key: &str,
|
||||
) -> Self::State {
|
||||
Rndr::set_attribute(el, key, &self);
|
||||
(el.clone(), self)
|
||||
}
|
||||
|
||||
fn rebuild(self, key: &str, state: &mut Self::State) {
|
||||
let (el, prev_value) = state;
|
||||
if self != *prev_value {
|
||||
R::set_attribute(el, key, &self);
|
||||
Rndr::set_attribute(el, key, &self);
|
||||
}
|
||||
*prev_value = self;
|
||||
}
|
||||
@@ -345,12 +361,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> AttributeValue<R> for Arc<str>
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
impl AttributeValue for Arc<str> {
|
||||
type AsyncOutput = Self;
|
||||
type State = (R::Element, Arc<str>);
|
||||
type State = (crate::renderer::types::Element, Arc<str>);
|
||||
type Cloneable = Arc<str>;
|
||||
type CloneableOwned = Arc<str>;
|
||||
|
||||
@@ -359,7 +372,7 @@ where
|
||||
}
|
||||
|
||||
fn to_html(self, key: &str, buf: &mut String) {
|
||||
<&str as AttributeValue<R>>::to_html(self.as_ref(), key, buf);
|
||||
<&str as AttributeValue>::to_html(self.as_ref(), key, buf);
|
||||
}
|
||||
|
||||
fn to_template(_key: &str, _buf: &mut String) {}
|
||||
@@ -367,9 +380,9 @@ where
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
key: &str,
|
||||
el: &R::Element,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
let (el, _) = <&str as AttributeValue<R>>::hydrate::<FROM_SERVER>(
|
||||
let (el, _) = <&str as AttributeValue>::hydrate::<FROM_SERVER>(
|
||||
self.as_ref(),
|
||||
key,
|
||||
el,
|
||||
@@ -377,15 +390,19 @@ where
|
||||
(el, self)
|
||||
}
|
||||
|
||||
fn build(self, el: &R::Element, key: &str) -> Self::State {
|
||||
R::set_attribute(el, key, &self);
|
||||
fn build(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
key: &str,
|
||||
) -> Self::State {
|
||||
Rndr::set_attribute(el, key, &self);
|
||||
(el.clone(), self)
|
||||
}
|
||||
|
||||
fn rebuild(self, key: &str, state: &mut Self::State) {
|
||||
let (el, prev_value) = state;
|
||||
if self != *prev_value {
|
||||
R::set_attribute(el, key, &self);
|
||||
Rndr::set_attribute(el, key, &self);
|
||||
}
|
||||
*prev_value = self;
|
||||
}
|
||||
@@ -406,12 +423,9 @@ where
|
||||
}
|
||||
// TODO impl AttributeValue for Rc<str> and Arc<str> too
|
||||
|
||||
impl<R> AttributeValue<R> for bool
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
impl AttributeValue for bool {
|
||||
type AsyncOutput = Self;
|
||||
type State = (R::Element, bool);
|
||||
type State = (crate::renderer::types::Element, bool);
|
||||
type Cloneable = Self;
|
||||
type CloneableOwned = Self;
|
||||
|
||||
@@ -431,19 +445,23 @@ where
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
key: &str,
|
||||
el: &R::Element,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
// if we're actually hydrating from SSRed HTML, we don't need to set the attribute
|
||||
// if we're hydrating from a CSR-cloned <template>, we do need to set non-StaticAttr attributes
|
||||
if !FROM_SERVER {
|
||||
R::set_attribute(el, key, "");
|
||||
Rndr::set_attribute(el, key, "");
|
||||
}
|
||||
(el.clone(), self)
|
||||
}
|
||||
|
||||
fn build(self, el: &R::Element, key: &str) -> Self::State {
|
||||
fn build(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
key: &str,
|
||||
) -> Self::State {
|
||||
if self {
|
||||
R::set_attribute(el, key, "");
|
||||
Rndr::set_attribute(el, key, "");
|
||||
}
|
||||
(el.clone(), self)
|
||||
}
|
||||
@@ -452,9 +470,9 @@ where
|
||||
let (el, prev_value) = state;
|
||||
if self != *prev_value {
|
||||
if self {
|
||||
R::set_attribute(el, key, "");
|
||||
Rndr::set_attribute(el, key, "");
|
||||
} else {
|
||||
R::remove_attribute(el, key);
|
||||
Rndr::remove_attribute(el, key);
|
||||
}
|
||||
}
|
||||
*prev_value = self;
|
||||
@@ -475,13 +493,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, R> AttributeValue<R> for Option<V>
|
||||
impl<V> AttributeValue for Option<V>
|
||||
where
|
||||
V: AttributeValue<R>,
|
||||
R: Renderer,
|
||||
V: AttributeValue,
|
||||
{
|
||||
type AsyncOutput = Option<V::AsyncOutput>;
|
||||
type State = (R::Element, Option<V::State>);
|
||||
type State = (crate::renderer::types::Element, Option<V::State>);
|
||||
type Cloneable = Option<V::Cloneable>;
|
||||
type CloneableOwned = Option<V::CloneableOwned>;
|
||||
|
||||
@@ -503,13 +520,17 @@ where
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
key: &str,
|
||||
el: &R::Element,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
let state = self.map(|v| v.hydrate::<FROM_SERVER>(key, el));
|
||||
(el.clone(), state)
|
||||
}
|
||||
|
||||
fn build(self, el: &R::Element, key: &str) -> Self::State {
|
||||
fn build(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
key: &str,
|
||||
) -> Self::State {
|
||||
let el = el.clone();
|
||||
let v = self.map(|v| v.build(&el, key));
|
||||
(el, v)
|
||||
@@ -520,7 +541,7 @@ where
|
||||
match (self, prev.as_mut()) {
|
||||
(None, None) => {}
|
||||
(None, Some(_)) => {
|
||||
R::remove_attribute(el, key);
|
||||
Rndr::remove_attribute(el, key);
|
||||
*prev = None;
|
||||
}
|
||||
(Some(value), None) => {
|
||||
@@ -561,12 +582,12 @@ pub(crate) fn escape_attr(value: &str) -> Cow<'_, str> {
|
||||
macro_rules! render_primitive {
|
||||
($($child_type:ty),* $(,)?) => {
|
||||
$(
|
||||
impl<R> AttributeValue<R> for $child_type
|
||||
impl AttributeValue for $child_type
|
||||
where
|
||||
R: Renderer,
|
||||
|
||||
{
|
||||
type AsyncOutput = $child_type;
|
||||
type State = (R::Element, $child_type);
|
||||
type State = (crate::renderer::types::Element, $child_type);
|
||||
type Cloneable = Self;
|
||||
type CloneableOwned = Self;
|
||||
|
||||
@@ -575,7 +596,7 @@ macro_rules! render_primitive {
|
||||
}
|
||||
|
||||
fn to_html(self, key: &str, buf: &mut String) {
|
||||
<String as AttributeValue<R>>::to_html(self.to_string(), key, buf);
|
||||
<String as AttributeValue>::to_html(self.to_string(), key, buf);
|
||||
}
|
||||
|
||||
fn to_template(_key: &str, _buf: &mut String) {}
|
||||
@@ -583,25 +604,25 @@ macro_rules! render_primitive {
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
key: &str,
|
||||
el: &R::Element,
|
||||
el: &crate::renderer::types::Element,
|
||||
) -> Self::State {
|
||||
// if we're actually hydrating from SSRed HTML, we don't need to set the attribute
|
||||
// if we're hydrating from a CSR-cloned <template>, we do need to set non-StaticAttr attributes
|
||||
if !FROM_SERVER {
|
||||
R::set_attribute(el, key, &self.to_string());
|
||||
Rndr::set_attribute(el, key, &self.to_string());
|
||||
}
|
||||
(el.clone(), self)
|
||||
}
|
||||
|
||||
fn build(self, el: &R::Element, key: &str) -> Self::State {
|
||||
R::set_attribute(el, key, &self.to_string());
|
||||
fn build(self, el: &crate::renderer::types::Element, key: &str) -> Self::State {
|
||||
Rndr::set_attribute(el, key, &self.to_string());
|
||||
(el.to_owned(), self)
|
||||
}
|
||||
|
||||
fn rebuild(self, key: &str, state: &mut Self::State) {
|
||||
let (el, prev_value) = state;
|
||||
if self != *prev_value {
|
||||
R::set_attribute(el, key, &self.to_string());
|
||||
Rndr::set_attribute(el, key, &self.to_string());
|
||||
}
|
||||
*prev_value = self;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user