mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 14:52:35 -05:00
Compare commits
1 Commits
each-leak
...
remove-ope
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aa21d1085 |
@@ -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/>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -30,7 +30,7 @@ pub fn ErrorTemplate(
|
||||
.into_iter()
|
||||
.filter_map(|(_k, v)| v.downcast_ref::<AppError>().cloned())
|
||||
.collect();
|
||||
log!("Errors: {errors:#?}");
|
||||
println!("Errors: {errors:#?}");
|
||||
|
||||
// Only the response code for the first error is actually sent from the server
|
||||
// this may be customized by the specific application
|
||||
@@ -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,
|
||||
|
||||
@@ -12,7 +12,8 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
use tower_http::services::ServeDir;
|
||||
use std::sync::Arc;
|
||||
use leptos::{LeptosOptions, Errors, view};
|
||||
use crate::landing::{App, AppProps};
|
||||
use crate::error_template::{ErrorTemplate, ErrorTemplateProps};
|
||||
use crate::errors::AppError;
|
||||
|
||||
pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<Body>) -> AxumResponse {
|
||||
let options = &*options;
|
||||
@@ -22,10 +23,9 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
} else{
|
||||
let handler = leptos_axum::render_app_to_stream(
|
||||
options.to_owned(),
|
||||
move |cx| view!{ cx, <App/> }
|
||||
);
|
||||
let mut errors = Errors::default();
|
||||
errors.insert_with_default_key(AppError::NotFound);
|
||||
let handler = leptos_axum::render_app_to_stream(options.to_owned(), move |cx| view!{cx, <ErrorTemplate outside_errors=errors.clone()/>});
|
||||
handler(req).await.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,14 +29,7 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
cx,
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Stylesheet id="leptos" href="/pkg/errors_axum.css"/>
|
||||
<Router fallback=|cx| {
|
||||
let mut outside_errors = Errors::default();
|
||||
outside_errors.insert_with_default_key(AppError::NotFound);
|
||||
view! { cx,
|
||||
<ErrorTemplate outside_errors/>
|
||||
}
|
||||
.into_view(cx)
|
||||
}>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"Error Examples:"</h1>
|
||||
</header>
|
||||
|
||||
@@ -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/>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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/>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -86,19 +86,21 @@ pub fn ContactList(cx: Scope) -> impl IntoView {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Params, PartialEq, Clone, Debug)]
|
||||
pub struct ContactParams {
|
||||
id: usize,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Contact(cx: Scope) -> impl IntoView {
|
||||
log::debug!("rendering <Contact/>");
|
||||
|
||||
let params = use_params::<ContactParams>(cx);
|
||||
let params = use_params_map(cx);
|
||||
let contact = create_resource(
|
||||
cx,
|
||||
move || params().map(|params| params.id).ok(),
|
||||
move || {
|
||||
params()
|
||||
.get("id")
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.parse::<usize>()
|
||||
.ok()
|
||||
},
|
||||
// any of the following would work (they're identical)
|
||||
// move |id| async move { get_contact(id).await }
|
||||
// move |id| get_contact(id),
|
||||
|
||||
@@ -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! {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -176,25 +176,3 @@ pub type ChildrenFn = Box<dyn Fn(Scope) -> Fragment>;
|
||||
/// 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(Scope) -> Fragment>;
|
||||
|
||||
/// A type for taking anything that implements [`IntoAttribute`].
|
||||
/// Very usefull inside components.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust
|
||||
/// use leptos::*;
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn MyHeading(
|
||||
/// cx: Scope,
|
||||
/// text: String,
|
||||
/// #[prop(optional, into)]
|
||||
/// class: Option<AttributeValue>
|
||||
/// ) -> impl IntoView {
|
||||
/// view!{
|
||||
/// cx,
|
||||
/// <h1 class=class>{text}</h1>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub type AttributeValue = Box<dyn IntoAttribute>;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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,
|
||||
{
|
||||
|
||||
@@ -102,67 +102,24 @@ impl std::fmt::Debug for Attribute {
|
||||
pub trait IntoAttribute {
|
||||
/// Converts the object into an [Attribute].
|
||||
fn into_attribute(self, cx: Scope) -> Attribute;
|
||||
/// Helper function for dealing with [Box<dyn IntoAttribute>]
|
||||
fn into_attribute_boxed(self: Box<Self>, cx: Scope) -> Attribute;
|
||||
}
|
||||
|
||||
impl<T: IntoAttribute + 'static> From<T> for Box<dyn IntoAttribute> {
|
||||
fn from(value: T) -> Self {
|
||||
Box::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Attribute {
|
||||
#[inline]
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_attribute_boxed(self: Box<Self>, _: Scope) -> Attribute {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_attr_boxed {
|
||||
() => {
|
||||
#[inline]
|
||||
fn into_attribute_boxed(self: Box<Self>, cx: Scope) -> Attribute {
|
||||
self.into_attribute(cx)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<Attribute> {
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
self.unwrap_or(Attribute::Option(cx, None))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for String {
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
Attribute::String(self)
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for bool {
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
Attribute::Bool(self)
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<String> {
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
Attribute::Option(cx, self)
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl<T, U> IntoAttribute for T
|
||||
@@ -174,35 +131,12 @@ where
|
||||
let modified_fn = Rc::new(move || (self)().into_attribute(cx));
|
||||
Attribute::Fn(cx, modified_fn)
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl<T: IntoAttribute> IntoAttribute for (Scope, T) {
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
self.1.into_attribute(self.0)
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for (Scope, Option<Box<dyn IntoAttribute>>) {
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
match self.1 {
|
||||
Some(bx) => bx.into_attribute_boxed(self.0),
|
||||
None => Attribute::Option(self.0, None),
|
||||
}
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for (Scope, Box<dyn IntoAttribute>) {
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
self.1.into_attribute_boxed(self.0)
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
macro_rules! attr_type {
|
||||
@@ -211,22 +145,12 @@ macro_rules! attr_type {
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
Attribute::String(self.to_string())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_attribute_boxed(self: Box<Self>, cx: Scope) -> Attribute {
|
||||
self.into_attribute(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<$attr_type> {
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
Attribute::Option(cx, self.map(|n| n.to_string()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_attribute_boxed(self: Box<Self>, cx: Scope) -> Attribute {
|
||||
self.into_attribute(cx)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ pub fn impl_params(ast: &syn::DeriveInput) -> proc_macro::TokenStream {
|
||||
|
||||
let gen = quote! {
|
||||
impl Params for #name {
|
||||
fn from_map(map: &::leptos_router::ParamsMap) -> Result<Self, ::leptos_router::ParamsError> {
|
||||
fn from_map(map: &::leptos_router::ParamsMap) -> Result<Self, ::leptos_router::RouterError> {
|
||||
Ok(Self {
|
||||
#(#fields,)*
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,9 +4,6 @@ use std::{error::Error, rc::Rc};
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
||||
type OnFormData = Rc<dyn Fn(&web_sys::FormData)>;
|
||||
type OnResponse = Rc<dyn Fn(&web_sys::Response)>;
|
||||
|
||||
/// An HTML [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) progressively
|
||||
/// enhanced to use client-side routing.
|
||||
#[component]
|
||||
@@ -33,14 +30,13 @@ pub fn Form<A>(
|
||||
error: Option<RwSignal<Option<Box<dyn Error>>>>,
|
||||
/// A callback will be called with the [FormData](web_sys::FormData) when the form is submitted.
|
||||
#[prop(optional)]
|
||||
on_form_data: Option<OnFormData>,
|
||||
/// Sets the `class` attribute on the underlying `<form>` tag, making it easier to style.
|
||||
#[prop(optional, into)]
|
||||
class: Option<AttributeValue>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
on_form_data: Option<Rc<dyn Fn(&web_sys::FormData)>>,
|
||||
/// A callback will be called with the [Response](web_sys::Response) the server sends in response
|
||||
/// to a form submission.
|
||||
#[prop(optional)]
|
||||
on_response: Option<OnResponse>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
on_response: Option<Rc<dyn Fn(&web_sys::Response)>>,
|
||||
/// Component children; should include the HTML of the form elements.
|
||||
children: Children,
|
||||
) -> impl IntoView
|
||||
@@ -54,9 +50,8 @@ where
|
||||
enctype: Option<String>,
|
||||
version: Option<RwSignal<usize>>,
|
||||
error: Option<RwSignal<Option<Box<dyn Error>>>>,
|
||||
on_form_data: Option<OnFormData>,
|
||||
on_response: Option<OnResponse>,
|
||||
class: Option<Attribute>,
|
||||
#[allow(clippy::type_complexity)] on_form_data: Option<Rc<dyn Fn(&web_sys::FormData)>>,
|
||||
#[allow(clippy::type_complexity)] on_response: Option<Rc<dyn Fn(&web_sys::Response)>>,
|
||||
children: Children,
|
||||
) -> HtmlElement<Form> {
|
||||
let action_version = version;
|
||||
@@ -133,7 +128,6 @@ where
|
||||
action=move || action.get()
|
||||
enctype=enctype
|
||||
on:submit=on_submit
|
||||
class=class
|
||||
>
|
||||
{children(cx)}
|
||||
</form>
|
||||
@@ -141,7 +135,6 @@ where
|
||||
}
|
||||
|
||||
let action = use_resolved_path(cx, move || action.to_href()());
|
||||
let class = class.map(|bx| bx.into_attribute_boxed(cx));
|
||||
inner(
|
||||
cx,
|
||||
method,
|
||||
@@ -151,7 +144,6 @@ where
|
||||
error,
|
||||
on_form_data,
|
||||
on_response,
|
||||
class,
|
||||
children,
|
||||
)
|
||||
}
|
||||
@@ -166,9 +158,6 @@ pub fn ActionForm<I, O>(
|
||||
/// by default using [create_server_action](leptos_server::create_server_action) or added
|
||||
/// manually using [leptos_server::Action::using_server_fn].
|
||||
action: Action<I, Result<O, ServerFnError>>,
|
||||
/// Sets the `class` attribute on the underlying `<form>` tag, making it easier to style.
|
||||
#[prop(optional, into)]
|
||||
class: Option<AttributeValue>,
|
||||
/// Component children; should include the HTML of the form elements.
|
||||
children: Children,
|
||||
) -> impl IntoView
|
||||
@@ -219,7 +208,7 @@ where
|
||||
action.set_pending(false);
|
||||
});
|
||||
});
|
||||
let class = class.map(|bx| bx.into_attribute_boxed(cx));
|
||||
|
||||
Form(
|
||||
cx,
|
||||
FormProps::builder()
|
||||
@@ -228,7 +217,6 @@ where
|
||||
.on_form_data(on_form_data)
|
||||
.on_response(on_response)
|
||||
.method("post")
|
||||
.class(class)
|
||||
.children(children)
|
||||
.build(),
|
||||
)
|
||||
@@ -244,9 +232,6 @@ pub fn MultiActionForm<I, O>(
|
||||
/// by default using [create_server_action](leptos_server::create_server_action) or added
|
||||
/// manually using [leptos_server::Action::using_server_fn].
|
||||
action: MultiAction<I, Result<O, ServerFnError>>,
|
||||
/// Sets the `class` attribute on the underlying `<form>` tag, making it easier to style.
|
||||
#[prop(optional, into)]
|
||||
class: Option<AttributeValue>,
|
||||
/// Component children; should include the HTML of the form elements.
|
||||
children: Children,
|
||||
) -> impl IntoView
|
||||
@@ -280,12 +265,10 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let class = class.map(|bx| bx.into_attribute_boxed(cx));
|
||||
view! { cx,
|
||||
<form
|
||||
method="POST"
|
||||
action=action
|
||||
class=class
|
||||
on:submit=on_submit
|
||||
>
|
||||
{children(cx)}
|
||||
|
||||
@@ -63,7 +63,7 @@ pub fn A<H>(
|
||||
replace: bool,
|
||||
/// Sets the `class` attribute on the underlying `<a>` tag, making it easier to style.
|
||||
#[prop(optional, into)]
|
||||
class: Option<AttributeValue>,
|
||||
class: Option<MaybeSignal<String>>,
|
||||
/// The nodes or elements to be shown inside the link.
|
||||
children: Children,
|
||||
) -> impl IntoView
|
||||
@@ -76,7 +76,7 @@ where
|
||||
exact: bool,
|
||||
state: Option<State>,
|
||||
replace: bool,
|
||||
class: Option<AttributeValue>,
|
||||
class: Option<MaybeSignal<String>>,
|
||||
children: Children,
|
||||
) -> HtmlElement<A> {
|
||||
let location = use_location(cx);
|
||||
@@ -104,7 +104,7 @@ where
|
||||
prop:state={state.map(|s| s.to_js_value())}
|
||||
prop:replace={replace}
|
||||
aria-current=move || if is_active.get() { Some("page") } else { None }
|
||||
class=class
|
||||
class=move || class.as_ref().map(|class| class.get())
|
||||
>
|
||||
{children(cx)}
|
||||
</a>
|
||||
|
||||
@@ -45,10 +45,8 @@
|
||||
//! // LR will enhance the active <a> link with the [aria-current] attribute
|
||||
//! // we can use this for styling them with CSS like `[aria-current] { font-weight: bold; }`
|
||||
//! <A href="contacts">"Contacts"</A>
|
||||
//! // But we can also use a normal class attribute like it is a normal component
|
||||
//! <A href="settings" class="my-class">"Settings"</A>
|
||||
//! // It also supports signals!
|
||||
//! <A href="about" class=move || "my-class">"About"</A>
|
||||
//! <A href="about">"About"</A>
|
||||
//! <A href="settings">"Settings"</A>
|
||||
//! </nav>
|
||||
//! <main>
|
||||
//! // <Routes/> both defines our routes and shows them on the page
|
||||
|
||||
Reference in New Issue
Block a user