Compare commits

..

1 Commits

Author SHA1 Message Date
Greg Johnston
fdf6ebaeaf fix: typed route params with #[derive(Params)] 2023-02-07 13:13:51 -05:00
16 changed files with 87 additions and 145 deletions

View File

@@ -66,8 +66,9 @@ pub fn Counters(cx: Scope) -> impl IntoView {
<For
each=counters
key=|counter| counter.0
view=move |cx, (id, (value, set_value)): (usize, (ReadSignal<i32>, WriteSignal<i32>))| {
view! { cx,
view=move |(id, (value, set_value)): (usize, (ReadSignal<i32>, WriteSignal<i32>))| {
view! {
cx,
<Counter id value set_value/>
}
}

View File

@@ -72,7 +72,7 @@ pub fn Counters(cx: Scope) -> impl IntoView {
<For
each={move || counters.get()}
key={|counter| counter.0}
view=move |cx, (id, (value, set_value))| {
view=move |(id, (value, set_value))| {
view! {
cx,
<Counter id value set_value/>

View File

@@ -49,7 +49,7 @@ pub fn ErrorTemplate(
// a unique key for each item as a reference
key=|(index, _error)| *index
// renders each item to a view
view=move |cx, error| {
view= move |error| {
let error_string = error.1.to_string();
let error_code= error.1.status_code();
view! { cx,

View File

@@ -91,7 +91,7 @@ pub fn Stories(cx: Scope) -> impl IntoView {
<For
each=move || stories.clone()
key=|story| story.id
view=move |cx, story: api::Story| {
view=move |story: api::Story| {
view! { cx,
<Story story/>
}

View File

@@ -53,7 +53,7 @@ pub fn Story(cx: Scope) -> impl IntoView {
<For
each=move || story.comments.clone().unwrap_or_default()
key=|comment| comment.id
view=move |cx, comment| view! { cx, <Comment comment /> }
view=move |comment| view! { cx, <Comment comment /> }
/>
</ul>
</div>
@@ -98,7 +98,7 @@ pub fn Comment(cx: Scope, comment: api::Comment) -> impl IntoView {
<For
each=move || comments.clone()
key=|comment| comment.id
view=move |cx, comment: api::Comment| view! { cx, <Comment comment /> }
view=move |comment: api::Comment| view! { cx, <Comment comment /> }
/>
</ul>
}

View File

@@ -15,7 +15,7 @@ pub fn error_template(cx: Scope, errors: Option<RwSignal<Errors>>) -> View {
// a unique key for each item as a reference
key=|error| error.0.clone()
// renders each item to a view
view= move |cx, error| {
view= move |error| {
let error_string = error.1.to_string();
view! {
cx,

View File

@@ -91,7 +91,7 @@ pub fn Stories(cx: Scope) -> impl IntoView {
<For
each=move || stories.clone()
key=|story| story.id
view=move |cx, story: api::Story| {
view=move |story: api::Story| {
view! { cx,
<Story story/>
}

View File

@@ -53,7 +53,7 @@ pub fn Story(cx: Scope) -> impl IntoView {
<For
each=move || story.comments.clone().unwrap_or_default()
key=|comment| comment.id
view=move |cx, comment| view! { cx, <Comment comment /> }
view=move |comment| view! { cx, <Comment comment /> }
/>
</ul>
</div>
@@ -98,7 +98,7 @@ pub fn Comment(cx: Scope, comment: api::Comment) -> impl IntoView {
<For
each=move || comments.clone()
key=|comment| comment.id
view=move |cx, comment: api::Comment| view! { cx, <Comment comment /> }
view=move |comment: api::Comment| view! { cx, <Comment comment /> }
/>
</ul>
}

View File

@@ -51,7 +51,7 @@ pub fn ErrorTemplate(
// a unique key for each item as a reference
key=|(index, _error)| *index
// renders each item to a view
view= move |cx, error| {
view= move |error| {
let error_string = error.1.to_string();
let error_code= error.1.status_code();
view! {

View File

@@ -216,7 +216,7 @@ pub fn TodoMVC(cx: Scope) -> impl IntoView {
<For
each=filtered_todos
key=|todo| todo.id
view=move |cx, todo: Todo| view! { cx, <Todo todo /> }
view=move |todo: Todo| view! { cx, <Todo todo /> }
/>
</ul>
</section>
@@ -262,7 +262,7 @@ pub fn Todo(cx: Scope, todo: Todo) -> impl IntoView {
let set_todos = use_context::<WriteSignal<Todos>>(cx).unwrap();
// this will be filled by _ref=input below
let todo_input = NodeRef::<Input>::new(cx);
let todo_input = NodeRef::<HtmlElement<Input>>::new(cx);
let save = move |value: &str| {
let value = value.trim();

View File

@@ -30,7 +30,7 @@ use std::hash::Hash;
/// // a unique key for each item
/// key=|counter| counter.id
/// // renders each item to a view
/// view=move |cx, counter: Counter| {
/// view=move |counter: Counter| {
/// view! {
/// cx,
/// <button>"Value: " {move || counter.count.get()}</button>
@@ -54,7 +54,7 @@ pub fn For<IF, I, T, EF, N, KF, K>(
where
IF: Fn() -> I + 'static,
I: IntoIterator<Item = T>,
EF: Fn(Scope, T) -> N + 'static,
EF: Fn(T) -> N + 'static,
N: IntoView,
KF: Fn(&T) -> K + 'static,
K: Eq + Hash + 'static,

View File

@@ -32,7 +32,7 @@ fn view_fn(cx: Scope) -> impl IntoView {
<For
each=|| vec![0, 1, 2, 3, 4, 5, 6, 7]
key=|i| *i
view=|cx, i| view! { cx, {i} }
view=|i| view! { cx, {i} }
/>
}
.into_view(cx);

View File

@@ -38,7 +38,6 @@ cfg_if! {
use crate::hydration::HydrationKey;
}
}
use leptos_reactive::Scope;
use smallvec::SmallVec;
use std::{borrow::Cow, cell::RefCell, fmt, hash::Hash, ops::Deref, rc::Rc};
@@ -160,7 +159,6 @@ impl Mountable for EachRepr {
/// The internal representation of an [`Each`] item.
#[derive(PartialEq, Eq)]
pub(crate) struct EachItem {
cx: Scope,
#[cfg(all(target_arch = "wasm32", feature = "web"))]
document_fragment: web_sys::DocumentFragment,
#[cfg(debug_assertions)]
@@ -186,7 +184,7 @@ impl fmt::Debug for EachItem {
}
impl EachItem {
fn new(cx: Scope, child: View) -> Self {
fn new(child: View) -> Self {
let id = HydrationCtx::id();
let markers = (
@@ -216,7 +214,6 @@ impl EachItem {
};
Self {
cx,
#[cfg(all(target_arch = "wasm32", feature = "web"))]
document_fragment,
#[cfg(debug_assertions)]
@@ -229,13 +226,6 @@ impl EachItem {
}
}
#[cfg(all(target_arch = "wasm32", feature = "web"))]
impl Drop for EachItem {
fn drop(&mut self) {
self.cx.dispose();
}
}
#[cfg(all(target_arch = "wasm32", feature = "web"))]
impl Mountable for EachItem {
fn get_mountable_node(&self) -> web_sys::Node {
@@ -282,7 +272,7 @@ pub struct Each<IF, I, T, EF, N, KF, K>
where
IF: Fn() -> I + 'static,
I: IntoIterator<Item = T>,
EF: Fn(Scope, T) -> N + 'static,
EF: Fn(T) -> N + 'static,
N: IntoView,
KF: Fn(&T) -> K + 'static,
K: Eq + Hash + 'static,
@@ -297,7 +287,7 @@ impl<IF, I, T, EF, N, KF, K> Each<IF, I, T, EF, N, KF, K>
where
IF: Fn() -> I + 'static,
I: IntoIterator<Item = T>,
EF: Fn(Scope, T) -> N + 'static,
EF: Fn(T) -> N + 'static,
N: IntoView,
KF: Fn(&T) -> K,
K: Eq + Hash + 'static,
@@ -317,7 +307,7 @@ impl<IF, I, T, EF, N, KF, K> IntoView for Each<IF, I, T, EF, N, KF, K>
where
IF: Fn() -> I + 'static,
I: IntoIterator<Item = T>,
EF: Fn(Scope, T) -> N + 'static,
EF: Fn(T) -> N + 'static,
N: IntoView,
KF: Fn(&T) -> K + 'static,
K: Eq + Hash + 'static,
@@ -327,7 +317,7 @@ where
debug_assertions,
instrument(level = "trace", name = "<Each />", skip_all)
)]
fn into_view(self, cx: Scope) -> crate::View {
fn into_view(self, cx: leptos_reactive::Scope) -> crate::View {
let Self {
items_fn,
each_fn,
@@ -380,7 +370,7 @@ where
*children_borrow = Vec::with_capacity(items.len());
for item in items {
let (each_item, _) = cx.run_child_scope(|cx| EachItem::new(cx, each_fn(cx, item).into_view(cx)));
let each_item = EachItem::new(each_fn(item).into_view(cx));
#[cfg(all(target_arch = "wasm32", feature = "web"))]
mount_child(MountKind::Before(&closing), &each_item);
@@ -394,7 +384,7 @@ where
} else {
*component.children.borrow_mut() = (items_fn)()
.into_iter()
.map(|child| cx.run_child_scope(|cx| Some(EachItem::new(cx, (each_fn)(cx, child).into_view(cx)))).0)
.map(|child| Some(EachItem::new((each_fn)(child).into_view(cx))))
.collect();
}
}
@@ -578,7 +568,7 @@ impl Default for DiffOpAddMode {
#[cfg(all(target_arch = "wasm32", feature = "web"))]
fn apply_cmds<T, EF, N>(
cx: Scope,
cx: leptos_reactive::Scope,
opening: &web_sys::Node,
closing: &web_sys::Node,
mut cmds: Diff,
@@ -586,7 +576,7 @@ fn apply_cmds<T, EF, N>(
mut items: SmallVec<[Option<T>; 128]>,
each_fn: &EF,
) where
EF: Fn(Scope, T) -> N,
EF: Fn(T) -> N,
N: IntoView,
{
let range = RANGE.with(|range| (*range).clone());
@@ -657,10 +647,9 @@ fn apply_cmds<T, EF, N>(
for DiffOpAdd { at, mode } in cmds.added {
let item = items[at].take().unwrap();
let (each_item, _) = cx.run_child_scope(|cx| {
let child = each_fn(cx, item).into_view(cx);
EachItem::new(cx, child)
});
let child = each_fn(item).into_view(cx);
let each_item = EachItem::new(child);
match mode {
DiffOpAddMode::Normal => {

View File

@@ -400,7 +400,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
}
/// Binds the element reference to [`NodeRef`].
pub fn node_ref(self, node_ref: NodeRef<El>) -> Self
pub fn node_ref(self, node_ref: &NodeRef<Self>) -> Self
where
Self: Clone,
{

View File

@@ -1,8 +1,4 @@
use std::cell::Cell;
use leptos_reactive::{create_effect, create_rw_signal, RwSignal, Scope};
use crate::{ElementDescriptor, HtmlElement};
use leptos_reactive::{create_rw_signal, RwSignal, Scope};
/// Contains a shared reference to a DOM node creating while using the `view`
/// macro to create your UI.
@@ -11,7 +7,7 @@ use crate::{ElementDescriptor, HtmlElement};
/// # use leptos::*;
/// #[component]
/// pub fn MyComponent(cx: Scope) -> impl IntoView {
/// let input_ref = NodeRef::<Input>::new(cx);
/// let input_ref = NodeRef::<HtmlElement<Input>>::new(cx);
///
/// let on_click = move |_| {
/// let node = input_ref
@@ -33,11 +29,10 @@ use crate::{ElementDescriptor, HtmlElement};
/// }
/// }
/// ```
pub struct NodeRef<T: ElementDescriptor + 'static>(
RwSignal<Option<HtmlElement<T>>>,
);
#[derive(Clone, PartialEq)]
pub struct NodeRef<T: Clone + 'static>(RwSignal<Option<T>>);
impl<T: ElementDescriptor + 'static> NodeRef<T> {
impl<T: Clone + 'static> NodeRef<T> {
/// Creates an empty reference.
pub fn new(cx: Scope) -> Self {
Self(create_rw_signal(cx, None))
@@ -49,10 +44,7 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
/// Initially, the value will be `None`, but once it is loaded the effect
/// will rerun and its value will be `Some(Element)`.
#[track_caller]
pub fn get(&self) -> Option<HtmlElement<T>>
where
T: Clone,
{
pub fn get(&self) -> Option<T> {
self.0.get()
}
@@ -61,10 +53,7 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
/// so that effects that use the node reference will rerun once it is loaded,
/// i.e., effects can be forward-declared.
#[track_caller]
pub fn load(&self, node: &HtmlElement<T>)
where
T: Clone,
{
pub fn load(&self, node: &T) {
self.0.update(|current| {
if current.is_some() {
crate::debug_warn!(
@@ -76,49 +65,27 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
*current = Some(node.clone());
});
}
/// Runs the provided closure when the `NodeRef` has been connected
/// with it's [`HtmlElement`].
pub fn on_load<F>(self, cx: Scope, f: F)
where
T: Clone,
F: FnOnce(HtmlElement<T>) + 'static,
{
let f = Cell::new(Some(f));
create_effect(cx, move |_| {
if let Some(node_ref) = self.get() {
f.take().unwrap()(node_ref);
}
});
}
}
impl<T: ElementDescriptor> Clone for NodeRef<T> {
fn clone(&self) -> Self {
Self(self.0)
}
}
impl<T: ElementDescriptor + 'static> Copy for NodeRef<T> {}
impl<T: Clone + 'static> Copy for NodeRef<T> {}
cfg_if::cfg_if! {
if #[cfg(not(feature = "stable"))] {
impl<T: Clone + ElementDescriptor + 'static> FnOnce<()> for NodeRef<T> {
type Output = Option<HtmlElement<T>>;
impl<T: Clone + 'static> FnOnce<()> for NodeRef<T> {
type Output = Option<T>;
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
self.get()
}
}
impl<T: Clone + ElementDescriptor + 'static> FnMut<()> for NodeRef<T> {
impl<T: Clone + 'static> FnMut<()> for NodeRef<T> {
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
}
}
impl<T: Clone + ElementDescriptor + Clone + 'static> Fn<()> for NodeRef<T> {
impl<T: Clone + 'static> Fn<()> for NodeRef<T> {
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()
}

View File

@@ -1,8 +1,9 @@
use crate::{is_component_node, Mode};
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned};
use syn::{spanned::Spanned, Expr, ExprLit, ExprPath, Lit};
use syn_rsx::{Node, NodeAttribute, NodeElement, NodeName, NodeValueExpr};
use syn_rsx::{Node, NodeAttribute, NodeElement, NodeName};
use crate::{is_component_node, Mode};
#[derive(Clone, Copy)]
enum TagType {
@@ -306,11 +307,9 @@ fn element_to_tokens_ssr(
template.push('<');
template.push_str(&tag_name);
let mut inner_html = None;
for attr in &node.attributes {
if let Node::Attribute(attr) = attr {
inner_html = attribute_to_tokens_ssr(cx, attr, template, holes, exprs_for_compiler);
attribute_to_tokens_ssr(cx, attr, template, holes, exprs_for_compiler);
}
}
@@ -340,52 +339,42 @@ fn element_to_tokens_ssr(
template.push_str("/>");
} else {
template.push('>');
for child in &node.children {
match child {
Node::Element(child) => element_to_tokens_ssr(
cx,
child,
template,
holes,
exprs_for_compiler,
false,
global_class,
),
Node::Text(text) => {
if let Some(value) = value_to_string(&text.value) {
template.push_str(&html_escape::encode_safe(&value));
} else {
template.push_str("{}");
let value = text.value.as_ref();
if let Some(inner_html) = inner_html {
template.push_str("{}");
let value = inner_html.as_ref();
holes.push(quote! {
(#value).into_attribute(cx).as_nameless_value_string().unwrap_or_default(),
})
} else {
for child in &node.children {
match child {
Node::Element(child) => element_to_tokens_ssr(
cx,
child,
template,
holes,
exprs_for_compiler,
false,
global_class,
),
Node::Text(text) => {
if let Some(value) = value_to_string(&text.value) {
template.push_str(&html_escape::encode_safe(&value));
} else {
template.push_str("{}");
let value = text.value.as_ref();
holes.push(quote! {
#value.into_view(#cx).render_to_string(#cx),
})
}
holes.push(quote! {
#value.into_view(#cx).render_to_string(#cx),
})
}
Node::Block(block) => {
if let Some(value) = value_to_string(&block.value) {
template.push_str(&value);
} else {
template.push_str("{}");
let value = block.value.as_ref();
holes.push(quote! {
#value.into_view(#cx).render_to_string(#cx),
})
}
}
Node::Fragment(_) => todo!(),
_ => {}
}
Node::Block(block) => {
if let Some(value) = value_to_string(&block.value) {
template.push_str(&value);
} else {
template.push_str("{}");
let value = block.value.as_ref();
holes.push(quote! {
#value.into_view(#cx).render_to_string(#cx),
})
}
}
Node::Fragment(_) => todo!(),
_ => {}
}
}
@@ -409,16 +398,15 @@ fn value_to_string(value: &syn_rsx::NodeValueExpr) -> Option<String> {
}
}
// returns `inner_html`
fn attribute_to_tokens_ssr<'a>(
fn attribute_to_tokens_ssr(
cx: &Ident,
node: &'a NodeAttribute,
node: &NodeAttribute,
template: &mut String,
holes: &mut Vec<TokenStream>,
exprs_for_compiler: &mut Vec<TokenStream>,
) -> Option<&'a NodeValueExpr> {
) {
let name = node.key.to_string();
if name == "ref" || name == "_ref" || name == "ref_" || name == "node_ref" {
if name == "ref" || name == "_ref" || name == "node_ref" {
// ignore refs on SSR
} else if name.strip_prefix("on:").is_some() {
let (event_type, handler) = event_from_attribute_node(node, false);
@@ -428,8 +416,6 @@ fn attribute_to_tokens_ssr<'a>(
} else if name.strip_prefix("prop:").is_some() || name.strip_prefix("class:").is_some() {
// ignore props for SSR
// ignore classes: we'll handle these separately
} else if name == "inner_html" {
return node.value.as_ref();
} else {
let name = name.replacen("attr:", "", 1);
@@ -454,8 +440,7 @@ fn attribute_to_tokens_ssr<'a>(
}
}
}
};
None
}
}
fn set_class_attribute_ssr(
@@ -752,7 +737,7 @@ fn element_to_tokens(
fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
let span = node.key.span();
let name = node.key.to_string();
if name == "ref" || name == "_ref" || name == "ref_" || name == "node_ref" {
if name == "ref" || name == "_ref" || name == "node_ref" {
let value = node
.value
.as_ref()
@@ -761,7 +746,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
let node_ref = quote_spanned! { span => node_ref };
quote! {
.#node_ref(#value)
.#node_ref(&#value)
}
} else if let Some(name) = name.strip_prefix("on:") {
let handler = node