module restructuring for 0.7

This commit is contained in:
Greg Johnston 2024-04-28 19:39:28 -04:00
parent 21dd7e9c76
commit 3406446ebd
11 changed files with 165 additions and 148 deletions

View File

@ -1,10 +1,6 @@
use crate::{ use crate::{children::Children, component, prelude::*, IntoView};
children::Children, component, from_form_data::FromFormData, prelude::*, use leptos_dom::helpers::window;
IntoView, use leptos_server::{ServerAction, ServerMultiAction};
};
use leptos_dom::{events::submit, helpers::window};
use leptos_server::{ArcServerMultiAction, ServerAction, ServerMultiAction};
use reactive_graph::actions::{ArcMultiAction, MultiAction};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use server_fn::{ use server_fn::{
client::Client, codec::PostUrl, request::ClientReq, ServerFn, ServerFnError, client::Client, codec::PostUrl, request::ClientReq, ServerFn, ServerFnError,
@ -14,11 +10,17 @@ use tachys::{
html::{ html::{
attribute::any_attribute::AnyAttribute, attribute::any_attribute::AnyAttribute,
element::{form, Form}, element::{form, Form},
event::submit,
}, },
reactive_graph::node_ref::NodeRef, reactive_graph::node_ref::NodeRef,
renderer::dom::Dom, renderer::dom::Dom,
}; };
use web_sys::{FormData, SubmitEvent}; use thiserror::Error;
use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
use web_sys::{
Event, FormData, HtmlButtonElement, HtmlFormElement, HtmlInputElement,
SubmitEvent,
};
/// Automatically turns a server [Action](leptos_server::Action) into an HTML /// Automatically turns a server [Action](leptos_server::Action) into an HTML
/// [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) /// [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
@ -252,3 +254,85 @@ pub(crate) fn resolve_redirect_url(loc: &str) -> Option<web_sys::Url> {
} }
} }
} }
/// Tries to deserialize a type from form data. This can be used for client-side
/// validation during form submission.
pub trait FromFormData
where
Self: Sized + serde::de::DeserializeOwned,
{
/// Tries to deserialize the data, given only the `submit` event.
fn from_event(ev: &web_sys::Event) -> Result<Self, FromFormDataError>;
/// Tries to deserialize the data, given the actual form data.
fn from_form_data(
form_data: &web_sys::FormData,
) -> Result<Self, serde_qs::Error>;
}
#[derive(Error, Debug)]
pub enum FromFormDataError {
#[error("Could not find <form> connected to event.")]
MissingForm(Event),
#[error("Could not create FormData from <form>: {0:?}")]
FormData(JsValue),
#[error("Deserialization error: {0:?}")]
Deserialization(serde_qs::Error),
}
impl<T> FromFormData for T
where
T: serde::de::DeserializeOwned,
{
fn from_event(ev: &Event) -> Result<Self, FromFormDataError> {
let submit_ev = ev.unchecked_ref();
let form_data = form_data_from_event(submit_ev)?;
Self::from_form_data(&form_data)
.map_err(FromFormDataError::Deserialization)
}
fn from_form_data(
form_data: &web_sys::FormData,
) -> Result<Self, serde_qs::Error> {
let data =
web_sys::UrlSearchParams::new_with_str_sequence_sequence(form_data)
.unwrap_throw();
let data = data.to_string().as_string().unwrap_or_default();
serde_qs::Config::new(5, false).deserialize_str::<Self>(&data)
}
}
fn form_data_from_event(
ev: &SubmitEvent,
) -> Result<FormData, FromFormDataError> {
let submitter = ev.submitter();
let mut submitter_name_value = None;
let opt_form = match &submitter {
Some(el) => {
if let Some(form) = el.dyn_ref::<HtmlFormElement>() {
Some(form.clone())
} else if let Some(input) = el.dyn_ref::<HtmlInputElement>() {
submitter_name_value = Some((input.name(), input.value()));
Some(ev.target().unwrap().unchecked_into())
} else if let Some(button) = el.dyn_ref::<HtmlButtonElement>() {
submitter_name_value = Some((button.name(), button.value()));
Some(ev.target().unwrap().unchecked_into())
} else {
None
}
}
None => ev.target().map(|form| form.unchecked_into()),
};
match opt_form.as_ref().map(FormData::new_with_form) {
None => Err(FromFormDataError::MissingForm(ev.clone().into())),
Some(Err(e)) => Err(FromFormDataError::FormData(e)),
Some(Ok(form_data)) => {
if let Some((name, value)) = submitter_name_value {
form_data
.append_with_str(&name, &value)
.map_err(FromFormDataError::FormData)?;
}
Ok(form_data)
}
}
}

View File

