module restructuring for 0.7
This commit is contained in:
parent
21dd7e9c76
commit
3406446ebd
|
@ -1,10 +1,6 @@
|
|||
use crate::{
|
||||
children::Children, component, from_form_data::FromFormData, prelude::*,
|
||||
IntoView,
|
||||
};
|
||||
use leptos_dom::{events::submit, helpers::window};
|
||||
use leptos_server::{ArcServerMultiAction, ServerAction, ServerMultiAction};
|
||||
use reactive_graph::actions::{ArcMultiAction, MultiAction};
|
||||
use crate::{children::Children, component, prelude::*, IntoView};
|
||||
use leptos_dom::helpers::window;
|
||||
use leptos_server::{ServerAction, ServerMultiAction};
|
||||
use serde::de::DeserializeOwned;
|
||||
use server_fn::{
|
||||
client::Client, codec::PostUrl, request::ClientReq, ServerFn, ServerFnError,
|
||||
|
@ -14,11 +10,17 @@ use tachys::{
|
|||
html::{
|
||||
attribute::any_attribute::AnyAttribute,
|
||||
element::{form, Form},
|
||||
event::submit,
|
||||
},
|
||||
reactive_graph::node_ref::NodeRef,
|
||||
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
|
||||
/// [`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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
use leptos_dom::events::{on, EventDescriptor, On};
|
||||
use std::future::Future;
|
||||
use tachys::{
|
||||
html::attribute::{global::OnAttribute, Attribute},
|
||||
hydration::Cursor,
|
||||
renderer::{dom::Dom, DomRenderer, Renderer},
|
||||
renderer::dom::Dom,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
|
||||
RenderHtml,
|
||||
},
|
||||
view::{Position, PositionState, Render, RenderHtml},
|
||||
};
|
||||
|
||||
pub struct View<T>(T)
|
||||
|
|
|
@ -146,40 +146,82 @@
|
|||
|
||||
extern crate self as leptos;
|
||||
|
||||
/// Exports all the core types of the library.
|
||||
pub mod prelude {
|
||||
// Traits
|
||||
// These should always be exported from the prelude
|
||||
pub use crate::suspense_component::FutureViewExt;
|
||||
pub use reactive_graph::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;
|
||||
pub use action_form::*;
|
||||
/// Components used for working with HTML forms, like `<ActionForm>`.
|
||||
pub mod form;
|
||||
|
||||
/// A standard way to wrap functions and closures to pass them to components.
|
||||
pub mod callback;
|
||||
|
||||
/// Types that can be passed as the `children` prop of a component.
|
||||
pub mod children;
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Traits used to implement component constructors.
|
||||
pub mod component;
|
||||
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 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")]
|
||||
pub mod nonce;
|
||||
mod show;
|
||||
|
||||
/// Components to load asynchronous data.
|
||||
pub mod suspense {
|
||||
pub use crate::{suspense_component::*, transition::*};
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
mod suspense_component;
|
||||
|
||||
/// Types for reactive string properties for components.
|
||||
pub mod text_prop;
|
||||
mod transition;
|
||||
pub use for_loop::*;
|
||||
pub use hydration_scripts::*;
|
||||
pub use leptos_macro::*;
|
||||
pub use reactive_graph::{
|
||||
self,
|
||||
signal::{arc_signal, create_signal, signal},
|
||||
};
|
||||
pub use leptos_macro;
|
||||
pub use server_fn;
|
||||
pub use show::*;
|
||||
pub use suspense_component::{Suspend, Suspense};
|
||||
pub use throw_error as error;
|
||||
pub use transition::*;
|
||||
#[doc(hidden)]
|
||||
pub use typed_builder;
|
||||
#[doc(hidden)]
|
||||
|
@ -188,35 +230,24 @@ mod into_view;
|
|||
pub use into_view::IntoView;
|
||||
pub use leptos_dom;
|
||||
pub use tachys;
|
||||
/// Tools to mount an application to the DOM, or to hydrate it from server-rendered HTML.
|
||||
pub mod mount;
|
||||
pub use any_spawner::Executor;
|
||||
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 mod from_form_data;
|
||||
|
||||
pub mod signals {
|
||||
pub use reactive_graph::signal::{
|
||||
arc_signal, signal, ArcReadSignal, ArcRwSignal, ArcWriteSignal,
|
||||
ReadSignal, RwSignal, WriteSignal,
|
||||
};
|
||||
}
|
||||
mod from_form_data;
|
||||
pub use reactive_graph as reactive;
|
||||
|
||||
/// Provide and access data along the reactive graph, sharing data without directly passing arguments.
|
||||
pub mod 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;
|
||||
/// 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.
|
||||
pub mod logging {
|
||||
|
|
|
@ -240,12 +240,13 @@ pub trait FutureViewExt: Sized {
|
|||
|
||||
impl<F> FutureViewExt for F where F: Future + Sized {}
|
||||
|
||||
/* // TODO remove in favor of Suspend()?
|
||||
#[macro_export]
|
||||
macro_rules! suspend {
|
||||
($fut:expr) => {
|
||||
move || $crate::prelude::FutureViewExt::wait(async move { $fut })
|
||||
};
|
||||
}
|
||||
}*/
|
||||
|
||||
pub struct Suspend<Fut>(pub Fut);
|
||||
|
||||
|
|
|
@ -1,17 +1,9 @@
|
|||
#![deny(missing_docs)]
|
||||
#![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 use tachys::html::event as events;
|
||||
|
||||
/// Utilities for simple isomorphic logging to the console or terminal.
|
||||
#[macro_use]
|
||||
|
|
|
@ -462,6 +462,7 @@ generate_event_types! {
|
|||
|
||||
// Export `web_sys` event types
|
||||
use super::attribute::NextAttribute;
|
||||
#[doc(no_inline)]
|
||||
pub use web_sys::{
|
||||
AnimationEvent, BeforeUnloadEvent, CompositionEvent, CustomEvent,
|
||||
DeviceMotionEvent, DeviceOrientationEvent, DragEvent, ErrorEvent, Event,
|
||||
|
|
Loading…
Reference in New Issue