Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2

This commit is contained in:
Jose Quesada 2022-12-04 21:30:55 -06:00
commit d2b4ae30d1
10 changed files with 225 additions and 41 deletions

View file

@ -20,6 +20,7 @@ default = ["csr", "serde"]
csr = [
"leptos_core/csr",
"leptos_dom/csr",
"leptos_dom/web",
"leptos_macro/csr",
"leptos_reactive/csr",
"leptos_server/csr",
@ -27,6 +28,7 @@ csr = [
hydrate = [
"leptos_core/hydrate",
"leptos_dom/hydrate",
"leptos_dom/web",
"leptos_macro/hydrate",
"leptos_reactive/hydrate",
"leptos_server/hydrate",

View file

@ -12,6 +12,7 @@ leptos_dom = { path = "../leptos_dom", default-features = false, version = "0.0.
leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0.18" }
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.18" }
log = "0.4"
typed-builder = "0.11"
[dev-dependencies]
leptos = { path = "../leptos", default-features = false, version = "0.0" }

View file

@ -3,20 +3,12 @@
//! This crate contains several utility pieces that depend on multiple crates.
//! They are all re-exported in the main `leptos` crate.
mod for_component;
mod map;
mod suspense;
//mod for_component;
//mod map;
//mod suspense;
pub use for_component::*;
pub use map::*;
pub use suspense::*;
//pub use for_component::*;
//pub use map::*;
//pub use suspense::*;
/// Describes the properties of a component. This is typically generated by the `Prop` derive macro
/// as part of the `#[component]` macro.
pub trait Prop {
/// Builder type, automatically generated.
type Builder;
/// The builder should be automatically generated using the `Prop` derive macro.
fn builder() -> Self::Builder;
}
pub use typed_builder::TypedBuilder;

View file

@ -44,8 +44,11 @@ pub struct ComponentRepr {
impl Drop for ComponentRepr {
fn drop(&mut self) {
// TODO: all ComponentReprs are immediately dropped,
// which means their scopes are immediately disposed,
// which means components have no reactivity
if let Some(disposer) = self.disposer.take() {
disposer.dispose();
//disposer.dispose();
}
}
}
@ -130,7 +133,7 @@ impl ComponentRepr {
/// A user-defined `leptos` component.
pub struct Component<F>
where
F: FnOnce(Scope) -> Vec<Node>,
F: FnOnce(Scope) -> Node,
{
name: Cow<'static, str>,
children_fn: F,
@ -138,7 +141,7 @@ where
impl<F> Component<F>
where
F: FnOnce(Scope) -> Vec<Node>,
F: FnOnce(Scope) -> Node,
{
/// Creates a new component.
pub fn new(name: impl Into<Cow<'static, str>>, f: F) -> Self {
@ -151,21 +154,21 @@ where
impl<F> IntoNode for Component<F>
where
F: FnOnce(Scope) -> Vec<Node>,
F: FnOnce(Scope) -> Node,
{
fn into_node(self, cx: Scope) -> Node {
let Self { name, children_fn } = self;
let mut children = None;
let disposer = cx.child_scope(|cx| children = Some(children_fn(cx)));
let disposer = cx.child_scope(|cx| children = Some(cx.untrack(move || children_fn(cx))));
let children = children.unwrap();
let mut repr = ComponentRepr::new(name);
repr.disposer = Some(disposer);
repr.children = children;
repr.children = vec![children];
repr.into_node(cx)
}

View file

@ -5,6 +5,7 @@ use itertools::{EitherOrBoth, Itertools};
use leptos_reactive::{create_effect, Scope};
use rustc_hash::FxHasher;
use smallvec::{smallvec, SmallVec};
use typed_builder::TypedBuilder;
use std::{
borrow::Cow,
cell::RefCell,
@ -388,7 +389,7 @@ fn diff<K: Eq + Hash>(from: &FxIndexSet<K>, to: &FxIndexSet<K>) -> Diff {
}
}
normalized_idx += 1;
normalized_idx = normalized_idx.wrapping_add(1);
}
let mut diffs = Diff {
@ -598,3 +599,78 @@ fn apply_cmds<T, EF, N>(
// items
children.drain_filter(|c| c.is_none());
}
/// Properties for the [For](crate::For) component, a keyed list.
#[derive(TypedBuilder)]
pub struct ForProps<IF, I, T, EF, N, KF, K>
where
IF: Fn() -> I + 'static,
I: IntoIterator<Item = T>,
EF: Fn(T) -> N + 'static,
N: IntoNode,
KF: Fn(&T) -> K + 'static,
K: Eq + Hash + 'static,
T: 'static,
{
/// Items over which the component should iterate.
pub each: IF,
/// A key function that will be applied to each item
pub key: KF,
/// Should provide a single child function, which takes
pub children: Box<dyn Fn() -> Vec<EF>>,
}
/// Iterates over children and displays them, keyed by the `key` function given.
///
/// This is much more efficient than naively iterating over nodes with `.iter().map(|n| view! { cx, ... })...`,
/// as it avoids re-creating DOM nodes that are not being changed.
///
/// ```
/// # use leptos::*;
///
/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
/// struct Counter {
/// id: usize,
/// count: RwSignal<i32>
/// }
///
/// fn Counters(cx: Scope) -> Element {
/// let (counters, set_counters) = create_signal::<Vec<Counter>>(cx, vec![]);
///
/// view! {
/// cx,
/// <div>
/// <For
/// // a function that returns the items we're iterating over; a signal is fine
/// each=counters
/// // a unique key for each item
/// key=|counter| counter.id
/// >
/// {|cx: Scope, counter: &Counter| {
/// let count = counter.count;
/// view! {
/// cx,
/// <button>"Value: " {move || count.get()}</button>
/// }
/// }
/// }
/// </For>
/// </div>
/// }
/// }
/// ```
#[allow(non_snake_case)]
pub fn For<IF, I, T, EF, N, KF, K>(cx: Scope, props: ForProps<IF, I, T, EF, N, KF, K>) -> Node
where
IF: Fn() -> I + 'static,
I: IntoIterator<Item = T>,
EF: Fn(T) -> N + 'static,
N: IntoNode,
KF: Fn(&T) -> K + 'static,
K: Eq + Hash + 'static,
T: 'static,
{
let each_fn = (props.children)().swap_remove(0);
EachKey::new(props.each, props.key, each_fn).into_node(cx)
}

View file

@ -76,7 +76,7 @@ macro_rules! generate_event_types {
pub struct $event;
impl EventDescriptor for $event {
type EventType = web_sys::MouseEvent;
type EventType = web_sys::$web_sys_event;
fn name(&self) -> Cow<'static, str> {
concat!("on", stringify!([<$event:lower>])).into()

View file

@ -409,7 +409,7 @@ impl<El: IntoElement> HtmlElement<El> {
}
} else {
_ = event_name;
_ = event;
_ = event_handler;
}
}

View file

@ -1,7 +1,7 @@
use std::{cell::{OnceCell, RefCell}, hash::Hash, rc::Rc};
use cfg_if::cfg_if;
use leptos_reactive::{Scope, create_effect};
use crate::{IntoNode, ComponentRepr, EachKey, Node, HtmlElement, Text, Element, Fragment, Unit, text, DynChild, IntoElement};
use crate::{IntoNode, ComponentRepr, EachKey, Node, HtmlElement, Text, Element, Fragment, Unit, text, DynChild, IntoElement, Component};
pub enum Child {
/// A (presumably reactive) function, which will be run inside an effect to do targeted updates to the node.
@ -43,7 +43,7 @@ impl IntoChild for Node {
impl IntoChild for String {
fn into_child(self, _cx: Scope) -> Child {
Child::Text(self.into())
Child::Text(self)
}
}
@ -102,12 +102,22 @@ node_type!(Vec<Node>);
node_type!(Fragment);
node_type!(ComponentRepr);
impl<El: IntoElement> IntoChild for HtmlElement<El> {
fn into_child(self, cx: Scope) -> Child {
Child::Node(self.into_node(cx))
}
}
impl<F> IntoChild for Component<F>
where
F: FnOnce(Scope) -> Node,
{
fn into_child(self, cx: Scope) -> Child {
Child::Node(self.into_node(cx))
}
}
macro_rules! text_type {
($child_type:ty) => {
impl IntoChild for $child_type {

View file

@ -121,11 +121,7 @@ impl ToTokens for InlinePropsBody {
None
};
//let modifiers = if first_lifetime.is_some() {
let modifiers = quote! { #[derive(Props)] };
/* } else {
quote! { #[derive(Props, PartialEq, Eq)] }
}; */
let modifiers = quote! { #[derive(leptos::TypedBuilder)] };
let (_scope_lifetime, fn_generics, struct_generics) = if let Some(lt) = first_lifetime {
let struct_generics: Punctuated<_, token::Comma> = generics

View file

@ -136,7 +136,7 @@ fn node_to_tokens(cx: &Ident, node: &Node, mode: Mode) -> TokenStream {
let span = node.value.span();
let value = node.value.as_ref();
quote_spanned! {
span => leptos::text(#value)
span => text(#value)
}
},
Node::Block(node) => {
@ -154,7 +154,7 @@ fn node_to_tokens(cx: &Ident, node: &Node, mode: Mode) -> TokenStream {
fn element_to_tokens(cx: &Ident, node: &NodeElement, mode: Mode) -> TokenStream {
let span = node.name.span();
if is_component_node(node) {
todo!("component node")
component_to_tokens(cx, node, mode)
} else {
let name = if is_custom_element(&node.name) {
let name = node.name.to_string();
@ -171,7 +171,27 @@ fn element_to_tokens(cx: &Ident, node: &NodeElement, mode: Mode) -> TokenStream
}
});
let children = node.children.iter().map(|node| {
let child = node_to_tokens(cx, node, mode);
let child = match node {
Node::Fragment(fragment) => {
fragment_to_tokens(cx, Span::call_site(), &fragment.children, mode)
},
Node::Text(node) => {
let span = node.value.span();
let value = node.value.as_ref();
quote_spanned! {
span => #[allow(unused_braces)] #value
}
},
Node::Block(node) => {
let span = node.value.span();
let value = node.value.as_ref();
quote_spanned! {
span => #[allow(unused_braces)] #value
}
},
Node::Element(node) => element_to_tokens(cx, node, mode),
Node::Comment(_) | Node::Doctype(_) | Node::Attribute(_) => quote! { },
};
quote! {
._child(cx, #child)
}
@ -192,7 +212,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute, mode: Mode) -> TokenStr
if mode != Mode::Ssr {
let value = node.value.as_ref().and_then(|expr| expr_to_ident(expr)).expect("'_ref' needs to be passed a variable name");
quote_spanned! {
span => ._ref(#value)
span => ._ref(#[allow(unused_braces)] #value)
}
} else {
todo!()
@ -209,11 +229,11 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute, mode: Mode) -> TokenStr
if NON_BUBBLING_EVENTS.contains(&name) {
quote_spanned! {
span => .on::<leptos::web_sys::#event_type>(#name, #handler)
span => .on::<leptos::web_sys::#event_type>(#name, #[allow(unused_braces)] #handler)
}
} else {
quote_spanned! {
span => .on_delegated::<leptos::web_sys::#event_type>(#name, #handler)
span => .on_delegated::<leptos::web_sys::#event_type>(#name, #[allow(unused_braces)] #handler)
}
}
} else {
@ -223,7 +243,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute, mode: Mode) -> TokenStr
let value = node.value.as_ref().expect("prop: attributes need a value").as_ref();
if mode != Mode::Ssr {
quote_spanned! {
span => ._prop(#cx, #name, #value)
span => ._prop(#cx, #name, #[allow(unused_braces)] #value)
}
} else {
todo!()
@ -232,14 +252,13 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute, mode: Mode) -> TokenStr
let value = node.value.as_ref().expect("class: attributes need a value").as_ref();
if mode != Mode::Ssr {
quote_spanned! {
span => ._class(#cx, #name, #value)
span => ._class(#cx, #name, #[allow(unused_braces)] #value)
}
} else {
todo!()
}
} else {
let name = name.replacen("attr:", "", 1);
let node_name = node.key;
let value = match node.value.as_ref() {
Some(value) => {
let value = value.as_ref();
@ -250,7 +269,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute, mode: Mode) -> TokenStr
};
if mode != Mode::Ssr {
quote_spanned! {
span => ._attr(#cx, #name, #value)
span => ._attr(#cx, #name, #[allow(unused_braces)] #value)
}
} else {
quote! { }
@ -258,6 +277,91 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute, mode: Mode) -> TokenStr
}
}
fn component_to_tokens(cx: &Ident, node: &NodeElement, mode: Mode) -> TokenStream {
let name = &node.name;
let component_name = ident_from_tag_name(&node.name);
let component_name_str = name.to_string();
let span = node.name.span();
let component_props_name = Ident::new(&format!("{component_name}Props"), span);
let children = if node.children.is_empty() {
quote! {}
} else if node.children.len() == 1 {
let child = component_child(cx, &node.children[0], mode);
quote_spanned! { span => .children(Box::new(move || vec![#child])) }
} else {
let children = node.children.iter()
.map(|node| component_child(cx, node, mode));
quote_spanned! { span => .children(Box::new(move || vec![#(#children),*])) }
};
let props = node.attributes.iter()
.filter_map(|node| if let Node::Attribute(node) = node { Some(node) } else { None })
.map(|attr| {
let name = &attr.key;
let span = attr.key.span();
let value = attr
.value
.as_ref()
.map(|v| {
let v = v.as_ref();
quote_spanned! { span => #v }
})
.unwrap_or_else(|| quote_spanned! { span => #name });
quote_spanned! {
span => .#name(#[allow(unused_braces)] #value)
}
});
let component_itself = quote_spanned! {
span => #name(
cx,
#component_props_name::builder()
#(#props)*
#children
.build(),
)
};
quote_spanned! {
span => leptos::Component::new(
#component_name_str,
move |cx| #component_itself
)
}
}
fn component_child(cx: &Ident, node: &Node, mode: Mode) -> TokenStream {
match node {
Node::Block(node) => {
let span = node.value.span();
let value = node.value.as_ref();
quote_spanned! {
span => #value
}
},
_ => node_to_tokens(cx, node, mode)
}
}
fn ident_from_tag_name(tag_name: &NodeName) -> Ident {
match tag_name {
NodeName::Path(path) => path
.path
.segments
.iter()
.last()
.map(|segment| segment.ident.clone())
.expect("element needs to have a name"),
NodeName::Block(_) => panic!("blocks not allowed in tag-name position"),
_ => Ident::new(
&tag_name.to_string().replace(['-', ':'], "_"),
tag_name.span(),
),
}
}
fn expr_to_ident(expr: &syn::Expr) -> Option<&ExprPath> {
match expr {
syn::Expr::Block(block) => block.block.stmts.last().and_then(|stmt| {