@ -1,88 +1,2 @@
use thiserror::Error;
use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
use web_sys::{
Event, FormData, HtmlButtonElement, HtmlFormElement, HtmlInputElement,
SubmitEvent,
};
/// Tries to deserialize a type from form data. This can be used for client-side
/// validation during form submission.
pub trait FromFormData
where
Self: Sized + serde::de::DeserializeOwned,
{
/// Tries to deserialize the data, given only the `submit` event.
fn from_event(ev: &web_sys::Event) -> Result<Self, FromFormDataError>;
/// Tries to deserialize the data, given the actual form data.
fn from_form_data(
form_data: &web_sys::FormData,
) -> Result<Self, serde_qs::Error>;
}
#[derive(Error, Debug)]
pub enum FromFormDataError {
#[error("Could not find <form> connected to event.")]
MissingForm(Event),
#[error("Could not create FormData from <form>: {0:?}")]
FormData(JsValue),
#[error("Deserialization error: {0:?}")]
Deserialization(serde_qs::Error),
}
impl<T> FromFormData for T
where
T: serde::de::DeserializeOwned,
{
fn from_event(ev: &Event) -> Result<Self, FromFormDataError> {
let submit_ev = ev.unchecked_ref();
let form_data = form_data_from_event(submit_ev)?;
Self::from_form_data(&form_data)
.map_err(FromFormDataError::Deserialization)
}
fn from_form_data(
form_data: &web_sys::FormData,
) -> Result<Self, serde_qs::Error> {
let data =
web_sys::UrlSearchParams::new_with_str_sequence_sequence(form_data)
.unwrap_throw();
let data = data.to_string().as_string().unwrap_or_default();
serde_qs::Config::new(5, false).deserialize_str::<Self>(&data)
}
}
fn form_data_from_event(
ev: &SubmitEvent,
) -> Result<FormData, FromFormDataError> {
let submitter = ev.submitter();
let mut submitter_name_value = None;
let opt_form = match &submitter {
Some(el) => {
if let Some(form) = el.dyn_ref::<HtmlFormElement>() {
Some(form.clone())
} else if let Some(input) = el.dyn_ref::<HtmlInputElement>() {
submitter_name_value = Some((input.name(), input.value()));
Some(ev.target().unwrap().unchecked_into())
} else if let Some(button) = el.dyn_ref::<HtmlButtonElement>() {
submitter_name_value = Some((button.name(), button.value()));
Some(ev.target().unwrap().unchecked_into())
} else {
None
}
}
None => ev.target().map(|form| form.unchecked_into()),
};
match opt_form.as_ref().map(FormData::new_with_form) {
None => Err(FromFormDataError::MissingForm(ev.clone().into())),
Some(Err(e)) => Err(FromFormDataError::FormData(e)),
Some(Ok(form_data)) => {
if let Some((name, value)) = submitter_name_value {
form_data
.append_with_str(&name, &value)
.map_err(FromFormDataError::FormData)?;
}
Ok(form_data)
}
}
}

View File

@ -1,14 +1,8 @@
use leptos_dom::events::{on, EventDescriptor, On};
use std::future::Future;
use tachys::{ use tachys::{
html::attribute::{global::OnAttribute, Attribute},
hydration::Cursor, hydration::Cursor,
renderer::{dom::Dom, DomRenderer, Renderer}, renderer::dom::Dom,
ssr::StreamBuilder, ssr::StreamBuilder,
view::{ view::{Position, PositionState, Render, RenderHtml},
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
RenderHtml,
},
}; };
pub struct View<T>(T) pub struct View<T>(T)

View File

