Compare commits

..

33 Commits
ci ... 3024

Author SHA1 Message Date
Greg Johnston
565a901c08 undo rename so I don't have to break all the rest 2024-09-26 16:56:48 -04:00
Greg Johnston
f626ac26e0 change: rename leptos::spawn to leptos::task and reexport scoped spawners 2024-09-26 16:25:59 -04:00
Greg Johnston
fb3159d093 fix: properly scope Suspend rebuilds within ownership tree (closes #3024) 2024-09-26 16:14:36 -04:00
zakstucke
67845be161 feat: read and write for StoredValue (#3027) 2024-09-26 15:46:09 -04:00
Greg Johnston
2bf1f46b88 fix: add correct namespaces for SVG and MathML elements (closes #3021) (#3025) 2024-09-25 19:42:42 -04:00
VincentBerthier
70ae3a0abb fix: island hydration warning message (#3023) 2024-09-25 15:38:04 -04:00
Greg Johnston
96e2b5cba1 fix: abort Suspend on cleanup, to avoid using disposed items (closes #3012) (#3019) 2024-09-24 21:45:39 -04:00
Greg Johnston
ef27a198d9 fix: correct Owner restoration in FlatRoutes (closes #3014) (#3018) 2024-09-24 21:45:30 -04:00
Greg Johnston
47299926bb fix: suppress resource loading during route generation (closes #3013) (#3017) 2024-09-24 21:45:22 -04:00
Greg Johnston
bdc2285658 change: remove generic rendering (#3015) 2024-09-24 17:01:57 -04:00
zakstucke
9d4ce6e526 Fix custom root when not using islands. (#3016) 2024-09-24 11:42:54 -07:00
Greg Johnston
3d2cdc21a1 beta6 2024-09-23 08:57:44 -04:00
Greg Johnston
93d939aef8 Merge pull request #3009 from leptos-rs/stores-patch
Stores patching
2024-09-22 21:45:10 -04:00
Greg Johnston
fb04750607 example: add example of patching fields 2024-09-22 19:52:48 -04:00
Greg Johnston
a080496e7e fix: notify correctly when patching stores 2024-09-22 19:51:50 -04:00
Greg Johnston
9fc1002167 perf: do not notify keyed iterator when only an inner field has changed 2024-09-22 19:50:30 -04:00
Greg Johnston
bc5c766530 fix: notify stores when modifying parent, without notifying siblings 2024-09-22 19:33:14 -04:00
Greg Johnston
17821f863a fix: restructure StoredValue so that nested calls do not deadlock (closes #2968) (#3004) 2024-09-22 19:21:49 -04:00
Baptiste
1ca4f34ef3 fix: optional props with islands (#3005) 2024-09-21 18:13:58 -04:00
Greg Johnston
8f0a1554b1 Merge pull request #3000 from leptos-rs/add-attr-any-view
Doing chores
2024-09-21 08:51:32 -04:00
Greg Johnston
38d4f26d03 fix: snip infinite type recursion 2024-09-21 07:51:27 -04:00
Greg Johnston
2b04c2710d Merge pull request #3002 from leptos-rs/fix-stores
Fix stores
2024-09-21 07:37:54 -04:00
Greg Johnston
a4937a1236 fix: correctly triggering parents vs siblings 2024-09-20 21:25:42 -04:00
Greg Johnston
f6f2c39686 chore: remove UntrackedWriter 2024-09-20 20:47:46 -04:00
Greg Johnston
d7eacf1ab5 chore: remove unused DocumentFragment mounting code 2024-09-20 20:41:51 -04:00
Greg Johnston
d1a4bbe28e chore: fix adding attr todo for static types 2024-09-20 20:41:51 -04:00
Greg Johnston
412ecd6b1b chore: remove dead iterator wrapper code 2024-09-20 20:41:51 -04:00
Greg Johnston
9bc0152121 chore: clean up todos in template SSR code 2024-09-20 20:41:51 -04:00
Greg Johnston
4b05cada8f chore: remove dead code for rendering guards 2024-09-20 20:41:51 -04:00
Greg Johnston
a818862704 chore: tidy up todos on EitherKeepAlive 2024-09-20 20:41:51 -04:00
Greg Johnston
173487debc fix: add add_any_attr() for AnyView<R> 2024-09-20 20:41:51 -04:00
Greg Johnston
449d96cc9a feat: add wrapper to support add_any_attr() fixes 2024-09-20 20:41:51 -04:00
Greg Johnston
f9bf6a95ed Merge pull request #3001 from leptos-rs/ci
Fix CI
2024-09-20 20:40:53 -04:00
125 changed files with 3719 additions and 5236 deletions

View File

@@ -40,36 +40,36 @@ members = [
exclude = ["benchmarks", "examples", "projects"]
[workspace.package]
version = "0.7.0-beta5"
version = "0.7.0-beta6"
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-beta6" }
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-beta6" }
leptos = { path = "./leptos", version = "0.7.0-beta6" }
leptos_config = { path = "./leptos_config", version = "0.7.0-beta6" }
leptos_dom = { path = "./leptos_dom", version = "0.7.0-beta6" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-beta6" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-beta6" }
leptos_macro = { path = "./leptos_macro", version = "0.7.0-beta6" }
leptos_router = { path = "./router", version = "0.7.0-beta6" }
leptos_router_macro = { path = "./router_macro", version = "0.7.0-beta6" }
leptos_server = { path = "./leptos_server", version = "0.7.0-beta6" }
leptos_meta = { path = "./meta", version = "0.7.0-beta6" }
next_tuple = { path = "./next_tuple", version = "0.1.0-beta6" }
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-beta6" }
reactive_stores = { path = "./reactive_stores", version = "0.1.0-beta6" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-beta6" }
server_fn = { path = "./server_fn", version = "0.7.0-beta6" }
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-beta6" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-beta6" }
tachys = { path = "./tachys", version = "0.1.0-beta6" }
[profile.release]
codegen-units = 1

View File

@@ -1,6 +1,6 @@
[package]
name = "throw_error"
version = "0.2.0-beta5"
version = "0.2.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -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"] }

View File

@@ -1 +0,0 @@
extend = [{ path = "../cargo-make/main.toml" }]

View File

@@ -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>

View File

@@ -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
);
}

View File

@@ -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,
);
}

View File

View File

@@ -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."/>

View File

@@ -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>,

View File

@@ -1,6 +1,6 @@
[package]
name = "hydration_context"
version = "0.2.0-beta5"
version = "0.2.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -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)()
}
}

View File

@@ -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);

View File

@@ -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 {
});
}
}
*/

View File

@@ -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();
});
})
})
})

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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>");
}

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_macro"
version = "0.7.0-beta5"
version = "0.7.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -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! {}

View File

@@ -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 }))

View File

@@ -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>,
@@ -116,7 +144,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
}
}
}
};

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.7.0-beta5"
version = "0.7.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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 {
}

View File

@@ -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
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "next_tuple"
version = "0.1.0-beta5"
version = "0.1.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_graph"
version = "0.1.0-beta5"
version = "0.1.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -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,
@@ -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>,
}
@@ -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)]
@@ -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)]

View File

@@ -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,
@@ -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>,
}
@@ -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(),
}
@@ -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));
}
}
@@ -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));
}
}

View File

@@ -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,
@@ -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),
),
}

View File

@@ -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,
@@ -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),
}
}
}
@@ -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)),
}
}
@@ -202,7 +202,7 @@ where
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,
)),
}
@@ -229,7 +229,7 @@ where
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)),
}
}
}

View File

@@ -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;
@@ -40,7 +40,7 @@ use std::{
/// # use reactive_graph::signal::*;
/// # 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();
@@ -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 }
@@ -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 }

View File

@@ -87,6 +87,7 @@ pub mod traits;
pub mod transition;
pub mod wrappers;
use computed::ScopedFuture;
pub use graph::untrack;
#[cfg(feature = "nightly")]
@@ -130,3 +131,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;
});
}

View File

@@ -13,24 +13,25 @@ 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;
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
@@ -209,7 +210,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();
}

View File

@@ -124,7 +124,7 @@ pub mod sandboxed {
}
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| {

View 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));
}
}

View 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),
}
})
}
}

View File

@@ -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
@@ -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`].
@@ -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.
@@ -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`].
@@ -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,
{
@@ -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();
}
}

View File

@@ -56,7 +56,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 signals 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

View File

@@ -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 signals 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

View File

@@ -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,
};
@@ -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),
}
}
}

View File

@@ -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,7 +63,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 signals 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
///
@@ -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),
}
}
}

View File

@@ -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()),
}
}
}

View File

@@ -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,7 +28,7 @@ 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 signals 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
/// ```
@@ -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()
}
}

View File

@@ -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,
@@ -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>
@@ -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(),
}
@@ -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,
};
@@ -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,
}

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores"
version = "0.1.0-beta5"
version = "0.1.0-beta6"
rust-version.workspace = true
edition.workspace = true

View File

@@ -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();
}
}

View File

@@ -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()),
}
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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]

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores_macro"
version = "0.1.0-beta5"
version = "0.1.0-beta6"
rust-version.workspace = true
edition.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.7.0-beta5"
version = "0.7.0-beta6"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

View File

@@ -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,

View File

@@ -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)
}
})
}));

View File

@@ -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

View File

@@ -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,)*>;

View File

@@ -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("/"), || ()),

View File

@@ -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;

View File

@@ -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,)*>;

View File

@@ -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, ..

View File

@@ -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)
}
}

View File

@@ -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 {

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router_macro"
version = "0.7.0-beta5"
version = "0.7.0-beta6"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

View File

@@ -1,6 +1,6 @@
[package]
name = "tachys"
version = "0.1.0-beta5"
version = "0.1.0-beta6"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -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)
}

View File

@@ -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,
{
}

View File

@@ -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,
{
}

View File

@@ -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,
{
}

View File

@@ -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]

View File

@@ -8,28 +8,25 @@ 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 +46,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 +75,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 +107,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 +130,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 +143,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 +174,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 +200,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 +216,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 +228,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 +249,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 +275,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 +284,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 +337,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 +359,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 +385,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 +394,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 +447,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 +466,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 +493,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 +523,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> {

View File

@@ -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;
}

View File