@ -146,40 +146,82 @@
extern crate self as leptos; extern crate self as leptos;
/// Exports all the core types of the library.
pub mod prelude { pub mod prelude {
// Traits
// These should always be exported from the prelude
pub use crate::suspense_component::FutureViewExt; pub use crate::suspense_component::FutureViewExt;
pub use reactive_graph::prelude::*; pub use reactive_graph::prelude::*;
pub use tachys::prelude::*; pub use tachys::prelude::*;
// Structs
// In the future, maybe we should remove this blanket export
// However, it is definitely useful relative to looking up every struct etc.
mod export_types {
#[cfg(feature = "nonce")]
pub use crate::nonce::*;
pub use crate::{
callback::*, children::*, component::*, context::*,
control_flow::*, error::*, form::*, hydration::*, into_view::*,
};
pub use leptos_config::*;
pub use leptos_dom::*;
pub use leptos_macro::*;
pub use leptos_server::*;
pub use reactive_graph::*;
pub use server_fn::*;
pub use tachys;
}
pub use export_types::*;
} }
mod action_form; /// Components used for working with HTML forms, like `<ActionForm>`.
pub use action_form::*; pub mod form;
/// A standard way to wrap functions and closures to pass them to components.
pub mod callback; pub mod callback;
/// Types that can be passed as the `children` prop of a component.
pub mod children; pub mod children;
#[doc(hidden)]
/// Traits used to implement component constructors.
pub mod component; pub mod component;
mod error_boundary; mod error_boundary;
pub use error_boundary::*;
/// Tools for handling errors.
pub mod error {
pub use crate::error_boundary::*;
pub use throw_error::*;
}
/// Control-flow components like `<Show>` and `<For>`.
pub mod control_flow {
pub use crate::{for_loop::*, show::*};
}
mod for_loop; mod for_loop;
mod hydration_scripts; mod show;
/// Components to enable server-side rendering and client-side hydration.
pub mod hydration;
/// Utilities for exporting nonces to be used for a Content Security Policy.
#[cfg(feature = "nonce")] #[cfg(feature = "nonce")]
pub mod nonce; pub mod nonce;
mod show;
/// Components to load asynchronous data.
pub mod suspense {
pub use crate::{suspense_component::*, transition::*};
}
#[macro_use] #[macro_use]
mod suspense_component; mod suspense_component;
/// Types for reactive string properties for components.
pub mod text_prop; pub mod text_prop;
mod transition; mod transition;
pub use for_loop::*; pub use leptos_macro;
pub use hydration_scripts::*;
pub use leptos_macro::*;
pub use reactive_graph::{
self,
signal::{arc_signal, create_signal, signal},
};
pub use server_fn; pub use server_fn;
pub use show::*;
pub use suspense_component::{Suspend, Suspense};
pub use throw_error as error;
pub use transition::*;
#[doc(hidden)] #[doc(hidden)]
pub use typed_builder; pub use typed_builder;
#[doc(hidden)] #[doc(hidden)]
@ -188,35 +230,24 @@ mod into_view;
pub use into_view::IntoView; pub use into_view::IntoView;
pub use leptos_dom; pub use leptos_dom;
pub use tachys; pub use tachys;
/// Tools to mount an application to the DOM, or to hydrate it from server-rendered HTML.
pub mod mount; pub mod mount;
pub use any_spawner::Executor;
pub use leptos_config as config; pub use leptos_config as config;
#[cfg(feature = "hydrate")]
pub use mount::hydrate_body;
pub use mount::mount_to_body;
pub use oco_ref as oco; pub use oco_ref as oco;
pub mod from_form_data; mod from_form_data;
pub use reactive_graph as reactive;
pub mod signals {
pub use reactive_graph::signal::{
arc_signal, signal, ArcReadSignal, ArcRwSignal, ArcWriteSignal,
ReadSignal, RwSignal, WriteSignal,
};
}
/// Provide and access data along the reactive graph, sharing data without directly passing arguments.
pub mod context { pub mod context {
pub use reactive_graph::owner::{provide_context, use_context}; pub use reactive_graph::owner::{provide_context, use_context};
} }
pub mod ev {
pub use tachys::html::event::*;
}
pub mod html {
pub use tachys::html::element::*;
}
pub use leptos_server as server; pub use leptos_server as server;
/// HTML element types.
pub use tachys::html::element as html;
/// HTML event types.
#[doc(no_inline)]
pub use tachys::html::event as ev;
/// Utilities for simple isomorphic logging to the console or terminal. /// Utilities for simple isomorphic logging to the console or terminal.
pub mod logging { pub mod logging {

View File

@ -240,12 +240,13 @@ pub trait FutureViewExt: Sized {
impl<F> FutureViewExt for F where F: Future + Sized {} impl<F> FutureViewExt for F where F: Future + Sized {}
/* // TODO remove in favor of Suspend()?
#[macro_export] #[macro_export]
macro_rules! suspend { macro_rules! suspend {
($fut:expr) => { ($fut:expr) => {
move || $crate::prelude::FutureViewExt::wait(async move { $fut }) move || $crate::prelude::FutureViewExt::wait(async move { $fut })
}; };
} }*/
pub struct Suspend<Fut>(pub Fut); pub struct Suspend<Fut>(pub Fut);

View File

@ -1,17 +1,9 @@
#![deny(missing_docs)] #![deny(missing_docs)]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
//! The DOM implementation for `leptos`. //! DOM helpers for Leptos.
use reactive_graph::owner::Owner;
use tachys::{
dom::body,
renderer::dom::Dom,
view::{Mountable, Render},
};
use web_sys::HtmlElement;
pub mod helpers; pub mod helpers;
pub use tachys::html::event as events;
/// Utilities for simple isomorphic logging to the console or terminal. /// Utilities for simple isomorphic logging to the console or terminal.
#[macro_use] #[macro_use]

View File

@ -462,6 +462,7 @@ generate_event_types! {
// Export `web_sys` event types // Export `web_sys` event types
use super::attribute::NextAttribute; use super::attribute::NextAttribute;
#[doc(no_inline)]
pub use web_sys::{ pub use web_sys::{
AnimationEvent, BeforeUnloadEvent, CompositionEvent, CustomEvent, AnimationEvent, BeforeUnloadEvent, CompositionEvent, CustomEvent,
DeviceMotionEvent, DeviceOrientationEvent, DragEvent, ErrorEvent, Event, DeviceMotionEvent, DeviceOrientationEvent, DragEvent, ErrorEvent, Event,