@@ -1,53 +1,46 @@
use super::attribute::{Attribute, NextAttribute};
use crate::{
renderer::DomRenderer,
renderer::Rndr,
view::{Position, ToTemplate},
};
use std::{future::Future, marker::PhantomData, sync::Arc};
use std::{future::Future, sync::Arc};
/// Adds a CSS class.
#[inline(always)]
pub fn class<C, R>(class: C) -> Class<C, R>
pub fn class<C>(class: C) -> Class<C>
where
C: IntoClass<R>,
R: DomRenderer,
C: IntoClass,
{
Class {
class,
rndr: PhantomData,
}
Class { class }
}
/// A CSS class.
#[derive(Debug)]
pub struct Class<C, R> {
pub struct Class<C> {
class: C,
rndr: PhantomData<R>,
}
impl<C, R> Clone for Class<C, R>
impl<C> Clone for Class<C>
where
C: Clone,
{
fn clone(&self) -> Self {
Self {
class: self.class.clone(),
rndr: PhantomData,
}
}
}
impl<C, R> Attribute<R> for Class<C, R>
impl<C> Attribute for Class<C>
where
C: IntoClass<R>,
R: DomRenderer,
C: IntoClass,
{
const MIN_LENGTH: usize = C::MIN_LENGTH;
type AsyncOutput = Class<C::AsyncOutput, R>;
type AsyncOutput = Class<C::AsyncOutput>;
type State = C::State;
type Cloneable = Class<C::Cloneable, R>;
type CloneableOwned = Class<C::CloneableOwned, R>;
type Cloneable = Class<C::Cloneable>;
type CloneableOwned = Class<C::CloneableOwned>;
fn html_len(&self) -> usize {
self.class.html_len() + 1
@@ -64,11 +57,14 @@ where
self.class.to_html(class);
}
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.class.hydrate::<FROM_SERVER>(el)
}
fn build(self, el: &R::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
self.class.build(el)
}
@@ -79,14 +75,12 @@ where
fn into_cloneable(self) -> Self::Cloneable {
Class {
class: self.class.into_cloneable(),
rndr: self.rndr,
}
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
Class {
class: self.class.into_cloneable_owned(),
rndr: self.rndr,
}
}
@@ -97,19 +91,17 @@ where
async fn resolve(self) -> Self::AsyncOutput {
Class {
class: self.class.resolve().await,
rndr: self.rndr,
}
}
}
impl<C, R> NextAttribute<R> for Class<C, R>
impl<C> NextAttribute for Class<C>
where
C: IntoClass<R>,
R: DomRenderer,
C: IntoClass,
{
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> {
@@ -117,10 +109,9 @@ where
}
}
impl<C, R> ToTemplate for Class<C, R>
impl<C> ToTemplate for Class<C>
where
C: IntoClass<R>,
R: DomRenderer,
C: IntoClass,
{
const CLASS: &'static str = C::TEMPLATE;
@@ -136,20 +127,20 @@ where
}
/// A possible value for a CSS class.
pub trait IntoClass<R: DomRenderer>: Send {
pub trait IntoClass: Send {
/// The HTML that should be included in a `<template>`.
const TEMPLATE: &'static str = "";
/// The minimum length of the HTML.
const MIN_LENGTH: usize = Self::TEMPLATE.len();
/// The type after all async data have resolved.
type AsyncOutput: IntoClass<R>;
type AsyncOutput: IntoClass;
/// The view state retained between building and rebuilding.
type State;
/// An equivalent value that can be cloned.
type Cloneable: IntoClass<R> + Clone;
type Cloneable: IntoClass + Clone;
/// An equivalent value that can be cloned and is `'static`.
type CloneableOwned: IntoClass<R> + Clone + 'static;
type CloneableOwned: IntoClass + Clone + 'static;
/// The estimated length of the HTML.
fn html_len(&self) -> usize;
@@ -163,10 +154,13 @@ pub trait IntoClass<R: DomRenderer>: 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 class 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;
/// Updates the value.
fn rebuild(self, state: &mut Self::State);
@@ -186,12 +180,9 @@ pub trait IntoClass<R: DomRenderer>: Send {
fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send;
}
impl<'a, R> IntoClass<R> for &'a str
where
R: DomRenderer,
{
impl<'a> IntoClass for &'a str {
type AsyncOutput = Self;
type State = (R::Element, Self);
type State = (crate::renderer::types::Element, Self);
type Cloneable = Self;
type CloneableOwned = Arc<str>;
@@ -203,22 +194,25 @@ where
class.push_str(self);
}
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 !FROM_SERVER {
R::set_attribute(el, "class", self);
Rndr::set_attribute(el, "class", self);
}
(el.clone(), self)
}
fn build(self, el: &R::Element) -> Self::State {
R::set_attribute(el, "class", self);
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_attribute(el, "class", self);
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
let (el, prev) = state;
if self != *prev {
R::set_attribute(el, "class", self);
Rndr::set_attribute(el, "class", self);
}
*prev = self;
}
@@ -238,12 +232,9 @@ where
}
}
impl<R> IntoClass<R> for String
where
R: DomRenderer,
{
impl IntoClass for String {
type AsyncOutput = Self;
type State = (R::Element, Self);
type State = (crate::renderer::types::Element, Self);
type Cloneable = Arc<str>;
type CloneableOwned = Arc<str>;
@@ -252,25 +243,28 @@ where
}
fn to_html(self, class: &mut String) {
IntoClass::<R>::to_html(self.as_str(), class);
IntoClass::to_html(self.as_str(), class);
}
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 !FROM_SERVER {
R::set_attribute(el, "class", &self);
Rndr::set_attribute(el, "class", &self);
}
(el.clone(), self)
}
fn build(self, el: &R::Element) -> Self::State {
R::set_attribute(el, "class", &self);
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_attribute(el, "class", &self);
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
let (el, prev) = state;
if self != *prev {
R::set_attribute(el, "class", &self);
Rndr::set_attribute(el, "class", &self);
}
*prev = self;
}
@@ -290,12 +284,9 @@ where
}
}
impl<R> IntoClass<R> for Arc<str>
where
R: DomRenderer,
{
impl IntoClass for Arc<str> {
type AsyncOutput = Self;
type State = (R::Element, Self);
type State = (crate::renderer::types::Element, Self);
type Cloneable = Self;
type CloneableOwned = Self;
@@ -304,25 +295,28 @@ where
}
fn to_html(self, class: &mut String) {
IntoClass::<R>::to_html(self.as_ref(), class);
IntoClass::to_html(self.as_ref(), class);
}
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 !FROM_SERVER {
R::set_attribute(el, "class", &self);
Rndr::set_attribute(el, "class", &self);
}
(el.clone(), self)
}
fn build(self, el: &R::Element) -> Self::State {
R::set_attribute(el, "class", &self);
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_attribute(el, "class", &self);
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
let (el, prev) = state;
if !Arc::ptr_eq(&self, prev) {
R::set_attribute(el, "class", &self);
Rndr::set_attribute(el, "class", &self);
}
*prev = self;
}
@@ -342,12 +336,9 @@ where
}
}
impl<R> IntoClass<R> for (&'static str, bool)
where
R: DomRenderer,
{
impl IntoClass for (&'static str, bool) {
type AsyncOutput = Self;
type State = (R::ClassList, bool);
type State = (crate::renderer::types::ClassList, bool);
type Cloneable = Self;
type CloneableOwned = Self;
@@ -362,20 +353,23 @@ 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 {
let (name, include) = self;
let class_list = R::class_list(el);
let class_list = Rndr::class_list(el);
if !FROM_SERVER && include {
R::add_class(&class_list, name);
Rndr::add_class(&class_list, name);
}
(class_list, self.1)
}
fn build(self, el: &R::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
let (name, include) = self;
let class_list = R::class_list(el);
let class_list = Rndr::class_list(el);
if include {
R::add_class(&class_list, name);
Rndr::add_class(&class_list, name);
}
(class_list, self.1)
}
@@ -385,9 +379,9 @@ where
let (class_list, prev_include) = state;
if include != *prev_include {
if include {
R::add_class(class_list, name);
Rndr::add_class(class_list, name);
} else {
R::remove_class(class_list, name);
Rndr::remove_class(class_list, name);
}
}
*prev_include = include;
@@ -409,11 +403,7 @@ where
}
#[cfg(feature = "nightly")]
impl<R, const V: &'static str> IntoClass<R>
for crate::view::static_types::Static<V>
where
R: DomRenderer,
{
impl<const V: &'static str> IntoClass for crate::view::static_types::Static<V> {
const TEMPLATE: &'static str = V;
type AsyncOutput = Self;
@@ -433,11 +423,14 @@ where
class.push_str(V);
}
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 {
R::set_attribute(el, "class", V);
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_attribute(el, "class", V);
}
fn rebuild(self, _state: &mut Self::State) {}

View File

@@ -1,7 +1,6 @@
use super::attribute::{Attribute, NextAttribute};
use crate::{
prelude::AddAnyAttr,
renderer::Renderer,
view::{Position, ToTemplate},
};
use send_wrapper::SendWrapper;
@@ -9,10 +8,9 @@ use std::{marker::PhantomData, sync::Arc};
/// Adds a directive to the element, which runs some custom logic in the browser when the element
/// is created or hydrated.
pub trait DirectiveAttribute<T, P, D, Rndr>
pub trait DirectiveAttribute<T, P, D>
where
D: IntoDirective<T, P, Rndr>,
Rndr: Renderer,
D: IntoDirective<T, P>,
{
/// The type of the element with the directive added.
type Output;
@@ -22,15 +20,14 @@ where
fn directive(self, handler: D, param: P) -> Self::Output;
}
impl<V, T, P, D, Rndr> DirectiveAttribute<T, P, D, Rndr> for V
impl<V, T, P, D> DirectiveAttribute<T, P, D> for V
where
V: AddAnyAttr<Rndr>,
D: IntoDirective<T, P, Rndr>,
V: AddAnyAttr,
D: IntoDirective<T, P>,
P: Clone + 'static,
T: 'static,
Rndr: Renderer,
{
type Output = <Self as AddAnyAttr<Rndr>>::Output<Directive<T, D, P, Rndr>>;
type Output = <Self as AddAnyAttr>::Output<Directive<T, D, P>>;
fn directive(self, handler: D, param: P) -> Self::Output {
self.add_any_attr(directive(handler, param))
@@ -40,26 +37,22 @@ where
/// Adds a directive to the element, which runs some custom logic in the browser when the element
/// is created or hydrated.
#[inline(always)]
pub fn directive<T, P, D, R>(handler: D, param: P) -> Directive<T, D, P, R>
pub fn directive<T, P, D>(handler: D, param: P) -> Directive<T, D, P>
where
D: IntoDirective<T, P, R>,
R: Renderer,
D: IntoDirective<T, P>,
{
Directive(Some(SendWrapper::new(DirectiveInner {
handler,
param,
t: PhantomData,
rndr: PhantomData,
})))
}
/// Custom logic that runs in the browser when the element is created or hydrated.
#[derive(Debug)]
pub struct Directive<T, D, P, R>(
Option<SendWrapper<DirectiveInner<T, D, P, R>>>,
);
pub struct Directive<T, D, P>(Option<SendWrapper<DirectiveInner<T, D, P>>>);
impl<T, D, P, R> Clone for Directive<T, D, P, R>
impl<T, D, P> Clone for Directive<T, D, P>
where
P: Clone + 'static,
D: Clone,
@@ -70,14 +63,13 @@ where
}
#[derive(Debug)]
struct DirectiveInner<T, D, P, R> {
struct DirectiveInner<T, D, P> {
handler: D,
param: P,
t: PhantomData<T>,
rndr: PhantomData<R>,
}
impl<T, D, P, R> Clone for DirectiveInner<T, D, P, R>
impl<T, D, P> Clone for DirectiveInner<T, D, P>
where
P: Clone + 'static,
D: Clone,
@@ -87,24 +79,22 @@ where
handler: self.handler.clone(),
param: self.param.clone(),
t: PhantomData,
rndr: PhantomData,
}
}
}
impl<T, P, D, R> Attribute<R> for Directive<T, D, P, R>
impl<T, P, D> Attribute for Directive<T, D, P>
where
D: IntoDirective<T, P, R>,
D: IntoDirective<T, P>,
P: Clone + 'static, // TODO this is just here to make them cloneable
T: 'static,
R: Renderer,
{
const MIN_LENGTH: usize = 0;
type AsyncOutput = Self;
type State = R::Element;
type Cloneable = Directive<T, D::Cloneable, P, R>;
type CloneableOwned = Directive<T, D::Cloneable, P, R>;
type State = crate::renderer::types::Element;
type Cloneable = Directive<T, D::Cloneable, P>;
type CloneableOwned = Directive<T, D::Cloneable, P>;
fn html_len(&self) -> usize {
0
@@ -119,13 +109,16 @@ 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 {
let inner = self.0.expect("directive removed early").take();
inner.handler.run(el.clone(), inner.param);
el.clone()
}
fn build(self, el: &R::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
let inner = self.0.expect("directive removed early").take();
inner.handler.run(el.clone(), inner.param);
el.clone()
@@ -142,17 +135,11 @@ where
fn into_cloneable_owned(self) -> Self::CloneableOwned {
let inner = self.0.map(|inner| {
let DirectiveInner {
handler,
param,
t,
rndr,
} = inner.take();
let DirectiveInner { handler, param, t } = inner.take();
SendWrapper::new(DirectiveInner {
handler: handler.into_cloneable(),
param,
t,
rndr,
})
});
Directive(inner)
@@ -171,16 +158,15 @@ where
}
}
impl<T, D, P, R> NextAttribute<R> for Directive<T, D, P, R>
impl<T, D, P> NextAttribute for Directive<T, D, P>
where
D: IntoDirective<T, P, R>,
D: IntoDirective<T, P>,
P: Clone + 'static,
T: 'static,
R: Renderer,
{
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> {
@@ -188,7 +174,7 @@ where
}
}
impl<T, D, P, R> ToTemplate for Directive<T, D, P, R> {
impl<T, D, P> ToTemplate for Directive<T, D, P> {
const CLASS: &'static str = "";
fn to_template(
@@ -211,12 +197,12 @@ impl<T, D, P, R> ToTemplate for Directive<T, D, P, R> {
/// # use leptos::{*, html::AnyElement};
///
/// // This doesn't take an attribute value
/// fn my_directive(el: R::Element) {
/// fn my_directive(el: crate::renderer::types::Element) {
/// // do sth
/// }
///
/// // This requires an attribute value
/// fn another_directive(el: R::Element, params: i32) {
/// fn another_directive(el: crate::renderer::types::Element, params: i32) {
/// // do sth
/// }
///
@@ -247,25 +233,24 @@ impl<T, D, P, R> ToTemplate for Directive<T, D, P, R> {
/// A directive can be a function with one or two parameters.
/// The first is the element the directive is added to and the optional
/// second is the parameter that is provided in the attribute.
pub trait IntoDirective<T: ?Sized, P, R: Renderer> {
pub trait IntoDirective<T: ?Sized, P> {
/// An equivalent to this directive that is cloneable and owned.
type Cloneable: IntoDirective<T, P, R> + Clone + 'static;
type Cloneable: IntoDirective<T, P> + Clone + 'static;
/// Calls the handler function
fn run(&self, el: R::Element, param: P);
fn run(&self, el: crate::renderer::types::Element, param: P);
/// Converts this into a cloneable type.
fn into_cloneable(self) -> Self::Cloneable;
}
impl<F, R> IntoDirective<(R::Element,), (), R> for F
impl<F> IntoDirective<(crate::renderer::types::Element,), ()> for F
where
F: Fn(R::Element) + 'static,
R: Renderer,
F: Fn(crate::renderer::types::Element) + 'static,
{
type Cloneable = Arc<dyn Fn(R::Element)>;
type Cloneable = Arc<dyn Fn(crate::renderer::types::Element)>;
fn run(&self, el: R::Element, _: ()) {
fn run(&self, el: crate::renderer::types::Element, _: ()) {
self(el)
}
@@ -274,13 +259,12 @@ where
}
}
impl<R> IntoDirective<(R::Element,), (), R> for Arc<dyn Fn(R::Element)>
where
R: Renderer,
impl IntoDirective<(crate::renderer::types::Element,), ()>
for Arc<dyn Fn(crate::renderer::types::Element)>
{
type Cloneable = Arc<dyn Fn(R::Element)>;
type Cloneable = Arc<dyn Fn(crate::renderer::types::Element)>;
fn run(&self, el: R::Element, _: ()) {
fn run(&self, el: crate::renderer::types::Element, _: ()) {
self(el)
}
@@ -289,15 +273,14 @@ where
}
}
impl<F, P, R> IntoDirective<(R::Element, P), P, R> for F
impl<F, P> IntoDirective<(crate::renderer::types::Element, P), P> for F
where
F: Fn(R::Element, P) + 'static,
F: Fn(crate::renderer::types::Element, P) + 'static,
P: 'static,
R: Renderer,
{
type Cloneable = Arc<dyn Fn(R::Element, P)>;
type Cloneable = Arc<dyn Fn(crate::renderer::types::Element, P)>;
fn run(&self, el: R::Element, param: P) {
fn run(&self, el: crate::renderer::types::Element, param: P) {
self(el, param);
}
@@ -306,14 +289,14 @@ where
}
}
impl<P, R> IntoDirective<(R::Element, P), P, R> for Arc<dyn Fn(R::Element, P)>
impl<P> IntoDirective<(crate::renderer::types::Element, P), P>
for Arc<dyn Fn(crate::renderer::types::Element, P)>
where
R: Renderer,
P: 'static,
{
type Cloneable = Arc<dyn Fn(R::Element, P)>;
type Cloneable = Arc<dyn Fn(crate::renderer::types::Element, P)>;
fn run(&self, el: R::Element, param: P) {
fn run(&self, el: crate::renderer::types::Element, param: P) {
self(el, param)
}

View File

@@ -1,20 +1,16 @@
use super::ElementWithChildren;
use crate::{
html::element::{CreateElement, ElementType, HtmlElement},
renderer::{dom::Dom, Renderer},
};
use std::{fmt::Debug, marker::PhantomData};
use crate::html::element::{ElementType, HtmlElement};
use std::fmt::Debug;
/// Creates a custom element.
#[track_caller]
pub fn custom<E, Rndr>(tag: E) -> HtmlElement<Custom<E>, (), (), Rndr>
pub fn custom<E>(tag: E) -> HtmlElement<Custom<E>, (), ()>
where
E: AsRef<str>,
Rndr: Renderer,
{
HtmlElement {
tag: Custom(tag),
rndr: PhantomData,
attributes: (),
children: (),
}
@@ -33,6 +29,7 @@ where
const SELF_CLOSING: bool = false;
const ESCAPE_CHILDREN: bool = true;
const TAG: &'static str = "";
const NAMESPACE: Option<&'static str> = None;
fn tag(&self) -> &str {
self.0.as_ref()
@@ -40,16 +37,3 @@ where
}
impl<E> ElementWithChildren for Custom<E> {}
impl<E> CreateElement<Dom> for Custom<E>
where
E: AsRef<str>,
{
fn create_element(&self) -> <Dom as Renderer>::Element {
use wasm_bindgen::intern;
crate::dom::document()
.create_element(intern(self.0.as_ref()))
.unwrap()
}
}

View File

@@ -5,7 +5,7 @@ use crate::{
event::{on, EventDescriptor},
style::IntoStyle,
},
renderer::{dom::Dom, DomRenderer, RemoveEventHandler},
renderer::RemoveEventHandler,
};
use wasm_bindgen::JsValue;
use web_sys::Element;
@@ -31,17 +31,17 @@ pub trait ElementExt {
/// Adds an attribute to the element, at runtime.
fn attr<At>(&self, attribute: At) -> At::State
where
At: Attribute<Dom>;
At: Attribute;
/// Adds a class to the element, at runtime.
fn class<C>(&self, class: C) -> C::State
where
C: IntoClass<Dom>;
C: IntoClass;
/// Adds a style to the element, at runtime.
fn style<S>(&self, style: S) -> S::State
where
S: IntoStyle<Dom>;
S: IntoStyle;
/// Adds an event listener to the element, at runtime.
fn on<E>(
@@ -58,18 +58,17 @@ pub trait ElementExt {
impl<T> ElementExt for T
where
T: AsRef<Element>,
Dom: DomRenderer,
{
fn attr<At>(&self, attribute: At) -> At::State
where
At: Attribute<Dom>,
At: Attribute,
{
attribute.build(self.as_ref())
}
fn class<C>(&self, class: C) -> C::State
where
C: IntoClass<Dom>,
C: IntoClass,
{
class.build(self.as_ref())
}
@@ -84,12 +83,12 @@ where
E::EventType: 'static,
E::EventType: From<JsValue>,
{
on::<E, _, Dom>(ev, cb).attach(self.as_ref())
on::<E, _>(ev, cb).attach(self.as_ref())
}
fn style<S>(&self, style: S) -> S::State
where
S: IntoStyle<Dom>,
S: IntoStyle,
{
style.build(self.as_ref())
}

View File

@@ -1,18 +1,12 @@
#[cfg(feature = "sledgehammer")]
use crate::renderer::sledgehammer::Sledgehammer;
use crate::{
html::{
attribute::{Attr, Attribute, AttributeValue},
element::{
CreateElement, ElementType, ElementWithChildren, HtmlElement,
},
element::{ElementType, ElementWithChildren, HtmlElement},
},
renderer::{dom::Dom, Renderer},
view::Render,
};
use next_tuple::NextTuple;
use once_cell::unsync::Lazy;
use std::{fmt::Debug, marker::PhantomData};
use std::fmt::Debug;
macro_rules! html_element_inner {
(
@@ -26,15 +20,15 @@ macro_rules! html_element_inner {
paste::paste! {
#[$meta]
#[track_caller]
pub fn $tag<Rndr>() -> HtmlElement<$struct_name, (), (), Rndr>
pub fn $tag() -> HtmlElement<$struct_name, (), ()>
where
Rndr: Renderer
{
HtmlElement {
tag: $struct_name,
attributes: (),
children: (),
rndr: PhantomData,
}
}
@@ -43,28 +37,28 @@ macro_rules! html_element_inner {
pub struct $struct_name;
// Typed attribute methods
impl<At, Ch, Rndr> HtmlElement<$struct_name, At, Ch, Rndr>
impl<At, Ch> HtmlElement<$struct_name, At, Ch>
where
At: Attribute<Rndr>,
Ch: Render<Rndr>,
Rndr: Renderer,
At: Attribute,
Ch: Render,
{
$(
#[doc = concat!("The [`", stringify!($attr), "`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/", stringify!($tag), "#", stringify!($attr) ,") attribute on `<", stringify!($tag), ">`.")]
pub fn $attr<V>(self, value: V) -> HtmlElement <
$struct_name,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V, Rndr>>,
Ch, Rndr
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
Ch
>
where
V: AttributeValue<Rndr>,
V: AttributeValue,
At: NextTuple,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V, Rndr>>: Attribute<Rndr>,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>: Attribute,
{
let HtmlElement { tag, rndr, children, attributes } = self;
let HtmlElement { tag, children, attributes } = self;
HtmlElement {
tag,
rndr,
children,
attributes: attributes.next_tuple($crate::html::attribute::$attr(value)),
}
@@ -78,6 +72,7 @@ macro_rules! html_element_inner {
const TAG: &'static str = stringify!($tag);
const SELF_CLOSING: bool = false;
const ESCAPE_CHILDREN: bool = $escape;
const NAMESPACE: Option<&'static str> = None;
#[inline(always)]
fn tag(&self) -> &str {
@@ -86,28 +81,6 @@ macro_rules! html_element_inner {
}
impl ElementWithChildren for $struct_name {}
impl CreateElement<Dom> for $struct_name {
#[track_caller]
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", fields(callsite = std::panic::Location::caller().to_string())))]
fn create_element(&self) -> <Dom as Renderer>::Element {
use wasm_bindgen::JsCast;
thread_local! {
static ELEMENT: Lazy<<Dom as Renderer>::Element> = Lazy::new(|| {
crate::dom::document().create_element(stringify!($tag)).unwrap()
});
}
ELEMENT.with(|e| e.clone_node()).unwrap().unchecked_into()
}
}
#[cfg(feature = "sledgehammer")]
impl CreateElement<Sledgehammer> for $struct_name {
fn create_element(&self) -> <Sledgehammer as Renderer>::Element {
Sledgehammer::element(stringify!($tag))
}
}
}
};
}
@@ -145,14 +118,14 @@ macro_rules! html_self_closing_elements {
paste::paste! {
$(
#[$meta]
pub fn $tag<Rndr>() -> HtmlElement<[<$tag:camel>], (), (), Rndr>
pub fn $tag() -> HtmlElement<[<$tag:camel>], (), ()>
where
Rndr: Renderer
{
HtmlElement {
attributes: (),
children: (),
rndr: PhantomData,
tag: [<$tag:camel>],
}
}
@@ -162,30 +135,29 @@ macro_rules! html_self_closing_elements {
pub struct [<$tag:camel>];
// Typed attribute methods
impl<At, Rndr> HtmlElement<[<$tag:camel>], At, (), Rndr>
impl<At> HtmlElement<[<$tag:camel>], At, ()>
where
At: Attribute<Rndr>,
Rndr: Renderer,
At: Attribute,
{
$(
#[doc = concat!("The [`", stringify!($attr), "`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/", stringify!($tag), "#", stringify!($attr) ,") attribute on `<", stringify!($tag), ">`.")]
pub fn $attr<V>(self, value: V) -> HtmlElement<
[<$tag:camel>],
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V, Rndr>>,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
(),
Rndr
>
where
V: AttributeValue<Rndr>,
V: AttributeValue,
At: NextTuple,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V, Rndr>>: Attribute<Rndr>,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>: Attribute,
{
let HtmlElement { tag, rndr, children, attributes,
let HtmlElement { tag, children, attributes,
} = self;
HtmlElement {
tag,
rndr,
children,
attributes: attributes.next_tuple($crate::html::attribute::$attr(value)),
}
@@ -199,25 +171,13 @@ macro_rules! html_self_closing_elements {
const TAG: &'static str = stringify!($tag);
const SELF_CLOSING: bool = true;
const ESCAPE_CHILDREN: bool = $escape;
const NAMESPACE: Option<&'static str> = None;
#[inline(always)]
fn tag(&self) -> &str {
Self::TAG
}
}
impl CreateElement<Dom> for [<$tag:camel>] {
fn create_element(&self) -> <Dom as Renderer>::Element {
use wasm_bindgen::JsCast;
thread_local! {
static ELEMENT: Lazy<<Dom as Renderer>::Element> = Lazy::new(|| {
crate::dom::document().create_element(stringify!($tag)).unwrap()
});
}
ELEMENT.with(|e| e.clone_node()).unwrap().unchecked_into()
}
}
)*
}
}

View File

@@ -1,10 +1,10 @@
use super::{ElementWithChildren, HtmlElement};
use crate::{
html::attribute::{Attribute, NextAttribute},
renderer::{DomRenderer, Renderer},
renderer::Rndr,
view::add_attr::AddAnyAttr,
};
use std::{future::Future, marker::PhantomData, sync::Arc};
use std::{future::Future, sync::Arc};
/// Returns an [`Attribute`] that sets the inner HTML of an element.
///
@@ -15,47 +15,40 @@ use std::{future::Future, marker::PhantomData, sync::Arc};
/// sanitize the input to avoid a cross-site scripting (XSS)
/// vulnerability.
#[inline(always)]
pub fn inner_html<T, R>(value: T) -> InnerHtml<T, R>
pub fn inner_html<T>(value: T) -> InnerHtml<T>
where
T: InnerHtmlValue<R>,
R: DomRenderer,
T: InnerHtmlValue,
{
InnerHtml {
value,
rndr: PhantomData,
}
InnerHtml { value }
}
/// Sets the inner HTML of an element.
#[derive(Debug)]
pub struct InnerHtml<T, R> {
pub struct InnerHtml<T> {
value: T,
rndr: PhantomData<R>,
}
impl<T, R> Clone for InnerHtml<T, R>
impl<T> Clone for InnerHtml<T>
where
T: Clone,
{
fn clone(&self) -> Self {
Self {
value: self.value.clone(),
rndr: PhantomData,
}
}
}
impl<T, R> Attribute<R> for InnerHtml<T, R>
impl<T> Attribute for InnerHtml<T>
where
T: InnerHtmlValue<R>,
R: DomRenderer,
T: InnerHtmlValue,
{
const MIN_LENGTH: usize = 0;
type AsyncOutput = InnerHtml<T::AsyncOutput, R>;
type AsyncOutput = InnerHtml<T::AsyncOutput>;
type State = T::State;
type Cloneable = InnerHtml<T::Cloneable, R>;
type CloneableOwned = InnerHtml<T::CloneableOwned, R>;
type Cloneable = InnerHtml<T::Cloneable>;
type CloneableOwned = InnerHtml<T::CloneableOwned>;
fn html_len(&self) -> usize {
self.value.html_len()
@@ -73,12 +66,12 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
el: &<R as Renderer>::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
self.value.hydrate::<FROM_SERVER>(el)
}
fn build(self, el: &<R as Renderer>::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
self.value.build(el)
}
@@ -89,14 +82,12 @@ where
fn into_cloneable(self) -> Self::Cloneable {
InnerHtml {
value: self.value.into_cloneable(),
rndr: self.rndr,
}
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
InnerHtml {
value: self.value.into_cloneable_owned(),
rndr: self.rndr,
}
}
@@ -107,19 +98,17 @@ where
async fn resolve(self) -> Self::AsyncOutput {
InnerHtml {
value: self.value.resolve().await,
rndr: self.rndr,
}
}
}
impl<T, R> NextAttribute<R> for InnerHtml<T, R>
impl<T> NextAttribute for InnerHtml<T>
where
T: InnerHtmlValue<R>,
R: DomRenderer,
T: InnerHtmlValue,
{
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> {
@@ -128,11 +117,11 @@ where
}
/// Sets the inner HTML of an element.
pub trait InnerHtmlAttribute<T, Rndr>
pub trait InnerHtmlAttribute<T>
where
T: InnerHtmlValue<Rndr>,
Rndr: DomRenderer,
Self: Sized + AddAnyAttr<Rndr>,
T: InnerHtmlValue,
Self: Sized + AddAnyAttr,
{
/// Sets the inner HTML of this element.
///
@@ -145,38 +134,36 @@ where
fn inner_html(
self,
value: T,
) -> <Self as AddAnyAttr<Rndr>>::Output<InnerHtml<T, Rndr>> {
) -> <Self as AddAnyAttr>::Output<InnerHtml<T>> {
self.add_any_attr(inner_html(value))
}
}
impl<T, E, At, Rndr> InnerHtmlAttribute<T, Rndr>
for HtmlElement<E, At, (), Rndr>
impl<T, E, At> InnerHtmlAttribute<T> for HtmlElement<E, At, ()>
where
Self: AddAnyAttr<Rndr>,
Self: AddAnyAttr,
E: ElementWithChildren,
At: Attribute<Rndr>,
T: InnerHtmlValue<Rndr>,
Rndr: DomRenderer,
At: Attribute,
T: InnerHtmlValue,
{
fn inner_html(
self,
value: T,
) -> <Self as AddAnyAttr<Rndr>>::Output<InnerHtml<T, Rndr>> {
) -> <Self as AddAnyAttr>::Output<InnerHtml<T>> {
self.add_any_attr(inner_html(value))
}
}
/// A possible value for [`InnerHtml`].
pub trait InnerHtmlValue<R: DomRenderer>: Send {
pub trait InnerHtmlValue: Send {
/// The type after all async data have resolved.
type AsyncOutput: InnerHtmlValue<R>;
type AsyncOutput: InnerHtmlValue;
/// The view state retained between building and rebuilding.
type State;
/// An equivalent value that can be cloned.
type Cloneable: InnerHtmlValue<R> + Clone;
type Cloneable: InnerHtmlValue + Clone;
/// An equivalent value that can be cloned and is `'static`.
type CloneableOwned: InnerHtmlValue<R> + Clone + 'static;
type CloneableOwned: InnerHtmlValue + Clone + 'static;
/// The estimated length of the HTML.
fn html_len(&self) -> usize;
@@ -189,10 +176,13 @@ pub trait InnerHtmlValue<R: DomRenderer>: 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 class 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;
/// Updates the value.
fn rebuild(self, state: &mut Self::State);
@@ -212,12 +202,9 @@ pub trait InnerHtmlValue<R: DomRenderer>: Send {
fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send;
}
impl<R> InnerHtmlValue<R> for String
where
R: DomRenderer,
{
impl InnerHtmlValue for String {
type AsyncOutput = Self;
type State = (R::Element, Self);
type State = (crate::renderer::types::Element, Self);
type Cloneable = Arc<str>;
type CloneableOwned = Arc<str>;
@@ -233,22 +220,22 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
el: &<R as Renderer>::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
if !FROM_SERVER {
R::set_inner_html(el, &self);
Rndr::set_inner_html(el, &self);
}
(el.clone(), self)
}
fn build(self, el: &<R as Renderer>::Element) -> Self::State {
R::set_inner_html(el, &self);
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_inner_html(el, &self);
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
if self != state.1 {
R::set_inner_html(&state.0, &self);
Rndr::set_inner_html(&state.0, &self);
state.1 = self;
}
}
@@ -268,12 +255,9 @@ where
}
}
impl<R> InnerHtmlValue<R> for Arc<str>
where
R: DomRenderer,
{
impl InnerHtmlValue for Arc<str> {
type AsyncOutput = Self;
type State = (R::Element, Self);
type State = (crate::renderer::types::Element, Self);
type Cloneable = Self;
type CloneableOwned = Self;
@@ -289,22 +273,22 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
el: &<R as Renderer>::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
if !FROM_SERVER {
R::set_inner_html(el, &self);
Rndr::set_inner_html(el, &self);
}
(el.clone(), self)
}
fn build(self, el: &<R as Renderer>::Element) -> Self::State {
R::set_inner_html(el, &self);
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_inner_html(el, &self);
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
if !Arc::ptr_eq(&self, &state.1) {
R::set_inner_html(&state.0, &self);
Rndr::set_inner_html(&state.0, &self);
state.1 = self;
}
}
@@ -324,12 +308,9 @@ where
}
}
impl<'a, R> InnerHtmlValue<R> for &'a str
where
R: DomRenderer,
{
impl<'a> InnerHtmlValue for &'a str {
type AsyncOutput = Self;
type State = (R::Element, Self);
type State = (crate::renderer::types::Element, Self);
type Cloneable = Self;
type CloneableOwned = Arc<str>;
@@ -345,22 +326,22 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
el: &<R as Renderer>::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
if !FROM_SERVER {
R::set_inner_html(el, self);
Rndr::set_inner_html(el, self);
}
(el.clone(), self)
}
fn build(self, el: &<R as Renderer>::Element) -> Self::State {
R::set_inner_html(el, self);
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_inner_html(el, self);
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
if self != state.1 {
R::set_inner_html(&state.0, self);
Rndr::set_inner_html(&state.0, self);
state.1 = self;
}
}
@@ -380,13 +361,12 @@ where
}
}
impl<T, R> InnerHtmlValue<R> for Option<T>
impl<T> InnerHtmlValue for Option<T>
where
T: InnerHtmlValue<R>,
R: DomRenderer,
T: InnerHtmlValue,
{
type AsyncOutput = Self;
type State = (R::Element, Option<T::State>);
type State = (crate::renderer::types::Element, Option<T::State>);
type Cloneable = Option<T::Cloneable>;
type CloneableOwned = Option<T::CloneableOwned>;
@@ -407,12 +387,12 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
el: &<R as Renderer>::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
(el.clone(), self.map(|n| n.hydrate::<FROM_SERVER>(el)))
}
fn build(self, el: &<R as Renderer>::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
(el.clone(), self.map(|n| n.build(el)))
}
@@ -420,7 +400,7 @@ where
let new_state = match (self, &mut state.1) {
(None, None) => None,
(None, Some(_)) => {
R::set_inner_html(&state.0, "");
Rndr::set_inner_html(&state.0, "");
Some(None)
}
(Some(new), None) => Some(Some(new.build(&state.0))),

View File

@@ -1,7 +1,7 @@
use crate::{
html::attribute::Attribute,
hydration::Cursor,
renderer::{CastFrom, Renderer},
renderer::{CastFrom, Rndr},
ssr::StreamBuilder,
view::{
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
@@ -13,7 +13,7 @@ use const_str_slice_concat::{
};
use futures::future::join;
use next_tuple::NextTuple;
use std::{marker::PhantomData, ops::Deref};
use std::ops::Deref;
mod custom;
mod element_ext;
@@ -27,29 +27,26 @@ pub use inner_html::*;
/// The typed representation of an HTML element.
#[derive(Debug, PartialEq, Eq)]
pub struct HtmlElement<E, At, Ch, Rndr> {
pub struct HtmlElement<E, At, Ch> {
pub(crate) tag: E,
pub(crate) rndr: PhantomData<Rndr>,
pub(crate) attributes: At,
pub(crate) children: Ch,
}
impl<E: Clone, At: Clone, Ch: Clone, Rndr> Clone
for HtmlElement<E, At, Ch, Rndr>
{
impl<E: Clone, At: Clone, Ch: Clone> Clone for HtmlElement<E, At, Ch> {
fn clone(&self) -> Self {
HtmlElement {
tag: self.tag.clone(),
rndr: PhantomData,
attributes: self.attributes.clone(),
children: self.children.clone(),
}
}
}
impl<E: Copy, At: Copy, Ch: Copy, Rndr> Copy for HtmlElement<E, At, Ch, Rndr> {}
impl<E: Copy, At: Copy, Ch: Copy> Copy for HtmlElement<E, At, Ch> {}
/*impl<E, At, Ch, Rndr> ElementType for HtmlElement<E, At, Ch, Rndr>
/*impl<E, At, Ch> ElementType for HtmlElement<E, At, Ch>
where
E: ElementType,
{
@@ -64,48 +61,42 @@ where
}
}*/
impl<E, At, Ch, NewChild, Rndr> ElementChild<Rndr, NewChild>
for HtmlElement<E, At, Ch, Rndr>
impl<E, At, Ch, NewChild> ElementChild<NewChild> for HtmlElement<E, At, Ch>
where
E: ElementWithChildren,
Ch: Render<Rndr> + NextTuple,
<Ch as NextTuple>::Output<NewChild>: Render<Rndr>,
Rndr: Renderer,
NewChild: Render<Rndr>,
Ch: Render + NextTuple,
<Ch as NextTuple>::Output<NewChild>: Render,
NewChild: Render,
{
type Output = HtmlElement<E, At, <Ch as NextTuple>::Output<NewChild>, Rndr>;
type Output = HtmlElement<E, At, <Ch as NextTuple>::Output<NewChild>>;
fn child(self, child: NewChild) -> Self::Output {
let HtmlElement {
tag,
rndr,
attributes,
children,
} = self;
HtmlElement {
tag,
rndr,
attributes,
children: children.next_tuple(child),
}
}
}
impl<E, At, Ch, Rndr> AddAnyAttr<Rndr> for HtmlElement<E, At, Ch, Rndr>
impl<E, At, Ch> AddAnyAttr for HtmlElement<E, At, Ch>
where
E: ElementType + CreateElement<Rndr> + Send,
At: Attribute<Rndr> + Send,
Ch: RenderHtml<Rndr> + Send,
Rndr: Renderer,
E: ElementType + Send,
At: Attribute + Send,
Ch: RenderHtml + Send,
{
type Output<SomeNewAttr: Attribute<Rndr>> = HtmlElement<
E,
<At as NextAttribute<Rndr>>::Output<SomeNewAttr>,
Ch,
Rndr,
>;
type Output<SomeNewAttr: Attribute> =
HtmlElement<E, <At as NextAttribute>::Output<SomeNewAttr>, Ch>;
fn add_any_attr<NewAttr: Attribute<Rndr>>(
fn add_any_attr<NewAttr: Attribute>(
self,
attr: NewAttr,
) -> Self::Output<NewAttr> {
@@ -113,22 +104,19 @@ where
tag,
attributes,
children,
rndr,
} = self;
HtmlElement {
tag,
attributes: attributes.add_any_attr(attr),
children,
rndr,
}
}
}
/// Adds a child to the element.
pub trait ElementChild<Rndr, NewChild>
pub trait ElementChild<NewChild>
where
NewChild: Render<Rndr>,
Rndr: Renderer,
NewChild: Render,
{
/// The type of the element, with the child added.
type Output;
@@ -150,6 +138,8 @@ pub trait ElementType: Send {
/// like `<style>` and `<script>`, which include other languages that should not use HTML
/// entity escaping.
const ESCAPE_CHILDREN: bool;
/// The element's namespace, if it is not HTML.
const NAMESPACE: Option<&'static str>;
/// The element's tag.
fn tag(&self) -> &str;
@@ -163,27 +153,20 @@ pub trait HasElementType {
pub(crate) trait ElementWithChildren {}
/// Creates an element.
pub trait CreateElement<R: Renderer> {
/// Creates an element.
fn create_element(&self) -> R::Element;
}
impl<E, At, Ch, Rndr> HasElementType for HtmlElement<E, At, Ch, Rndr>
impl<E, At, Ch> HasElementType for HtmlElement<E, At, Ch>
where
E: ElementType,
{
type ElementType = E::Output;
}
impl<E, At, Ch, Rndr> Render<Rndr> for HtmlElement<E, At, Ch, Rndr>
impl<E, At, Ch> Render for HtmlElement<E, At, Ch>
where
E: ElementType + CreateElement<Rndr>,
At: Attribute<Rndr>,
Ch: Render<Rndr>,
Rndr: Renderer,
E: ElementType,
At: Attribute,
Ch: Render,
{
type State = ElementState<At::State, Ch::State, Rndr>;
type State = ElementState<At::State, Ch::State>;
fn rebuild(self, state: &mut Self::State) {
let ElementState {
@@ -196,7 +179,7 @@ where
}
fn build(self) -> Self::State {
let el = Rndr::create_element(self.tag);
let el = Rndr::create_element(self.tag.tag(), E::NAMESPACE);
let attrs = self.attributes.build(&el);
let children = if E::SELF_CLOSING {
@@ -210,19 +193,17 @@ where
el,
attrs,
children,
rndr: PhantomData,
}
}
}
impl<E, At, Ch, Rndr> RenderHtml<Rndr> for HtmlElement<E, At, Ch, Rndr>
impl<E, At, Ch> RenderHtml for HtmlElement<E, At, Ch>
where
E: ElementType + CreateElement<Rndr> + Send,
At: Attribute<Rndr> + Send,
Ch: RenderHtml<Rndr> + Send,
Rndr: Renderer,
E: ElementType + Send,
At: Attribute + Send,
Ch: RenderHtml + Send,
{
type AsyncOutput = HtmlElement<E, At::AsyncOutput, Ch::AsyncOutput, Rndr>;
type AsyncOutput = HtmlElement<E, At::AsyncOutput, Ch::AsyncOutput>;
const MIN_LENGTH: usize = if E::SELF_CLOSING {
3 // < ... />
@@ -247,7 +228,7 @@ where
join(self.attributes.resolve(), self.children.resolve()).await;
HtmlElement {
tag: self.tag,
rndr: PhantomData,
attributes,
children,
}
@@ -350,7 +331,7 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<Rndr>,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
// non-Static custom elements need special support in templates
@@ -365,7 +346,8 @@ where
} else if curr_position != Position::Current {
cursor.sibling();
}
let el = Rndr::Element::cast_from(cursor.current()).unwrap();
let el = crate::renderer::types::Element::cast_from(cursor.current())
.unwrap();
let attrs = self.attributes.hydrate::<FROM_SERVER>(&el);
@@ -378,23 +360,26 @@ where
};
// go to next sibling
cursor.set(el.as_ref().clone());
cursor.set(
<crate::renderer::types::Element as AsRef<
crate::renderer::types::Node,
>>::as_ref(&el)
.clone(),
);
position.set(Position::NextChild);
ElementState {
el,
attrs,
children,
rndr: PhantomData,
}
}
}
/// Renders an [`Attribute`] (which can be one or more HTML attributes) into an HTML buffer.
pub fn attributes_to_html<At, R>(attr: At, buf: &mut String) -> String
pub fn attributes_to_html<At>(attr: At, buf: &mut String) -> String
where
At: Attribute<R>,
R: Renderer,
At: Attribute,
{
// `class` and `style` are created first, and pushed later
// this is because they can be filled by a mixture of values that include
@@ -429,36 +414,38 @@ where
}
/// The retained view state for an HTML element.
pub struct ElementState<At, Ch, R: Renderer> {
pub(crate) el: R::Element,
pub struct ElementState<At, Ch> {
pub(crate) el: crate::renderer::types::Element,
pub(crate) attrs: At,
pub(crate) children: Option<Ch>,
rndr: PhantomData<R>,
}
impl<At, Ch, R: Renderer> Deref for ElementState<At, Ch, R> {
type Target = R::Element;
impl<At, Ch> Deref for ElementState<At, Ch> {
type Target = crate::renderer::types::Element;
fn deref(&self) -> &Self::Target {
&self.el
}
}
impl<At, Ch, R> Mountable<R> for ElementState<At, Ch, R>
where
R: Renderer,
{
impl<At, Ch> Mountable for ElementState<At, Ch> {
fn unmount(&mut self) {
R::remove(self.el.as_ref());
Rndr::remove(self.el.as_ref());
}
fn mount(&mut self, parent: &R::Element, marker: Option<&R::Node>) {
R::insert_node(parent, self.el.as_ref(), marker);
fn mount(
&mut self,
parent: &crate::renderer::types::Element,
marker: Option<&crate::renderer::types::Node>,
) {
Rndr::insert_node(parent, self.el.as_ref(), marker);
}
fn insert_before_this(&self, child: &mut dyn Mountable<R>) -> bool {
if let Some(parent) = R::get_parent(self.el.as_ref()) {
if let Some(element) = R::Element::cast_from(parent) {
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
if let Some(parent) = Rndr::get_parent(self.el.as_ref()) {
if let Some(element) =
crate::renderer::types::Element::cast_from(parent)
{
child.mount(&element, Some(self.el.as_ref()));
return true;
}
@@ -467,12 +454,11 @@ where
}
}
impl<E, At, Ch, Rndr> ToTemplate for HtmlElement<E, At, Ch, Rndr>
impl<E, At, Ch> ToTemplate for HtmlElement<E, At, Ch>
where
E: ElementType,
At: Attribute<Rndr> + ToTemplate,
Ch: Render<Rndr> + ToTemplate,
Rndr: Renderer,
At: Attribute + ToTemplate,
Ch: Render + ToTemplate,
{
const TEMPLATE: &'static str = str_from_buffer(&const_concat(&[
"<",
@@ -555,7 +541,7 @@ where
}
}
}
/*
#[cfg(all(test, feature = "testing"))]
mod tests {
#[cfg(feature = "nightly")]
@@ -614,3 +600,4 @@ mod tests {
assert_eq!(html.len(), allocated_len);
}
}
*/

View File

@@ -1,6 +1,6 @@
use crate::{
html::attribute::Attribute,
renderer::{CastFrom, DomRenderer, RemoveEventHandler},
renderer::{CastFrom, RemoveEventHandler, Rndr},
view::{Position, ToTemplate},
};
use send_wrapper::SendWrapper;
@@ -51,13 +51,12 @@ where
}
/// An event listener with a typed event target.
pub struct Targeted<E, T, R> {
pub struct Targeted<E, T> {
event: E,
el_ty: PhantomData<T>,
rndr: PhantomData<R>,
}
impl<E, T, R> Targeted<E, T, R> {
impl<E, T> Targeted<E, T> {
/// Returns the inner event.
pub fn into_inner(self) -> E {
self.event
@@ -66,17 +65,17 @@ impl<E, T, R> Targeted<E, T, R> {
/// Returns the event's target, as an HTML element of the correct type.
pub fn target(&self) -> T
where
T: CastFrom<R::Element>,
R: DomRenderer,
R::Event: From<E>,
T: CastFrom<crate::renderer::types::Element>,
crate::renderer::types::Event: From<E>,
E: Clone,
{
let ev = R::Event::from(self.event.clone());
R::event_target(&ev)
let ev = crate::renderer::types::Event::from(self.event.clone());
Rndr::event_target(&ev)
}
}
impl<E, T, R> Deref for Targeted<E, T, R> {
impl<E, T> Deref for Targeted<E, T> {
type Target = E;
fn deref(&self) -> &Self::Target {
@@ -84,64 +83,60 @@ impl<E, T, R> Deref for Targeted<E, T, R> {
}
}
impl<E, T, R> DerefMut for Targeted<E, T, R> {
impl<E, T> DerefMut for Targeted<E, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.event
}
}
impl<E, T, R> From<E> for Targeted<E, T, R> {
impl<E, T> From<E> for Targeted<E, T> {
fn from(event: E) -> Self {
Targeted {
event,
el_ty: PhantomData,
rndr: PhantomData,
}
}
}
/// Creates an [`Attribute`] that will add an event listener to an element.
pub fn on<E, F, R>(event: E, cb: F) -> On<E, F, R>
pub fn on<E, F>(event: E, cb: F) -> On<E, F>
where
F: FnMut(E::EventType) + 'static,
E: EventDescriptor + Send + 'static,
E::EventType: 'static,
E::EventType: From<R::Event>,
R: DomRenderer,
E::EventType: From<crate::renderer::types::Event>,
{
On {
event,
cb: Some(SendWrapper::new(cb)),
ty: PhantomData,
}
}
/// Creates an [`Attribute`] that will add an event listener with a typed target to an element.
#[allow(clippy::type_complexity)]
pub fn on_target<E, T, R, F>(
pub fn on_target<E, T, F>(
event: E,
mut cb: F,
) -> On<E, Box<dyn FnMut(E::EventType)>, R>
) -> On<E, Box<dyn FnMut(E::EventType)>>
where
T: HasElementType,
F: FnMut(Targeted<E::EventType, <T as HasElementType>::ElementType, R>)
F: FnMut(Targeted<E::EventType, <T as HasElementType>::ElementType>)
+ 'static,
E: EventDescriptor + Send + 'static,
E::EventType: 'static,
R: DomRenderer,
E::EventType: From<R::Event>,
E::EventType: From<crate::renderer::types::Event>,
{
on(event, Box::new(move |ev: E::EventType| cb(ev.into())))
}
/// An [`Attribute`] that adds an event listener to an element.
pub struct On<E, F, R> {
pub struct On<E, F> {
event: E,
cb: Option<SendWrapper<F>>,
ty: PhantomData<R>,
}
impl<E, F, R> Clone for On<E, F, R>
impl<E, F> Clone for On<E, F>
where
E: Clone,
F: Clone,
@@ -150,30 +145,33 @@ where
Self {
event: self.event.clone(),
cb: self.cb.clone(),
ty: PhantomData,
}
}
}
impl<E, F, R> On<E, F, R>
impl<E, F> On<E, F>
where
F: EventCallback<E::EventType>,
E: EventDescriptor + Send + 'static,
E::EventType: 'static,
R: DomRenderer,
E::EventType: From<R::Event>,
E::EventType: From<crate::renderer::types::Event>,
{
/// Attaches the event listener to the element.
pub fn attach(self, el: &R::Element) -> RemoveEventHandler<R::Element> {
fn attach_inner<R: DomRenderer>(
el: &R::Element,
cb: Box<dyn FnMut(R::Event)>,
pub fn attach(
self,
el: &crate::renderer::types::Element,
) -> RemoveEventHandler<crate::renderer::types::Element> {
fn attach_inner(
el: &crate::renderer::types::Element,
cb: Box<dyn FnMut(crate::renderer::types::Event)>,
name: Cow<'static, str>,
delegation_key: Option<Cow<'static, str>>,
) -> RemoveEventHandler<R::Element> {
) -> RemoveEventHandler<crate::renderer::types::Element> {
match delegation_key {
None => R::add_event_listener(el, &name, cb),
Some(key) => R::add_event_listener_delegated(el, name, key, cb),
None => Rndr::add_event_listener(el, &name, cb),
Some(key) => {
Rndr::add_event_listener_delegated(el, name, key, cb)
}
}
}
@@ -182,7 +180,7 @@ where
#[cfg(feature = "tracing")]
let span = tracing::Span::current();
let cb = Box::new(move |ev: R::Event| {
let cb = Box::new(move |ev: crate::renderer::types::Event| {
#[cfg(all(debug_assertions, feature = "reactive_graph"))]
let _rx_guard =
reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
@@ -191,9 +189,9 @@ where
let ev = E::EventType::from(ev);
cb.invoke(ev);
}) as Box<dyn FnMut(R::Event)>;
}) as Box<dyn FnMut(crate::renderer::types::Event)>;
attach_inner::<R>(
attach_inner(
el,
cb,
self.event.name(),
@@ -203,30 +201,32 @@ where
}
}
impl<E, F, R> Debug for On<E, F, R>
impl<E, F> Debug for On<E, F>
where
E: Debug,
R: DomRenderer,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("On").field(&self.event).finish()
}
}
impl<E, F, R> Attribute<R> for On<E, F, R>
impl<E, F> Attribute for On<E, F>
where
F: EventCallback<E::EventType>,
E: EventDescriptor + Send + 'static,
E::EventType: 'static,
R: DomRenderer,
E::EventType: From<R::Event>,
E::EventType: From<crate::renderer::types::Event>,
{
const MIN_LENGTH: usize = 0;
type AsyncOutput = Self;
// a function that can be called once to remove the event listener
type State = (R::Element, Option<RemoveEventHandler<R::Element>>);
type Cloneable = On<E, SharedEventCallback<E::EventType>, R>;
type CloneableOwned = On<E, SharedEventCallback<E::EventType>, R>;
type State = (
crate::renderer::types::Element,
Option<RemoveEventHandler<crate::renderer::types::Element>>,
);
type Cloneable = On<E, SharedEventCallback<E::EventType>>;
type CloneableOwned = On<E, SharedEventCallback<E::EventType>>;
#[inline(always)]
fn html_len(&self) -> usize {
@@ -244,13 +244,16 @@ where
}
#[inline(always)]
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 {
let cleanup = self.attach(el);
(el.clone(), Some(cleanup))
}
#[inline(always)]
fn build(self, el: &R::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
let cleanup = self.attach(el);
(el.clone(), Some(cleanup))
}
@@ -268,7 +271,6 @@ where
On {
cb: self.cb.map(|cb| SendWrapper::new(cb.take().into_shared())),
event: self.event,
ty: self.ty,
}
}
@@ -276,7 +278,6 @@ where
On {
cb: self.cb.map(|cb| SendWrapper::new(cb.take().into_shared())),
event: self.event,
ty: self.ty,
}
}
@@ -293,17 +294,17 @@ where
}
}
impl<E, F, R> NextAttribute<R> for On<E, F, R>
impl<E, F> NextAttribute for On<E, F>
where
F: EventCallback<E::EventType>,
E: EventDescriptor + Send + 'static,
E::EventType: 'static,
R: DomRenderer,
E::EventType: From<R::Event>,
{
type Output<NewAttr: Attribute<R>> = (Self, NewAttr);
fn add_any_attr<NewAttr: Attribute<R>>(
E::EventType: From<crate::renderer::types::Event>,
{
type Output<NewAttr: Attribute> = (Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
@@ -311,7 +312,7 @@ where
}
}
impl<E, F, R> ToTemplate for On<E, F, R> {
impl<E, F> ToTemplate for On<E, F> {
#[inline(always)]
fn to_template(
_buf: &mut String,
@@ -404,9 +405,9 @@ impl<E: FromWasmAbi> Custom<E> {
/// # use tachys::prelude::*;
/// # use tachys::html;
/// # use tachys::html::event as ev;
/// # fn custom_event() -> impl Render<Dom> {
/// # fn custom_event() -> impl Render {
/// let mut non_passive_wheel = ev::Custom::new("wheel");
/// non_passive_wheel.options_mut().passive(false);
/// non_passive_wheel.options_mut().set_passive(false);
///
/// let canvas =
/// html::element::canvas().on(non_passive_wheel, |e: ev::WheelEvent| {

View File

@@ -2,30 +2,26 @@ use super::attribute::Attribute;
use crate::{
hydration::Cursor,
prelude::{Render, RenderHtml},
renderer::Renderer,
ssr::StreamBuilder,
view::{add_attr::AddAnyAttr, Position, PositionState},
};
use std::marker::PhantomData;
/// An island of interactivity in an otherwise-inert HTML document.
pub struct Island<Rndr, View> {
pub struct Island<View> {
component: &'static str,
props_json: String,
view: View,
rndr: PhantomData<Rndr>,
}
const ISLAND_TAG: &str = "leptos-island";
const ISLAND_CHILDREN_TAG: &str = "leptos-children";
impl<Rndr, View> Island<Rndr, View> {
impl<View> Island<View> {
/// Creates a new island with the given component name.
pub fn new(component: &'static str, view: View) -> Self {
Island {
component,
props_json: String::new(),
view,
rndr: PhantomData,
}
}
@@ -57,10 +53,9 @@ impl<Rndr, View> Island<Rndr, View> {
}
}
impl<Rndr, View> Render<Rndr> for Island<Rndr, View>
impl<View> Render for Island<View>
where
View: Render<Rndr>,
Rndr: Renderer,
View: Render,
{
type State = View::State;
@@ -73,42 +68,38 @@ where
}
}
impl<Rndr, View> AddAnyAttr<Rndr> for Island<Rndr, View>
impl<View> AddAnyAttr for Island<View>
where
View: RenderHtml<Rndr>,
Rndr: Renderer,
View: RenderHtml,
{
type Output<SomeNewAttr: Attribute<Rndr>> =
Island<Rndr, <View as AddAnyAttr<Rndr>>::Output<SomeNewAttr>>;
type Output<SomeNewAttr: Attribute> =
Island<<View as AddAnyAttr>::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 Island {
component,
props_json,
view,
rndr,
} = self;
Island {
component,
props_json,
view: view.add_any_attr(attr),
rndr,
}
}
}
impl<Rndr, View> RenderHtml<Rndr> for Island<Rndr, View>
impl<View> RenderHtml for Island<View>
where
View: RenderHtml<Rndr>,
Rndr: Renderer,
View: RenderHtml,
{
type AsyncOutput = Island<Rndr, View::AsyncOutput>;
type AsyncOutput = Island<View::AsyncOutput>;
const MIN_LENGTH: usize = ISLAND_TAG.len() * 2
+ "<>".len()
@@ -125,13 +116,11 @@ where
component,
props_json,
view,
rndr,
} = self;
Island {
component,
props_json,
view: view.resolve().await,
rndr,
}
}
@@ -178,7 +167,7 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<Rndr>,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
if position.get() == Position::FirstChild {
@@ -192,18 +181,14 @@ where
}
/// The children that will be projected into an [`Island`].
pub struct IslandChildren<Rndr, View> {
pub struct IslandChildren<View> {
view: View,
rndr: PhantomData<Rndr>,
}
impl<Rndr, View> IslandChildren<Rndr, View> {
impl<View> IslandChildren<View> {
/// Creates a new representation of the children.
pub fn new(view: View) -> Self {
IslandChildren {
view,
rndr: PhantomData,
}
IslandChildren { view }
}
fn open_tag(buf: &mut String) {
@@ -219,10 +204,9 @@ impl<Rndr, View> IslandChildren<Rndr, View> {
}
}
impl<Rndr, View> Render<Rndr> for IslandChildren<Rndr, View>
impl<View> Render for IslandChildren<View>
where
View: Render<Rndr>,
Rndr: Renderer,
View: Render,
{
type State = ();
@@ -231,35 +215,32 @@ where
fn rebuild(self, _state: &mut Self::State) {}
}
impl<Rndr, View> AddAnyAttr<Rndr> for IslandChildren<Rndr, View>
impl<View> AddAnyAttr for IslandChildren<View>
where
View: RenderHtml<Rndr>,
Rndr: Renderer,
View: RenderHtml,
{
type Output<SomeNewAttr: Attribute<Rndr>> =
IslandChildren<Rndr, <View as AddAnyAttr<Rndr>>::Output<SomeNewAttr>>;
type Output<SomeNewAttr: Attribute> =
IslandChildren<<View as AddAnyAttr>::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 IslandChildren { view, rndr } = self;
let IslandChildren { view } = self;
IslandChildren {
view: view.add_any_attr(attr),
rndr,
}
}
}
impl<Rndr, View> RenderHtml<Rndr> for IslandChildren<Rndr, View>
impl<View> RenderHtml for IslandChildren<View>
where
View: RenderHtml<Rndr>,
Rndr: Renderer,
View: RenderHtml,
{
type AsyncOutput = IslandChildren<Rndr, View::AsyncOutput>;
type AsyncOutput = IslandChildren<View::AsyncOutput>;
const MIN_LENGTH: usize = ISLAND_CHILDREN_TAG.len() * 2
+ "<>".len()
@@ -271,10 +252,9 @@ where
}
async fn resolve(self) -> Self::AsyncOutput {
let IslandChildren { view, rndr } = self;
let IslandChildren { view } = self;
IslandChildren {
view: view.resolve().await,
rndr,
}
}
@@ -321,7 +301,7 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<Rndr>,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
// island children aren't hydrated

View File

@@ -3,10 +3,10 @@ use crate::{
hydration::Cursor,
no_attrs,
prelude::AddAnyAttr,
renderer::{CastFrom, DomRenderer, Renderer},
renderer::{CastFrom, Rndr},
view::{Position, PositionState, Render, RenderHtml},
};
use std::{borrow::Cow, marker::PhantomData};
use std::borrow::Cow;
/// Types for HTML attributes.
pub mod attribute;
@@ -28,20 +28,16 @@ pub mod property;
pub mod style;
/// A `<!DOCTYPE>` declaration.
pub struct Doctype<R: Renderer> {
pub struct Doctype {
value: &'static str,
rndr: PhantomData<R>,
}
/// Creates a `<!DOCTYPE>`.
pub fn doctype<R: Renderer>(value: &'static str) -> Doctype<R> {
Doctype {
value,
rndr: PhantomData,
}
pub fn doctype(value: &'static str) -> Doctype {
Doctype { value }
}
impl<R: Renderer> Render<R> for Doctype<R> {
impl Render for Doctype {
type State = ();
fn build(self) -> Self::State {}
@@ -49,12 +45,9 @@ impl<R: Renderer> Render<R> for Doctype<R> {
fn rebuild(self, _state: &mut Self::State) {}
}
no_attrs!(Doctype<R>);
no_attrs!(Doctype);
impl<R> RenderHtml<R> for Doctype<R>
where
R: Renderer + Send,
{
impl RenderHtml for Doctype {
type AsyncOutput = Self;
const MIN_LENGTH: usize = "<!DOCTYPE html>".len();
@@ -79,7 +72,7 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
_cursor: &Cursor<R>,
_cursor: &Cursor,
_position: &PositionState,
) -> Self::State {
}
@@ -97,11 +90,8 @@ impl InertElement {
}
}
impl<Rndr> Render<Rndr> for InertElement
where
Rndr: DomRenderer,
{
type State = Rndr::Element;
impl Render for InertElement {
type State = crate::renderer::types::Element;
fn build(self) -> Self::State {
Rndr::create_element_from_html(&self.html)
@@ -110,18 +100,15 @@ where
fn rebuild(self, _state: &mut Self::State) {}
}
impl<Rndr> AddAnyAttr<Rndr> for InertElement
where
Rndr: DomRenderer,
{
type Output<SomeNewAttr: Attribute<Rndr>> = Self;
impl AddAnyAttr for InertElement {
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,
{
panic!(
"InertElement does not support adding attributes. It should only \
@@ -130,10 +117,7 @@ where
}
}
impl<Rndr> RenderHtml<Rndr> for InertElement
where
Rndr: DomRenderer,
{
impl RenderHtml for InertElement {
type AsyncOutput = Self;
const MIN_LENGTH: usize = 0;
@@ -161,7 +145,7 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<Rndr>,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
let curr_position = position.get();
@@ -170,7 +154,8 @@ where
} else if curr_position != Position::Current {
cursor.sibling();
}
let el = Rndr::Element::cast_from(cursor.current()).unwrap();
let el = crate::renderer::types::Element::cast_from(cursor.current())
.unwrap();
position.set(Position::NextChild);
el
}

View File

@@ -3,30 +3,27 @@ use super::{
element::ElementType,
};
use crate::{
html::element::HtmlElement, prelude::Render, renderer::Renderer,
view::add_attr::AddAnyAttr,
html::element::HtmlElement, prelude::Render, view::add_attr::AddAnyAttr,
};
use std::marker::PhantomData;
/// Describes a container that can be used to hold a reference to an HTML element.
pub trait NodeRefContainer<E, Rndr>: Send + Clone
pub trait NodeRefContainer<E>: Send + Clone
where
E: ElementType,
Rndr: Renderer,
{
/// Fills the container with the element.
fn load(self, el: &Rndr::Element);
fn load(self, el: &crate::renderer::types::Element);
}
/// An [`Attribute`] that will fill a [`NodeRefContainer`] with an HTML element.
#[derive(Debug)]
pub struct NodeRefAttr<E, C, Rndr> {
pub struct NodeRefAttr<E, C> {
container: C,
ty: PhantomData<E>,
rndr: PhantomData<Rndr>,
}
impl<E, C, Rndr> Clone for NodeRefAttr<E, C, Rndr>
impl<E, C> Clone for NodeRefAttr<E, C>
where
C: Clone,
{
@@ -34,35 +31,32 @@ where
Self {
container: self.container.clone(),
ty: PhantomData,
rndr: PhantomData,
}
}
}
/// Creates an attribute that will fill a [`NodeRefContainer`] with the element it is applied to.
pub fn node_ref<E, C, Rndr>(container: C) -> NodeRefAttr<E, C, Rndr>
pub fn node_ref<E, C>(container: C) -> NodeRefAttr<E, C>
where
E: ElementType,
C: NodeRefContainer<E, Rndr>,
Rndr: Renderer,
C: NodeRefContainer<E>,
{
NodeRefAttr {
container,
ty: PhantomData,
rndr: PhantomData,
}
}
impl<E, C, Rndr> Attribute<Rndr> for NodeRefAttr<E, C, Rndr>
impl<E, C> Attribute for NodeRefAttr<E, C>
where
E: ElementType,
C: NodeRefContainer<E, Rndr>,
Rndr: Renderer,
Rndr::Element: PartialEq,
C: NodeRefContainer<E>,
crate::renderer::types::Element: PartialEq,
{
const MIN_LENGTH: usize = 0;
type AsyncOutput = Self;
type State = Rndr::Element;
type State = crate::renderer::types::Element;
type Cloneable = ();
type CloneableOwned = ();
@@ -82,13 +76,13 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
el: &<Rndr as Renderer>::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
self.container.load(el);
el.to_owned()
}
fn build(self, el: &<Rndr as Renderer>::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
self.container.load(el);
el.to_owned()
}
@@ -112,16 +106,16 @@ where
}
}
impl<E, C, Rndr> NextAttribute<Rndr> for NodeRefAttr<E, C, Rndr>
impl<E, C> NextAttribute for NodeRefAttr<E, C>
where
E: ElementType,
C: NodeRefContainer<E, Rndr>,
Rndr: Renderer,
Rndr::Element: PartialEq,
{
type Output<NewAttr: Attribute<Rndr>> = (Self, NewAttr);
C: NodeRefContainer<E>,
fn add_any_attr<NewAttr: Attribute<Rndr>>(
crate::renderer::types::Element: PartialEq,
{
type Output<NewAttr: Attribute> = (Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
@@ -130,35 +124,33 @@ where
}
/// Adds the `node_ref` attribute to an element.
pub trait NodeRefAttribute<E, C, Rndr>
pub trait NodeRefAttribute<E, C>
where
E: ElementType,
C: NodeRefContainer<E, Rndr>,
Rndr: Renderer,
Rndr::Element: PartialEq,
C: NodeRefContainer<E>,
crate::renderer::types::Element: PartialEq,
{
/// Binds this HTML element to a [`NodeRefContainer`].
fn node_ref(
self,
container: C,
) -> <Self as AddAnyAttr<Rndr>>::Output<NodeRefAttr<E, C, Rndr>>
) -> <Self as AddAnyAttr>::Output<NodeRefAttr<E, C>>
where
Self: Sized + AddAnyAttr<Rndr>,
<Self as AddAnyAttr<Rndr>>::Output<NodeRefAttr<E, C, Rndr>>:
Render<Rndr>,
Self: Sized + AddAnyAttr,
<Self as AddAnyAttr>::Output<NodeRefAttr<E, C>>: Render,
{
self.add_any_attr(node_ref(container))
}
}
impl<E, At, Ch, C, Rndr> NodeRefAttribute<E, C, Rndr>
for HtmlElement<E, At, Ch, Rndr>
impl<E, At, Ch, C> NodeRefAttribute<E, C> for HtmlElement<E, At, Ch>
where
E: ElementType,
At: Attribute<Rndr>,
Ch: Render<Rndr>,
C: NodeRefContainer<E, Rndr>,
Rndr: Renderer,
Rndr::Element: PartialEq,
At: Attribute,
Ch: Render,
C: NodeRefContainer<E>,
crate::renderer::types::Element: PartialEq,
{
}

View File

@@ -1,37 +1,34 @@
use super::attribute::{Attribute, NextAttribute};
use crate::{
renderer::DomRenderer,
renderer::Rndr,
view::{Position, ToTemplate},
};
use send_wrapper::SendWrapper;
use std::{borrow::Cow, marker::PhantomData, sync::Arc};
use std::{borrow::Cow, sync::Arc};
use wasm_bindgen::JsValue;
/// Creates an [`Attribute`] that will set a DOM property on an element.
#[inline(always)]
pub fn prop<K, P, R>(key: K, value: P) -> Property<K, P, R>
pub fn prop<K, P>(key: K, value: P) -> Property<K, P>
where
K: AsRef<str>,
P: IntoProperty<R>,
R: DomRenderer,
P: IntoProperty,
{
Property {
key,
value: Some(SendWrapper::new(value)),
rndr: PhantomData,
}
}
/// An [`Attribute`] that will set a DOM property on an element.
#[derive(Debug)]
pub struct Property<K, P, R> {
pub struct Property<K, P> {
key: K,
// property values will only be accessed in the browser
value: Option<SendWrapper<P>>,
rndr: PhantomData<R>,
}
impl<K, P, R> Clone for Property<K, P, R>
impl<K, P> Clone for Property<K, P>
where
K: Clone,
P: Clone,
@@ -40,23 +37,21 @@ where
Self {
key: self.key.clone(),
value: self.value.clone(),
rndr: PhantomData,
}
}
}
impl<K, P, R> Attribute<R> for Property<K, P, R>
impl<K, P> Attribute for Property<K, P>
where
K: AsRef<str> + Send,
P: IntoProperty<R>,
R: DomRenderer,
P: IntoProperty,
{
const MIN_LENGTH: usize = 0;
type AsyncOutput = Self;
type State = P::State;
type Cloneable = Property<Arc<str>, P::Cloneable, R>;
type CloneableOwned = Property<Arc<str>, P::CloneableOwned, R>;
type Cloneable = Property<Arc<str>, P::Cloneable>;
type CloneableOwned = Property<Arc<str>, P::CloneableOwned>;
#[inline(always)]
fn html_len(&self) -> usize {
@@ -72,14 +67,17 @@ 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 {
self.value
.expect("property removed early")
.take()
.hydrate::<FROM_SERVER>(el, self.key.as_ref())
}
fn build(self, el: &R::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
self.value
.expect("property removed early")
.take()
@@ -99,7 +97,6 @@ where
value: self
.value
.map(|value| SendWrapper::new(value.take().into_cloneable())),
rndr: self.rndr,
}
}
@@ -109,7 +106,6 @@ where
value: self.value.map(|value| {
SendWrapper::new(value.take().into_cloneable_owned())
}),
rndr: self.rndr,
}
}
@@ -126,15 +122,14 @@ where
}
}
impl<K, P, R> NextAttribute<R> for Property<K, P, R>
impl<K, P> NextAttribute for Property<K, P>
where
K: AsRef<str> + Send,
P: IntoProperty<R>,
R: DomRenderer,
P: IntoProperty,
{
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> {
@@ -142,11 +137,10 @@ where
}
}
impl<K, P, R> ToTemplate for Property<K, P, R>
impl<K, P> ToTemplate for Property<K, P>
where
K: AsRef<str>,
P: IntoProperty<R>,
R: DomRenderer,
P: IntoProperty,
{
fn to_template(
_buf: &mut String,
@@ -159,23 +153,27 @@ where
}
/// A possible value for a DOM property.
pub trait IntoProperty<R: DomRenderer> {
pub trait IntoProperty {
/// The view state retained between building and rebuilding.
type State;
/// An equivalent value that can be cloned.
type Cloneable: IntoProperty<R> + Clone;
type Cloneable: IntoProperty + Clone;
/// An equivalent value that can be cloned and is `'static`.
type CloneableOwned: IntoProperty<R> + Clone + 'static;
type CloneableOwned: IntoProperty + Clone + 'static;
/// Adds the property on an element created from HTML.
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State;
/// Adds the property 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;
/// Updates the property with a new value.
fn rebuild(self, state: &mut Self::State, key: &str);
@@ -189,34 +187,35 @@ pub trait IntoProperty<R: DomRenderer> {
macro_rules! prop_type {
($prop_type:ty) => {
impl<R> IntoProperty<R> for $prop_type
where
R: DomRenderer,
{
type State = (R::Element, JsValue);
impl IntoProperty for $prop_type {
type State = (crate::renderer::types::Element, JsValue);
type Cloneable = Self;
type CloneableOwned = Self;
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let value = self.into();
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn build(self, el: &R::Element, key: &str) -> Self::State {
fn build(
self,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let value = self.into();
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = self.into();
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -229,32 +228,33 @@ macro_rules! prop_type {
}
}
impl<R> IntoProperty<R> for Option<$prop_type>
where
R: DomRenderer,
{
type State = (R::Element, JsValue);
impl IntoProperty for Option<$prop_type> {
type State = (crate::renderer::types::Element, JsValue);
type Cloneable = Self;
type CloneableOwned = Self;
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let was_some = self.is_some();
let value = self.into();
if was_some {
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
fn build(self, el: &R::Element, key: &str) -> Self::State {
fn build(
self,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let was_some = self.is_some();
let value = self.into();
if was_some {
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -262,7 +262,7 @@ macro_rules! prop_type {
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = self.into();
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -279,34 +279,35 @@ macro_rules! prop_type {
macro_rules! prop_type_str {
($prop_type:ty) => {
impl<R> IntoProperty<R> for $prop_type
where
R: DomRenderer,
{
type State = (R::Element, JsValue);
impl IntoProperty for $prop_type {
type State = (crate::renderer::types::Element, JsValue);
type Cloneable = Arc<str>;
type CloneableOwned = Arc<str>;
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let value = JsValue::from(&*self);
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn build(self, el: &R::Element, key: &str) -> Self::State {
fn build(
self,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let value = JsValue::from(&*self);
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from(&*self);
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -321,32 +322,33 @@ macro_rules! prop_type_str {
}
}
impl<R> IntoProperty<R> for Option<$prop_type>
where
R: DomRenderer,
{
type State = (R::Element, JsValue);
impl IntoProperty for Option<$prop_type> {
type State = (crate::renderer::types::Element, JsValue);
type Cloneable = Option<Arc<str>>;
type CloneableOwned = Option<Arc<str>>;
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let was_some = self.is_some();
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
if was_some {
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
fn build(self, el: &R::Element, key: &str) -> Self::State {
fn build(
self,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let was_some = self.is_some();
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
if was_some {
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -354,7 +356,7 @@ macro_rules! prop_type_str {
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -375,34 +377,35 @@ macro_rules! prop_type_str {
};
}
impl<R> IntoProperty<R> for Arc<str>
where
R: DomRenderer,
{
type State = (R::Element, JsValue);
impl IntoProperty for Arc<str> {
type State = (crate::renderer::types::Element, JsValue);
type Cloneable = Self;
type CloneableOwned = Self;
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn build(self, el: &R::Element, key: &str) -> Self::State {
fn build(
self,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let value = JsValue::from_str(self.as_ref());
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
(el.clone(), value)
}
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from_str(self.as_ref());
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}
@@ -415,32 +418,33 @@ where
}
}
impl<R> IntoProperty<R> for Option<Arc<str>>
where
R: DomRenderer,
{
type State = (R::Element, JsValue);
impl IntoProperty for Option<Arc<str>> {
type State = (crate::renderer::types::Element, JsValue);
type Cloneable = Self;
type CloneableOwned = Self;
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let was_some = self.is_some();
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
if was_some {
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
fn build(self, el: &R::Element, key: &str) -> Self::State {
fn build(
self,
el: &crate::renderer::types::Element,
key: &str,
) -> Self::State {
let was_some = self.is_some();
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
if was_some {
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
}
(el.clone(), value)
}
@@ -448,7 +452,7 @@ where
fn rebuild(self, state: &mut Self::State, key: &str) {
let (el, prev) = state;
let value = JsValue::from(self.map(|n| JsValue::from_str(&n)));
R::set_property(el, key, &value);
Rndr::set_property(el, key, &value);
*prev = value;
}

View File

@@ -2,54 +2,47 @@ use super::attribute::{Attribute, NextAttribute};
#[cfg(feature = "nightly")]
use crate::view::static_types::Static;
use crate::{
renderer::DomRenderer,
renderer::Rndr,
view::{Position, ToTemplate},
};
use std::{future::Future, marker::PhantomData, sync::Arc};
use std::{future::Future, sync::Arc};
/// Returns an [`Attribute`] that will add to an element's CSS styles.
#[inline(always)]
pub fn style<S, R>(style: S) -> Style<S, R>
pub fn style<S>(style: S) -> Style<S>
where
S: IntoStyle<R>,
R: DomRenderer,
S: IntoStyle,
{
Style {
style,
rndr: PhantomData,
}
Style { style }
}
/// An [`Attribute`] that will add to an element's CSS styles.
#[derive(Debug)]
pub struct Style<S, R> {
pub struct Style<S> {
style: S,
rndr: PhantomData<R>,
}
impl<S, R> Clone for Style<S, R>
impl<S> Clone for Style<S>
where
S: Clone,
{
fn clone(&self) -> Self {
Self {
style: self.style.clone(),
rndr: PhantomData,
}
}
}
impl<S, R> Attribute<R> for Style<S, R>
impl<S> Attribute for Style<S>
where
S: IntoStyle<R>,
R: DomRenderer,
S: IntoStyle,
{
const MIN_LENGTH: usize = 0;
type AsyncOutput = Style<S::AsyncOutput, R>;
type AsyncOutput = Style<S::AsyncOutput>;
type State = S::State;
type Cloneable = Style<S::Cloneable, R>;
type CloneableOwned = Style<S::CloneableOwned, R>;
type Cloneable = Style<S::Cloneable>;
type CloneableOwned = Style<S::CloneableOwned>;
// TODO
#[inline(always)]
@@ -67,11 +60,14 @@ where
self.style.to_html(style);
}
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.style.hydrate::<FROM_SERVER>(el)
}
fn build(self, el: &R::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
self.style.build(el)
}
@@ -82,14 +78,12 @@ where
fn into_cloneable(self) -> Self::Cloneable {
Style {
style: self.style.into_cloneable(),
rndr: self.rndr,
}
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
Style {
style: self.style.into_cloneable_owned(),
rndr: self.rndr,
}
}
@@ -100,19 +94,17 @@ where
async fn resolve(self) -> Self::AsyncOutput {
Style {
style: self.style.resolve().await,
rndr: self.rndr,
}
}
}
impl<S, R> NextAttribute<R> for Style<S, R>
impl<S> NextAttribute for Style<S>
where
S: IntoStyle<R>,
R: DomRenderer,
S: IntoStyle,
{
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> {
@@ -120,10 +112,9 @@ where
}
}
impl<S, R> ToTemplate for Style<S, R>
impl<S> ToTemplate for Style<S>
where
S: IntoStyle<R>,
R: DomRenderer,
S: IntoStyle,
{
fn to_template(
_buf: &mut String,
@@ -138,25 +129,28 @@ where
/// Any type that can be added to the `style` attribute or set as a style in
/// the [`CssStyleDeclaration`]. This could be a plain string, or a property name-value pair.
pub trait IntoStyle<R: DomRenderer>: Send {
pub trait IntoStyle: Send {
/// The type after all async data have resolved.
type AsyncOutput: IntoStyle<R>;
type AsyncOutput: IntoStyle;
/// The view state retained between building and rebuilding.
type State;
/// An equivalent value that can be cloned.
type Cloneable: IntoStyle<R> + Clone;
type Cloneable: IntoStyle + Clone;
/// An equivalent value that can be cloned and is `'static`.
type CloneableOwned: IntoStyle<R> + Clone + 'static;
type CloneableOwned: IntoStyle + Clone + 'static;
/// Renders the style to HTML.
fn to_html(self, style: &mut String);
/// 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 style 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;
/// Updates the value.
fn rebuild(self, state: &mut Self::State);
@@ -176,12 +170,9 @@ pub trait IntoStyle<R: DomRenderer>: Send {
fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send;
}
impl<'a, R> IntoStyle<R> for &'a str
where
R: DomRenderer,
{
impl<'a> IntoStyle for &'a str {
type AsyncOutput = Self;
type State = (R::Element, &'a str);
type State = (crate::renderer::types::Element, &'a str);
type Cloneable = Self;
type CloneableOwned = Arc<str>;
@@ -190,19 +181,22 @@ where
style.push(';');
}
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 {
(el.clone(), self)
}
fn build(self, el: &R::Element) -> Self::State {
R::set_attribute(el, "style", self);
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_attribute(el, "style", self);
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
let (el, prev) = state;
if self != *prev {
R::set_attribute(el, "style", self);
Rndr::set_attribute(el, "style", self);
}
*prev = self;
}
@@ -222,12 +216,9 @@ where
}
}
impl<R> IntoStyle<R> for Arc<str>
where
R: DomRenderer,
{
impl IntoStyle for Arc<str> {
type AsyncOutput = Self;
type State = (R::Element, Arc<str>);
type State = (crate::renderer::types::Element, Arc<str>);
type Cloneable = Self;
type CloneableOwned = Self;
@@ -236,19 +227,22 @@ where
style.push(';');
}
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 {
(el.clone(), self)
}
fn build(self, el: &R::Element) -> Self::State {
R::set_attribute(el, "style", &self);
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_attribute(el, "style", &self);
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
let (el, prev) = state;
if self != *prev {
R::set_attribute(el, "style", &self);
Rndr::set_attribute(el, "style", &self);
}
*prev = self;
}
@@ -268,12 +262,9 @@ where
}
}
impl<R> IntoStyle<R> for String
where
R: DomRenderer,
{
impl IntoStyle 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>;
@@ -282,19 +273,22 @@ where
style.push(';');
}
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 {
(el.clone(), self)
}
fn build(self, el: &R::Element) -> Self::State {
R::set_attribute(el, "style", &self);
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_attribute(el, "style", &self);
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
let (el, prev) = state;
if self != *prev {
R::set_attribute(el, "style", &self);
Rndr::set_attribute(el, "style", &self);
}
*prev = self;
}
@@ -314,12 +308,9 @@ where
}
}
impl<R> IntoStyle<R> for (Arc<str>, Arc<str>)
where
R: DomRenderer,
{
impl IntoStyle for (Arc<str>, Arc<str>) {
type AsyncOutput = Self;
type State = (R::CssStyleDeclaration, Arc<str>);
type State = (crate::renderer::types::CssStyleDeclaration, Arc<str>);
type Cloneable = Self;
type CloneableOwned = Self;
@@ -331,15 +322,18 @@ where
style.push(';');
}
fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State {
let style = R::style(el);
fn hydrate<const FROM_SERVER: bool>(
self,
el: &crate::renderer::types::Element,
) -> Self::State {
let style = Rndr::style(el);
(style, self.1)
}
fn build(self, el: &R::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
let (name, value) = self;
let style = R::style(el);
R::set_css_property(&style, &name, &value);
let style = Rndr::style(el);
Rndr::set_css_property(&style, &name, &value);
(style, value)
}
@@ -347,7 +341,7 @@ where
let (name, value) = self;
let (style, prev) = state;
if value != *prev {
R::set_css_property(style, &name, &value);
Rndr::set_css_property(style, &name, &value);
}
*prev = value;
}
@@ -367,12 +361,9 @@ where
}
}
impl<'a, R> IntoStyle<R> for (&'a str, &'a str)
where
R: DomRenderer,
{
impl<'a> IntoStyle for (&'a str, &'a str) {
type AsyncOutput = Self;
type State = (R::CssStyleDeclaration, &'a str);
type State = (crate::renderer::types::CssStyleDeclaration, &'a str);
type Cloneable = Self;
type CloneableOwned = (Arc<str>, Arc<str>);
@@ -384,15 +375,18 @@ where
style.push(';');
}
fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State {
let style = R::style(el);
fn hydrate<const FROM_SERVER: bool>(
self,
el: &crate::renderer::types::Element,
) -> Self::State {
let style = Rndr::style(el);
(style, self.1)
}
fn build(self, el: &R::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
let (name, value) = self;
let style = R::style(el);
R::set_css_property(&style, name, value);
let style = Rndr::style(el);
Rndr::set_css_property(&style, name, value);
(style, self.1)
}
@@ -400,7 +394,7 @@ where
let (name, value) = self;
let (style, prev) = state;
if value != *prev {
R::set_css_property(style, name, value);
Rndr::set_css_property(style, name, value);
}
*prev = value;
}
@@ -420,12 +414,9 @@ where
}
}
impl<'a, R> IntoStyle<R> for (&'a str, String)
where
R: DomRenderer,
{
impl<'a> IntoStyle for (&'a str, String) {
type AsyncOutput = Self;
type State = (R::CssStyleDeclaration, String);
type State = (crate::renderer::types::CssStyleDeclaration, String);
type Cloneable = (Arc<str>, Arc<str>);
type CloneableOwned = (Arc<str>, Arc<str>);
@@ -437,15 +428,18 @@ where
style.push(';');
}
fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State {
let style = R::style(el);
fn hydrate<const FROM_SERVER: bool>(
self,
el: &crate::renderer::types::Element,
) -> Self::State {
let style = Rndr::style(el);
(style, self.1)
}
fn build(self, el: &R::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
let (name, value) = &self;
let style = R::style(el);
R::set_css_property(&style, name, value);
let style = Rndr::style(el);
Rndr::set_css_property(&style, name, value);
(style, self.1)
}
@@ -453,7 +447,7 @@ where
let (name, value) = self;
let (style, prev) = state;
if value != *prev {
R::set_css_property(style, name, &value);
Rndr::set_css_property(style, name, &value);
}
*prev = value;
}
@@ -474,10 +468,7 @@ where
}
#[cfg(feature = "nightly")]
impl<'a, const V: &'static str, R> IntoStyle<R> for (&'a str, Static<V>)
where
R: DomRenderer,
{
impl<'a, const V: &'static str> IntoStyle for (&'a str, Static<V>) {
type AsyncOutput = Self;
type State = ();
type Cloneable = (Arc<str>, Static<V>);
@@ -491,13 +482,16 @@ where
style.push(';');
}
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 {
let (name, _) = &self;
let style = R::style(el);
R::set_css_property(&style, name, V);
let style = Rndr::style(el);
Rndr::set_css_property(&style, name, V);
}
fn rebuild(self, _state: &mut Self::State) {}
@@ -518,10 +512,7 @@ where
}
#[cfg(feature = "nightly")]
impl<const V: &'static str, R> IntoStyle<R> for (Arc<str>, Static<V>)
where
R: DomRenderer,
{
impl<const V: &'static str> IntoStyle for (Arc<str>, Static<V>) {
type AsyncOutput = Self;
type State = ();
type Cloneable = (Arc<str>, Static<V>);
@@ -535,13 +526,16 @@ where
style.push(';');
}
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 {
let (name, _) = &self;
let style = R::style(el);
R::set_css_property(&style, name, V);
let style = Rndr::style(el);
Rndr::set_css_property(&style, name, V);
}
fn rebuild(self, _state: &mut Self::State) {}
@@ -562,11 +556,7 @@ where
}
#[cfg(feature = "nightly")]
impl<const V: &'static str, R> IntoStyle<R>
for crate::view::static_types::Static<V>
where
R: DomRenderer,
{
impl<const V: &'static str> IntoStyle for crate::view::static_types::Static<V> {
type AsyncOutput = Self;
type State = ();
type Cloneable = Self;
@@ -579,12 +569,12 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
_el: &<R>::Element,
_el: &crate::renderer::types::Element,
) -> Self::State {
}
fn build(self, el: &<R>::Element) -> Self::State {
R::set_attribute(el, "style", V);
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_attribute(el, "style", V);
}
fn rebuild(self, _state: &mut Self::State) {}

View File

@@ -1,5 +1,5 @@
use crate::{
renderer::{CastFrom, Renderer},
renderer::{CastFrom, Rndr},
view::{Position, PositionState},
};
use std::{cell::RefCell, rc::Rc};
@@ -10,27 +10,29 @@ use std::{cell::RefCell, rc::Rc};
/// implements [`RenderHtml`](crate::view::RenderHtml) knows how to advance the cursor to access
/// the nodes it needs.
#[derive(Debug)]
pub struct Cursor<R: Renderer>(Rc<RefCell<R::Node>>);
pub struct Cursor(Rc<RefCell<crate::renderer::types::Node>>);
impl<R: Renderer> Clone for Cursor<R> {
impl Clone for Cursor {
fn clone(&self) -> Self {
Self(Rc::clone(&self.0))
}
}
impl<R> Cursor<R>
impl Cursor
where
R: Renderer,
R::Element: AsRef<R::Node>,
crate::renderer::types::Element: AsRef<crate::renderer::types::Node>,
{
/// Creates a new cursor starting at the root element.
pub fn new(root: R::Element) -> Self {
Self(Rc::new(RefCell::new(root.as_ref().clone())))
pub fn new(root: crate::renderer::types::Element) -> Self {
let root = <crate::renderer::types::Element as AsRef<
crate::renderer::types::Node,
>>::as_ref(&root)
.clone();
Self(Rc::new(RefCell::new(root)))
}
/// Returns the node at which the cursor is currently located.
pub fn current(&self) -> R::Node {
pub fn current(&self) -> crate::renderer::types::Node {
self.0.borrow().clone()
}
@@ -39,14 +41,14 @@ where
/// Does nothing if there is no child.
pub fn child(&self) {
//crate::log("advancing to next child of ");
//R::log_node(&self.current());
//Rndr::log_node(&self.current());
let mut inner = self.0.borrow_mut();
if let Some(node) = R::first_child(&*inner) {
if let Some(node) = Rndr::first_child(&inner) {
*inner = node;
}
//drop(inner);
//crate::log(">> which is ");
//R::log_node(&self.current());
//Rndr::log_node(&self.current());
}
/// Advances to the next sibling of the node at which the cursor is located.
@@ -54,14 +56,14 @@ where
/// Does nothing if there is no sibling.
pub fn sibling(&self) {
//crate::log("advancing to next sibling of ");
//R::log_node(&self.current());
//Rndr::log_node(&self.current());
let mut inner = self.0.borrow_mut();
if let Some(node) = R::next_sibling(&*inner) {
if let Some(node) = Rndr::next_sibling(&inner) {
*inner = node;
}
//drop(inner);
//crate::log(">> which is ");
//R::log_node(&self.current());
//Rndr::log_node(&self.current());
}
/// Moves to the parent of the node at which the cursor is located.
@@ -69,20 +71,23 @@ where
/// Does nothing if there is no parent.
pub fn parent(&self) {
let mut inner = self.0.borrow_mut();
if let Some(node) = R::get_parent(&*inner) {
if let Some(node) = Rndr::get_parent(&inner) {
*inner = node;
}
}
/// Sets the cursor to some node.
pub fn set(&self, node: R::Node) {
pub fn set(&self, node: crate::renderer::types::Node) {
*self.0.borrow_mut() = node;
}
/// Advances to the next placeholder node.
pub fn next_placeholder(&self, position: &PositionState) -> R::Placeholder {
pub fn next_placeholder(
&self,
position: &PositionState,
) -> crate::renderer::types::Placeholder {
//crate::dom::log("looking for placeholder after");
//R::log_node(&self.current());
//Rndr::log_node(&self.current());
if position.get() == Position::FirstChild {
self.child();
} else {
@@ -90,12 +95,12 @@ where
}
let marker = self.current();
position.set(Position::NextChild);
R::Placeholder::cast_from(marker)
crate::renderer::types::Placeholder::cast_from(marker)
.expect("could not convert current node into marker node")
/*let marker2 = marker.clone();
R::Placeholder::cast_from(marker).unwrap_or_else(|| {
Rndr::Placeholder::cast_from(marker).unwrap_or_else(|| {
crate::dom::log("expecting to find a marker. instead, found");
R::log_node(&marker2);
Rndr::log_node(&marker2);
panic!("oops.");
})*/
}

View File

@@ -5,7 +5,7 @@
#![allow(incomplete_features)] // yolo
#![cfg_attr(feature = "nightly", feature(unsized_const_params))]
#![deny(missing_docs)]
//#![deny(missing_docs)]
/// Commonly-used traits.
pub mod prelude {

View File

@@ -1,16 +1,12 @@
use crate::{
html::{
attribute::{Attr, Attribute, AttributeValue},
element::{
CreateElement, ElementType, ElementWithChildren, HtmlElement,
},
element::{ElementType, ElementWithChildren, HtmlElement},
},
renderer::{dom::Dom, Renderer},
view::Render,
};
use next_tuple::NextTuple;
use once_cell::unsync::Lazy;
use std::{fmt::Debug, marker::PhantomData};
use std::fmt::Debug;
macro_rules! mathml_global {
($tag:ty, $attr:ty) => {
@@ -18,18 +14,18 @@ macro_rules! mathml_global {
/// A MathML attribute.
pub fn $attr<V>(self, value: V) -> HtmlElement <
[<$tag:camel>],
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V, Rndr>>,
Ch, Rndr
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
Ch
>
where
V: AttributeValue<Rndr>,
V: AttributeValue,
At: NextTuple,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V, Rndr>>: Attribute<Rndr>,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>: Attribute,
{
let HtmlElement { tag, rndr, children, attributes } = self;
let HtmlElement { tag, children, attributes } = self;
HtmlElement {
tag,
rndr,
children,
attributes: attributes.next_tuple($crate::html::attribute::$attr(value)),
}
@@ -45,15 +41,15 @@ macro_rules! mathml_elements {
// `tag()` function
/// A MathML element.
#[track_caller]
pub fn $tag<Rndr>() -> HtmlElement<[<$tag:camel>], (), (), Rndr>
pub fn $tag() -> HtmlElement<[<$tag:camel>], (), ()>
where
Rndr: Renderer
{
HtmlElement {
tag: [<$tag:camel>],
attributes: (),
children: (),
rndr: PhantomData,
}
}
@@ -61,11 +57,11 @@ macro_rules! mathml_elements {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct [<$tag:camel>];
impl<At, Ch, Rndr> HtmlElement<[<$tag:camel>], At, Ch, Rndr>
impl<At, Ch> HtmlElement<[<$tag:camel>], At, Ch>
where
At: Attribute<Rndr>,
Ch: Render<Rndr>,
Rndr: Renderer,
At: Attribute,
Ch: Render,
{
mathml_global!($tag, displaystyle);
mathml_global!($tag, href);
@@ -80,18 +76,18 @@ macro_rules! mathml_elements {
/// A MathML attribute.
pub fn $attr<V>(self, value: V) -> HtmlElement <
[<$tag:camel>],
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V, Rndr>>,
Ch, Rndr
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
Ch
>
where
V: AttributeValue<Rndr>,
V: AttributeValue,
At: NextTuple,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V, Rndr>>: Attribute<Rndr>,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>: Attribute,
{
let HtmlElement { tag, rndr, children, attributes } = self;
let HtmlElement { tag, children, attributes } = self;
HtmlElement {
tag,
rndr,
children,
attributes: attributes.next_tuple($crate::html::attribute::$attr(value)),
}
@@ -105,6 +101,7 @@ macro_rules! mathml_elements {
const TAG: &'static str = stringify!($tag);
const SELF_CLOSING: bool = false;
const ESCAPE_CHILDREN: bool = true;
const NAMESPACE: Option<&'static str> = Some("http://www.w3.org/1998/Math/MathML");
#[inline(always)]
fn tag(&self) -> &str {
@@ -113,22 +110,6 @@ macro_rules! mathml_elements {
}
impl ElementWithChildren for [<$tag:camel>] {}
impl CreateElement<Dom> for [<$tag:camel>] {
fn create_element(&self) -> <Dom as Renderer>::Element {
use wasm_bindgen::JsCast;
thread_local! {
static ELEMENT: Lazy<<Dom as Renderer>::Element> = Lazy::new(|| {
crate::dom::document().create_element_ns(
Some(wasm_bindgen::intern("http://www.w3.org/1998/Math/MathML")),
stringify!($tag)
).unwrap()
});
}
ELEMENT.with(|e| e.clone_node()).unwrap().unchecked_into()
}
}
)*
}
}

View File

@@ -3,29 +3,29 @@ use crate::{
hydration::Cursor,
no_attrs,
prelude::{Mountable, Render, RenderHtml},
renderer::{DomRenderer, Renderer},
renderer::Rndr,
view::{strings::StrState, Position, PositionState, ToTemplate},
};
use oco_ref::Oco;
/// Retained view state for [`Oco`].
pub struct OcoStrState<R: Renderer> {
node: R::Text,
pub struct OcoStrState {
node: crate::renderer::types::Text,
str: Oco<'static, str>,
}
impl<R: Renderer> Render<R> for Oco<'static, str> {
type State = OcoStrState<R>;
impl Render for Oco<'static, str> {
type State = OcoStrState;
fn build(self) -> Self::State {
let node = R::create_text_node(&self);
let node = Rndr::create_text_node(&self);
OcoStrState { node, str: self }
}
fn rebuild(self, state: &mut Self::State) {
let OcoStrState { node, str } = state;
if &self == str {
R::set_text(node, &self);
Rndr::set_text(node, &self);
*str = self;
}
}
@@ -33,10 +33,7 @@ impl<R: Renderer> Render<R> for Oco<'static, str> {
no_attrs!(Oco<'static, str>);
impl<R> RenderHtml<R> for Oco<'static, str>
where
R: Renderer,
{
impl RenderHtml for Oco<'static, str> {
type AsyncOutput = Self;
const MIN_LENGTH: usize = 0;
@@ -54,7 +51,7 @@ where
escape: bool,
mark_branches: bool,
) {
<&str as RenderHtml<R>>::to_html_with_buf(
<&str as RenderHtml>::to_html_with_buf(
&self,
buf,
position,
@@ -65,13 +62,13 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<R>,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
let this: &str = self.as_ref();
let StrState { node, .. } = <&str as RenderHtml<R>>::hydrate::<
FROM_SERVER,
>(this, cursor, position);
let StrState { node, .. } = <&str as RenderHtml>::hydrate::<FROM_SERVER>(
this, cursor, position,
);
OcoStrState { node, str: self }
}
}
@@ -92,30 +89,27 @@ impl ToTemplate for Oco<'static, str> {
}
}
impl<R: Renderer> Mountable<R> for OcoStrState<R> {
impl Mountable for OcoStrState {
fn unmount(&mut self) {
self.node.unmount()
}
fn mount(
&mut self,
parent: &<R as Renderer>::Element,
marker: Option<&<R as Renderer>::Node>,
parent: &crate::renderer::types::Element,
marker: Option<&crate::renderer::types::Node>,
) {
R::insert_node(parent, self.node.as_ref(), marker);
Rndr::insert_node(parent, self.node.as_ref(), marker);
}
fn insert_before_this(&self, child: &mut dyn Mountable<R>) -> bool {
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
self.node.insert_before_this(child)
}
}
impl<R> AttributeValue<R> for Oco<'static, str>
where
R: Renderer,
{
impl AttributeValue for Oco<'static, str> {
type AsyncOutput = Self;
type State = (R::Element, Oco<'static, str>);
type State = (crate::renderer::types::Element, Oco<'static, str>);
type Cloneable = Self;
type CloneableOwned = Self;
@@ -124,7 +118,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) {}
@@ -132,9 +126,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,
@@ -142,15 +136,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;
}
@@ -174,12 +172,9 @@ where
}
}
impl<R> IntoClass<R> for Oco<'static, str>
where
R: DomRenderer,
{
impl IntoClass for Oco<'static, str> {
type AsyncOutput = Self;
type State = (R::Element, Self);
type State = (crate::renderer::types::Element, Self);
type Cloneable = Self;
type CloneableOwned = Self;
@@ -188,25 +183,28 @@ where
}
fn to_html(self, class: &mut String) {
IntoClass::<R>::to_html(self.as_str(), class);
IntoClass::to_html(self.as_str(), class);
}
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 !FROM_SERVER {
R::set_attribute(el, "class", &self);
Rndr::set_attribute(el, "class", &self);
}
(el.clone(), self)
}
fn build(self, el: &R::Element) -> Self::State {
R::set_attribute(el, "class", &self);
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
Rndr::set_attribute(el, "class", &self);
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
let (el, prev) = state;
if self != *prev {
R::set_attribute(el, "class", &self);
Rndr::set_attribute(el, "class", &self);
}
*prev = self;
}

View File

@@ -1,6 +1,5 @@
use super::{ReactiveFunction, SharedReactiveFunction, Suspend};
use crate::{html::class::IntoClass, renderer::DomRenderer};
use any_spawner::Executor;
use crate::{html::class::IntoClass, renderer::Rndr};
use futures::FutureExt;
use reactive_graph::{effect::RenderEffect, signal::guards::ReadGuard};
use std::{
@@ -8,12 +7,11 @@ use std::{
sync::Arc,
};
impl<F, C, R> IntoClass<R> for F
impl<F, C> IntoClass for F
where
F: ReactiveFunction<Output = C>,
C: IntoClass<R> + 'static,
C: IntoClass + 'static,
C::State: 'static,
R: DomRenderer,
{
type AsyncOutput = C::AsyncOutput;
type State = RenderEffect<C::State>;
@@ -31,7 +29,7 @@ where
fn hydrate<const FROM_SERVER: bool>(
mut self,
el: &R::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
// TODO FROM_SERVER vs template
let el = el.clone();
@@ -46,7 +44,7 @@ where
})
}
fn build(mut self, el: &R::Element) -> Self::State {
fn build(mut self, el: &crate::renderer::types::Element) -> Self::State {
let el = el.to_owned();
RenderEffect::new(move |prev| {
let value = self.invoke();
@@ -92,14 +90,13 @@ where
}
}
impl<F, T, R> IntoClass<R> for (&'static str, F)
impl<F, T> IntoClass for (&'static str, F)
where
F: ReactiveFunction<Output = T>,
T: Borrow<bool> + Send + 'static,
R: DomRenderer,
{
type AsyncOutput = (&'static str, bool);
type State = RenderEffect<(R::ClassList, bool)>;
type State = RenderEffect<(crate::renderer::types::ClassList, bool)>;
type Cloneable = (&'static str, SharedReactiveFunction<T>);
type CloneableOwned = (&'static str, SharedReactiveFunction<T>);
@@ -111,56 +108,63 @@ where
let (name, mut f) = self;
let include = *f.invoke().borrow();
if include {
<&str as IntoClass<R>>::to_html(name, class);
<&str as IntoClass>::to_html(name, class);
}
}
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 {
// TODO FROM_SERVER vs template
let (name, mut f) = self;
let class_list = R::class_list(el);
let name = R::intern(name);
let class_list = Rndr::class_list(el);
let name = Rndr::intern(name);
RenderEffect::new(move |prev: Option<(R::ClassList, bool)>| {
let include = *f.invoke().borrow();
if let Some((class_list, prev)) = prev {
if include {
if !prev {
R::add_class(&class_list, name);
}
} else if prev {
R::remove_class(&class_list, name);
}
}
(class_list.clone(), include)
})
}
fn build(self, el: &R::Element) -> Self::State {
let (name, mut f) = self;
let class_list = R::class_list(el);
let name = R::intern(name);
RenderEffect::new(move |prev: Option<(R::ClassList, bool)>| {
let include = *f.invoke().borrow();
match prev {
Some((class_list, prev)) => {
RenderEffect::new(
move |prev: Option<(crate::renderer::types::ClassList, bool)>| {
let include = *f.invoke().borrow();
if let Some((class_list, prev)) = prev {
if include {
if !prev {
R::add_class(&class_list, name);
Rndr::add_class(&class_list, name);
}
} else if prev {
R::remove_class(&class_list, name);
Rndr::remove_class(&class_list, name);
}
}
None => {
if include {
R::add_class(&class_list, name);
(class_list.clone(), include)
},
)
}
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
let (name, mut f) = self;
let class_list = Rndr::class_list(el);
let name = Rndr::intern(name);
RenderEffect::new(
move |prev: Option<(crate::renderer::types::ClassList, bool)>| {
let include = *f.invoke().borrow();
match prev {
Some((class_list, prev)) => {
if include {
if !prev {
Rndr::add_class(&class_list, name);
}
} else if prev {
Rndr::remove_class(&class_list, name);
}
}
None => {
if include {
Rndr::add_class(&class_list, name);
}
}
}
}
(class_list.clone(), include)
})
(class_list.clone(), include)
},
)
}
fn rebuild(self, state: &mut Self::State) {
@@ -173,10 +177,10 @@ where
Some((class_list, prev)) => {
if include {
if !prev {
R::add_class(&class_list, name);
Rndr::add_class(&class_list, name);
}
} else if prev {
R::remove_class(&class_list, name);
Rndr::remove_class(&class_list, name);
}
(class_list.clone(), include)
}
@@ -208,14 +212,14 @@ where
// TODO this needs a non-reactive form too to be restored
/*
impl<F, T, R> IntoClass<R> for (Vec<Cow<'static, str>>, F)
impl<F, T> IntoClass for (Vec<Cow<'static, str>>, F)
where
F: ReactiveFunction<Output = T>,
T: Borrow<bool> + Send + 'static,
R: DomRenderer,
{
type AsyncOutput = (Vec<Cow<'static, str>>, bool);
type State = RenderEffect<(R::ClassList, bool)>;
type State = RenderEffect<(crate::renderer::types::ClassList, bool)>;
type Cloneable = (Vec<Cow<'static, str>>, SharedReactiveFunction<T>);
type CloneableOwned = (Vec<Cow<'static, str>>, SharedReactiveFunction<T>);
@@ -228,29 +232,29 @@ where
let include = *f.invoke().borrow();
if include {
for name in names {
<&str as IntoClass<R>>::to_html(&name, class);
<&str as IntoClass>::to_html(&name, class);
}
}
}
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 {
// TODO FROM_SERVER vs template
let (names, mut f) = self;
let class_list = R::class_list(el);
let class_list = Rndr::class_list(el);
RenderEffect::new(move |prev: Option<(R::ClassList, bool)>| {
RenderEffect::new(move |prev: Option<(crate::renderer::types::ClassList, bool)>| {
let include = *f.invoke().borrow();
if let Some((class_list, prev)) = prev {
if include {
if !prev {
for name in &names {
// TODO multi-class optimizations here
R::add_class(&class_list, name);
Rndr::add_class(&class_list, name);
}
}
} else if prev {
for name in &names {
R::remove_class(&class_list, name);
Rndr::remove_class(&class_list, name);
}
}
}
@@ -258,30 +262,30 @@ where
})
}
fn build(self, el: &R::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
let (names, mut f) = self;
let class_list = R::class_list(el);
let class_list = Rndr::class_list(el);
RenderEffect::new(move |prev: Option<(R::ClassList, bool)>| {
RenderEffect::new(move |prev: Option<(crate::renderer::types::ClassList, bool)>| {
let include = *f.invoke().borrow();
match prev {
Some((class_list, prev)) => {
if include {
for name in &names {
if !prev {
R::add_class(&class_list, name);
Rndr::add_class(&class_list, name);
}
}
} else if prev {
for name in &names {
R::remove_class(&class_list, name);
Rndr::remove_class(&class_list, name);
}
}
}
None => {
if include {
for name in &names {
R::add_class(&class_list, name);
Rndr::add_class(&class_list, name);
}
}
}
@@ -295,19 +299,19 @@ where
let prev_value = state.take_value();
*state = RenderEffect::new_with_value(
move |prev: Option<(R::ClassList, bool)>| {
move |prev: Option<(crate::renderer::types::ClassList, bool)>| {
let include = *f.invoke().borrow();
match prev {
Some((class_list, prev)) => {
if include {
for name in &names {
if !prev {
R::add_class(&class_list, name);
Rndr::add_class(&class_list, name);
}
}
} else if prev {
for name in &names {
R::remove_class(&class_list, name);
Rndr::remove_class(&class_list, name);
}
}
(class_list.clone(), include)
@@ -339,13 +343,12 @@ where
}
*/
impl<G, R> IntoClass<R> for ReadGuard<String, G>
impl<G> IntoClass for ReadGuard<String, G>
where
G: Deref<Target = String> + Send,
R: DomRenderer,
{
type AsyncOutput = Self;
type State = <String as IntoClass<R>>::State;
type State = <String as IntoClass>::State;
type Cloneable = Arc<str>;
type CloneableOwned = Arc<str>;
@@ -354,25 +357,25 @@ where
}
fn to_html(self, class: &mut String) {
<&str as IntoClass<R>>::to_html(self.deref().as_str(), class);
<&str as IntoClass>::to_html(self.deref().as_str(), class);
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &<R>::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
<String as IntoClass<R>>::hydrate::<FROM_SERVER>(
<String as IntoClass>::hydrate::<FROM_SERVER>(
self.deref().to_owned(),
el,
)
}
fn build(self, el: &<R>::Element) -> Self::State {
<String as IntoClass<R>>::build(self.deref().to_owned(), el)
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
<String as IntoClass>::build(self.deref().to_owned(), el)
}
fn rebuild(self, state: &mut Self::State) {
<String as IntoClass<R>>::rebuild(self.deref().to_owned(), state)
<String as IntoClass>::rebuild(self.deref().to_owned(), state)
}
fn into_cloneable(self) -> Self::Cloneable {
@@ -390,13 +393,12 @@ where
}
}
impl<G, R> IntoClass<R> for (&'static str, ReadGuard<bool, G>)
impl<G> IntoClass for (&'static str, ReadGuard<bool, G>)
where
G: Deref<Target = bool> + Send,
R: DomRenderer,
{
type AsyncOutput = Self;
type State = <(&'static str, bool) as IntoClass<R>>::State;
type State = <(&'static str, bool) as IntoClass>::State;
type Cloneable = (&'static str, bool);
type CloneableOwned = (&'static str, bool);
@@ -405,7 +407,7 @@ where
}
fn to_html(self, class: &mut String) {
<(&'static str, bool) as IntoClass<R>>::to_html(
<(&'static str, bool) as IntoClass>::to_html(
(self.0, *self.1.deref()),
class,
);
@@ -413,23 +415,23 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
el: &<R>::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
<(&'static str, bool) as IntoClass<R>>::hydrate::<FROM_SERVER>(
<(&'static str, bool) as IntoClass>::hydrate::<FROM_SERVER>(
(self.0, *self.1.deref()),
el,
)
}
fn build(self, el: &<R>::Element) -> Self::State {
<(&'static str, bool) as IntoClass<R>>::build(
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
<(&'static str, bool) as IntoClass>::build(
(self.0, *self.1.deref()),
el,
)
}
fn rebuild(self, state: &mut Self::State) {
<(&'static str, bool) as IntoClass<R>>::rebuild(
<(&'static str, bool) as IntoClass>::rebuild(
(self.0, *self.1.deref()),
state,
)
@@ -454,14 +456,13 @@ where
mod stable {
macro_rules! class_signal_arena {
($sig:ident) => {
impl<C, R, S> IntoClass<R> for $sig<C, S>
impl<C, S> IntoClass for $sig<C, S>
where
$sig<C, S>: Get<Value = C>,
S: Send + Sync + 'static,
S: Storage<C> + Storage<Option<C>>,
C: IntoClass<R> + Send + Sync + Clone + 'static,
C: IntoClass + Send + Sync + Clone + 'static,
C::State: 'static,
R: DomRenderer,
{
type AsyncOutput = Self;
type State = RenderEffect<C::State>;
@@ -479,12 +480,15 @@ mod stable {
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
(move || self.get()).hydrate::<FROM_SERVER>(el)
}
fn build(self, el: &R::Element) -> Self::State {
fn build(
self,
el: &crate::renderer::types::Element,
) -> Self::State {
(move || self.get()).build(el)
}
@@ -507,15 +511,15 @@ mod stable {
}
}
impl<R, S> IntoClass<R> for (&'static str, $sig<bool, S>)
impl<S> IntoClass for (&'static str, $sig<bool, S>)
where
$sig<bool, S>: Get<Value = bool>,
S: Send + 'static,
S: Storage<bool>,
R: DomRenderer,
{
type AsyncOutput = Self;
type State = RenderEffect<(R::ClassList, bool)>;
type State =
RenderEffect<(crate::renderer::types::ClassList, bool)>;
type Cloneable = Self;
type CloneableOwned = Self;
@@ -527,29 +531,29 @@ mod stable {
let (name, f) = self;
let include = f.get();
if include {
<&str as IntoClass<R>>::to_html(name, class);
<&str as IntoClass>::to_html(name, class);
}
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
IntoClass::<R>::hydrate::<FROM_SERVER>(
IntoClass::hydrate::<FROM_SERVER>(
(self.0, move || self.1.get()),
el,
)
}
fn build(self, el: &R::Element) -> Self::State {
IntoClass::<R>::build((self.0, move || self.1.get()), el)
fn build(
self,
el: &crate::renderer::types::Element,
) -> Self::State {
IntoClass::build((self.0, move || self.1.get()), el)
}
fn rebuild(self, state: &mut Self::State) {
IntoClass::<R>::rebuild(
(self.0, move || self.1.get()),
state,
)
IntoClass::rebuild((self.0, move || self.1.get()), state)
}
fn into_cloneable(self) -> Self::Cloneable {
@@ -571,12 +575,11 @@ mod stable {
macro_rules! class_signal {
($sig:ident) => {
impl<C, R> IntoClass<R> for $sig<C>
impl<C> IntoClass for $sig<C>
where
$sig<C>: Get<Value = C>,
C: IntoClass<R> + Send + Sync + Clone + 'static,
C: IntoClass + Send + Sync + Clone + 'static,
C::State: 'static,
R: DomRenderer,
{
type AsyncOutput = Self;
type State = RenderEffect<C::State>;
@@ -594,12 +597,15 @@ mod stable {
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
(move || self.get()).hydrate::<FROM_SERVER>(el)
}
fn build(self, el: &R::Element) -> Self::State {
fn build(
self,
el: &crate::renderer::types::Element,
) -> Self::State {
(move || self.get()).build(el)
}
@@ -622,13 +628,13 @@ mod stable {
}
}
impl<R> IntoClass<R> for (&'static str, $sig<bool>)
impl IntoClass for (&'static str, $sig<bool>)
where
$sig<bool>: Get<Value = bool>,
R: DomRenderer,
{
type AsyncOutput = Self;
type State = RenderEffect<(R::ClassList, bool)>;
type State =
RenderEffect<(crate::renderer::types::ClassList, bool)>;
type Cloneable = Self;
type CloneableOwned = Self;
@@ -640,29 +646,29 @@ mod stable {
let (name, f) = self;
let include = f.get();
if include {
<&str as IntoClass<R>>::to_html(name, class);
<&str as IntoClass>::to_html(name, class);
}
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &R::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
IntoClass::<R>::hydrate::<FROM_SERVER>(
IntoClass::hydrate::<FROM_SERVER>(
(self.0, move || self.1.get()),
el,
)
}
fn build(self, el: &R::Element) -> Self::State {
IntoClass::<R>::build((self.0, move || self.1.get()), el)
fn build(
self,
el: &crate::renderer::types::Element,
) -> Self::State {
IntoClass::build((self.0, move || self.1.get()), el)
}
fn rebuild(self, state: &mut Self::State) {
IntoClass::<R>::rebuild(
(self.0, move || self.1.get()),
state,
)
IntoClass::rebuild((self.0, move || self.1.get()), state)
}
fn into_cloneable(self) -> Self::Cloneable {
@@ -683,7 +689,7 @@ mod stable {
}
use super::RenderEffect;
use crate::{html::class::IntoClass, renderer::DomRenderer};
use crate::html::class::IntoClass;
use reactive_graph::{
computed::{ArcMemo, Memo},
owner::Storage,
@@ -703,14 +709,13 @@ mod stable {
class_signal!(ArcSignal);
}
impl<Fut, Rndr> IntoClass<Rndr> for Suspend<Fut>
impl<Fut> IntoClass for Suspend<Fut>
where
Fut: Clone + Future + Send + 'static,
Fut::Output: IntoClass<Rndr>,
Rndr: DomRenderer + 'static,
Fut::Output: IntoClass,
{
type AsyncOutput = Fut::Output;
type State = Rc<RefCell<Option<<Fut::Output as IntoClass<Rndr>>::State>>>;
type State = Rc<RefCell<Option<<Fut::Output as IntoClass>::State>>>;
type Cloneable = Self;
type CloneableOwned = Self;
@@ -728,11 +733,11 @@ where
fn hydrate<const FROM_SERVER: bool>(
self,
el: &<Rndr>::Element,
el: &crate::renderer::types::Element,
) -> Self::State {
let el = el.to_owned();
let state = Rc::new(RefCell::new(None));
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(&state);
async move {
*state.borrow_mut() =
@@ -743,10 +748,10 @@ where
state
}
fn build(self, el: &<Rndr>::Element) -> Self::State {
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
let el = el.to_owned();
let state = Rc::new(RefCell::new(None));
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(&state);
async move {
*state.borrow_mut() = Some(self.inner.await.build(&el));
@@ -757,7 +762,7 @@ where
}
fn rebuild(self, state: &mut Self::State) {
Executor::spawn_local({
reactive_graph::spawn_local_scoped({
let state = Rc::clone(state);
async move {
let value = self.inner.await;

View File

@@ -1,325 +0,0 @@
/* The implementations here are no longer valid because the read guards are not Send.
* If we switch to using some kind of Send async lock, it would be possible to restore them.
*
*
* //! Implements the [`Render`] and [`RenderHtml`] traits for signal guard types.
use crate::{
html::attribute::Attribute,
hydration::Cursor,
prelude::RenderHtml,
renderer::{CastFrom, Renderer},
view::{
add_attr::AddAnyAttr, strings::StrState, Mountable, Position,
PositionState, Render, ToTemplate,
},
};
use reactive_graph::signal::guards::ReadGuard;
use std::{
fmt::Write,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
num::{
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8,
NonZeroIsize, NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64,
NonZeroU8, NonZeroUsize,
},
ops::Deref,
};
// any changes here should also be made in src/view/primitives.rs
// TODO should also apply to mapped signal read guards
macro_rules! render_primitive {
($($child_type:ty),* $(,)?) => {
$(
paste::paste! {
pub struct [<ReadGuard $child_type:camel State>]<R>(R::Text, $child_type) where R: Renderer;
impl<'a, R: Renderer> Mountable<R> for [<ReadGuard $child_type:camel State>]<R> {
fn unmount(&mut self) {
self.0.unmount()
}
fn mount(
&mut self,
parent: &<R as Renderer>::Element,
marker: Option<&<R as Renderer>::Node>,
) {
R::insert_node(parent, self.0.as_ref(), marker);
}
fn insert_before_this(&self,
child: &mut dyn Mountable<R>,
) -> bool {
child.mount(parent, Some(self.0.as_ref()));
true
}
}
impl<G, R: Renderer> Render<R> for ReadGuard<$child_type, G>
where G: Deref<Target = $child_type>
{
type State = [<ReadGuard $child_type:camel State>]<R>;
fn build(self) -> Self::State {
let node = R::create_text_node(&self.to_string());
[<ReadGuard $child_type:camel State>](node, *self)
}
fn rebuild(self, state: &mut Self::State) {
let [<ReadGuard $child_type:camel State>](node, this) = state;
if &self != this {
R::set_text(node, &self.to_string());
*this = *self;
}
}
}
impl<G, R> AddAnyAttr<R> for ReadGuard<$child_type, G>
where
R: Renderer,
G: Deref<Target = $child_type> + Send
{
type Output<SomeNewAttr: Attribute<R>> = ReadGuard<$child_type, G>;
fn add_any_attr<NewAttr: Attribute<R>>(
self,
_attr: NewAttr,
) -> Self::Output<NewAttr>
where
Self::Output<NewAttr>: RenderHtml<R>,
{
// TODO: there is a strange compiler thing that seems to prevent us returning Self here,
// even though we've already said that Output is always the same as Self
todo!()
}
}
impl<G, R> RenderHtml<R> for ReadGuard<$child_type, G>
where
R: Renderer,
G: Deref<Target = $child_type> + Send
{
type AsyncOutput = Self;
const MIN_LENGTH: usize = 0;
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
// add a comment node to separate from previous sibling, if any
if matches!(position, Position::NextChildAfterText) {
buf.push_str("<!>")
}
_ = write!(buf, "{}", self);
*position = Position::NextChildAfterText;
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<R>,
position: &PositionState,
) -> Self::State {
if position.get() == Position::FirstChild {
cursor.child();
} else {
cursor.sibling();
}
// separating placeholder marker comes before text node
if matches!(position.get(), Position::NextChildAfterText) {
cursor.sibling();
}
let node = cursor.current();
let node = R::Text::cast_from(node)
.expect("couldn't cast text node from node");
if !FROM_SERVER {
R::set_text(&node, &self.to_string());
}
position.set(Position::NextChildAfterText);
[<ReadGuard $child_type:camel State>](node, *self)
}
async fn resolve(self) -> Self::AsyncOutput {
self
}
}
impl<'a, G> ToTemplate for ReadGuard<$child_type, G>
{
const TEMPLATE: &'static str = " <!>";
fn to_template(
buf: &mut String,
_class: &mut String,
_style: &mut String,
_inner_html: &mut String,
position: &mut Position,
) {
if matches!(*position, Position::NextChildAfterText) {
buf.push_str("<!>")
}
buf.push(' ');
*position = Position::NextChildAfterText;
}
}
}
)*
};
}
render_primitive![
usize,
u8,
u16,
u32,
u64,
u128,
isize,
i8,
i16,
i32,
i64,
i128,
f32,
f64,
char,
bool,
IpAddr,
SocketAddr,
SocketAddrV4,
SocketAddrV6,
Ipv4Addr,
Ipv6Addr,
NonZeroI8,
NonZeroU8,
NonZeroI16,
NonZeroU16,
NonZeroI32,
NonZeroU32,
NonZeroI64,
NonZeroU64,
NonZeroI128,
NonZeroU128,
NonZeroIsize,
NonZeroUsize,
];
// strings
pub struct ReadGuardStringState<R: Renderer> {
node: R::Text,
str: String,
}
impl<G, R: Renderer> Render<R> for ReadGuard<String, G>
where
G: Deref<Target = String>,
{
type State = ReadGuardStringState<R>;
fn build(self) -> Self::State {
let node = R::create_text_node(&self);
ReadGuardStringState {
node,
str: self.to_string(),
}
}
fn rebuild(self, state: &mut Self::State) {
let ReadGuardStringState { node, str } = state;
if *self != *str {
R::set_text(node, &self);
str.clear();
str.push_str(&self);
}
}
}
impl<G, R> AddAnyAttr<R> for ReadGuard<String, G>
where
G: Deref<Target = String> + Send,
R: Renderer,
{
type Output<SomeNewAttr: Attribute<R>> = ReadGuard<String, G>;
fn add_any_attr<NewAttr: Attribute<R>>(
self,
attr: NewAttr,
) -> Self::Output<NewAttr>
where
Self::Output<NewAttr>: RenderHtml<R>,
{
// TODO: there is a strange compiler thing that seems to prevent us returning Self here,
// even though we've already said that Output is always the same as Self
todo!()
}
}
impl<G, R> RenderHtml<R> for ReadGuard<String, G>
where
R: Renderer,
G: Deref<Target = String> + Send,
{
type AsyncOutput = Self;
const MIN_LENGTH: usize = 0;
fn to_html_with_buf(self, buf: &mut String, position: &mut Position, escape: bool, mark_branches: bool) {
<&str as RenderHtml<R>>::to_html_with_buf(&self, buf, position)
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<R>,
position: &PositionState,
) -> Self::State {
let this: &str = self.as_ref();
let StrState { node, .. } =
this.hydrate::<FROM_SERVER>(cursor, position);
ReadGuardStringState {
node,
str: self.to_string(),
}
}
async fn resolve(self) -> Self::AsyncOutput {
self
}
}
impl<G> ToTemplate for ReadGuard<String, G> {
const TEMPLATE: &'static str = <&str as ToTemplate>::TEMPLATE;
fn to_template(
buf: &mut String,
class: &mut String,
style: &mut String,
inner_html: &mut String,
position: &mut Position,
) {
<&str as ToTemplate>::to_template(
buf, class, style, inner_html, position,
)
}
}
impl<R: Renderer> Mountable<R> for ReadGuardStringState<R> {
fn unmount(&mut self) {
self.node.unmount()
}
fn mount(
&mut self,
parent: &<R as Renderer>::Element,
marker: Option<&<R as Renderer>::Node>,
) {
R::insert_node(parent, self.node.as_ref(), marker);
}
fn insert_before_this(&self,
child: &mut dyn Mountable<R>,
) -> bool {
child.mount(parent, Some(self.node.as_ref()));
true
}
}*/

Some files were not shown because too many files have changed in this diff Show More