Compare commits

...

9 Commits

Author SHA1 Message Date
Greg Johnston
8a99623fd6 0.2.0-alpha (#515) 2023-02-13 07:49:29 -05:00
Greg Johnston
7d6c4930e4 remove .unwrap() from redirect in Actix integration (#514) 2023-02-13 06:02:43 -05:00
IcosaHedron
81d6689cc0 do not unwrap use_context in integrations axum redirect (#513) 2023-02-12 21:59:12 -05:00
Greg Johnston
989b5b93c3 CI: fix Wasm testing (#511) 2023-02-12 19:39:32 -05:00
Greg Johnston
ca510f72c1 fix: SSR export in Wasm mode (#512) 2023-02-12 19:12:15 -05:00
Greg Johnston
6dd3be75d1 fix: import in leptos_dom and add Wasm build to CI for regressions (#510) 2023-02-12 18:58:57 -05:00
g-re-g
51e11e756a Typos and a small cleanup (#509) 2023-02-12 18:11:31 -05:00
Greg Johnston
1dbcfe2861 change: reorganize module exports and reexports (#503) 2023-02-12 17:04:36 -05:00
Greg Johnston
db3f46c501 Add docs on testing (closes #489) (#508) 2023-02-12 17:03:12 -05:00
57 changed files with 609 additions and 1644 deletions

View File

@@ -30,6 +30,9 @@ jobs:
override: true
components: rustfmt
- name: Add wasm32-unknown-unknown
run: rustup target add wasm32-unknown-unknown
- name: Setup cargo-make
uses: davidB/rust-cargo-make@v1
@@ -43,4 +46,3 @@ jobs:
- name: Run tests with all features
run: cargo make ci

View File

@@ -19,17 +19,17 @@ members = [
exclude = ["benchmarks", "examples"]
[workspace.package]
version = "0.1.3"
version = "0.2.0-alpha"
[workspace.dependencies]
leptos = { path = "./leptos", default-features = false, version = "0.1.3" }
leptos_dom = { path = "./leptos_dom", default-features = false, version = "0.1.3" }
leptos_macro = { path = "./leptos_macro", default-features = false, version = "0.1.3" }
leptos_reactive = { path = "./leptos_reactive", default-features = false, version = "0.1.3" }
leptos_server = { path = "./leptos_server", default-features = false, version = "0.1.3" }
leptos_config = { path = "./leptos_config", default-features = false, version = "0.1.3" }
leptos_router = { path = "./router", version = "0.1.3" }
leptos_meta = { path = "./meta", default-feature = false, version = "0.1.3" }
leptos = { path = "./leptos", default-features = false, version = "0.2.0-alpha" }
leptos_dom = { path = "./leptos_dom", default-features = false, version = "0.2.0-alpha" }
leptos_macro = { path = "./leptos_macro", default-features = false, version = "0.2.0-alpha" }
leptos_reactive = { path = "./leptos_reactive", default-features = false, version = "0.2.0-alpha" }
leptos_server = { path = "./leptos_server", default-features = false, version = "0.2.0-alpha" }
leptos_config = { path = "./leptos_config", default-features = false, version = "0.2.0-alpha" }
leptos_router = { path = "./router", version = "0.2.0-alpha" }
leptos_meta = { path = "./meta", default-feature = false, version = "0.2.0-alpha" }
[profile.release]
codegen-units = 1

View File

@@ -12,13 +12,17 @@ dependencies = ["build", "check-examples", "test"]
[tasks.build]
clear = true
dependencies = ["build-all"]
dependencies = ["build-all", "build-wasm"]
[tasks.build-all]
command = "cargo"
args = ["+nightly", "build-all-features"]
install_crate = "cargo-all-features"
[tasks.build-wasm]
clear = true
dependencies = [{ name = "build-wasm", path = "leptos" }]
[tasks.check-examples]
clear = true
dependencies = [

View File

@@ -12,8 +12,8 @@
- [Error Handling](./view/07_errors.md)
- [Parent-Child Communication](./view/08_parent_child.md)
- [Passing Children to Components](./view/09_component_children.md)
- [Interlude: Reactivity and Functions](interlude_functions.md)
- [Testing]()
- [Interlude: Reactivity and Functions](./interlude_functions.md)
- [Testing](./testing.md)
- [Interlude: Styling — CSS, Tailwind, Style.rs, and more]()
- [Async]()
- [Resource]()

180
docs/book/src/testing.md Normal file
View File

@@ -0,0 +1,180 @@
# Testing Your Components
Testing user interfaces can be relatively tricky, but really important. This article
will discuss a couple principles and approaches for testing a Leptos app.
## 1. Test business logic with ordinary Rust tests
In many cases, it makes sense to pull the logic out of your components and test
it separately. For some simple components, theres no particular logic to test, but
for many its worth using a testable wrapping type and implementing the logic in
ordinary Rust `impl` blocks.
For example, instead of embedding logic in a component directly like this:
```rust
#[component]
pub fn TodoApp(cx: Scope) -> impl IntoView {
let (todos, set_todos) = create_signal(cx, vec![Todo { /* ... */ }]);
// ⚠️ this is hard to test because it's embedded in the component
let maximum = move || todos.with(|todos| {
todos.iter().filter(|todo| todo.completed).sum()
});
}
```
You could pull that logic out into a separate data structure and test it:
```rust
pub struct Todos(Vec<Todo>);
impl Todos {
pub fn remaining(&self) -> usize {
todos.iter().filter(|todo| todo.completed).sum()
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_remaining {
// ...
}
}
#[component]
pub fn TodoApp(cx: Scope) -> impl IntoView {
let (todos, set_todos) = create_signal(cx, Todos(vec![Todo { /* ... */ }]));
// ✅ this has a test associated with it
let maximum = move || todos.with(Todos::remaining);
}
```
In general, the less of your logic is wrapped into your components themselves, the
more idiomatic your code will feel and the easier it will be to test.
## 2. Test components with `wasm-bindgen-test`
[`wasm-bindgen-test`](https://crates.io/crates/wasm-bindgen-test) is a great utility
for integrating or end-to-end testing WebAssembly apps in a headless browser.
To use this testing utility, you need to add `wasm-bindgen-test` to your `Cargo.toml`:
```toml
[dev-dependencies]
wasm-bindgen-test = "0.3.0"
```
You should create tests in a separate `tests` directory. You can then run your tests in the browser of your choice:
```bash
wasm-pack test --firefox
```
> To see the full setup, check out the tests for the [`counter`](https://github.com/leptos-rs/leptos/tree/main/examples/counter) example.
### Writing Your Tests
Most tests will involve some combination of vanilla DOM manipulation and comparison to a `view`. For example, heres a test [for the
`counter` example](https://github.com/leptos-rs/leptos/blob/main/examples/counter/tests/mod.rs).
First, we set up the testing environment.
```rust
use wasm_bindgen_test::*;
use counter::*;
use leptos::*;
use web_sys::HtmlElement;
// tell the test runner to run tests in the browser
wasm_bindgen_test_configure!(run_in_browser);
```
Im going to create a simpler wrapper for each test case, and mount it there.
This makes it easy to encapsulate the test results.
```rust
// like marking a regular test with #[test]
#[wasm_bindgen_test]
fn clear() {
let document = leptos::document();
let test_wrapper = document.create_element("section").unwrap();
document.body().unwrap().append_child(&test_wrapper);
// start by rendering our counter and mounting it to the DOM
// note that we start at the initial value of 10
mount_to(
test_wrapper.clone().unchecked_into(),
|cx| view! { cx, <SimpleCounter initial_value=10 step=1/> },
);
```
Well use some manual DOM operations to grab the `<div>` that wraps
the whole component, as well as the `clear` button.
```rust
// now we extract the buttons by iterating over the DOM
// this would be easier if they had IDs
let div = test_wrapper.query_selector("div").unwrap().unwrap();
let clear = test_wrapper
.query_selector("button")
.unwrap()
.unwrap()
.unchecked_into::<web_sys::HtmlElement>();
```
Now we can use ordinary DOM APIs to simulate user interaction.
```rust
// now let's click the `clear` button
clear.click();
```
You can test individual DOM element attributes or text node values. Sometimes
I like to test the whole view at once. We can do this by testing the elements
`outerHTML` against our expectations.
```rust
assert_eq!(
div.outer_html(),
// here we spawn a mini reactive system to render the test case
run_scope(create_runtime(), |cx| {
// it's as if we're creating it with a value of 0, right?
let (value, set_value) = create_signal(cx, 0);
// we can remove the event listeners because they're not rendered to HTML
view! { cx,
<div>
<button>"Clear"</button>
<button>"-1"</button>
<span>"Value: " {value} "!"</span>
<button>"+1"</button>
</div>
}
// the view returned an HtmlElement<Div>, which is a smart pointer for
// a DOM element. So we can still just call .outer_html()
.outer_html()
})
);
```
That test involved us manually replicating the `view` thats inside the component.
There's actually an easier way to do this... We can just test against a `<SimpleCounter/>`
with the initial value `0`. This is where our wrapping element comes in: Ill just test
the wrappers `innerHTML` against another comparison case.
```rust
assert_eq!(test_wrapper.inner_html(), {
let comparison_wrapper = document.create_element("section").unwrap();
leptos::mount_to(
comparison_wrapper.clone().unchecked_into(),
|cx| view! { cx, <SimpleCounter initial_value=0 step=1/>},
);
comparison_wrapper.inner_html()
});
}
```
This is only a very limited introduction to testing. But I hope its useful as you begin to build applications.
> For more, see [the testing section of the `wasm-bindgen` guide](https://rustwasm.github.io/wasm-bindgen/wasm-bindgen-test/index.html#testing-on-wasm32-unknown-unknown-with-wasm-bindgen-test).

View File

@@ -1,16 +1,86 @@
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
use counter::*;
use leptos::*;
use web_sys::HtmlElement;
use counter::*;
#[wasm_bindgen_test]
fn clear() {
let document = leptos::document();
let test_wrapper = document.create_element("section").unwrap();
document.body().unwrap().append_child(&test_wrapper);
// start by rendering our counter and mounting it to the DOM
// note that we start at the initial value of 10
mount_to(
test_wrapper.clone().unchecked_into(),
|cx| view! { cx, <SimpleCounter initial_value=10 step=1/> },
);
// now we extract the buttons by iterating over the DOM
// this would be easier if they had IDs
let div = test_wrapper.query_selector("div").unwrap().unwrap();
let clear = test_wrapper
.query_selector("button")
.unwrap()
.unwrap()
.unchecked_into::<web_sys::HtmlElement>();
// now let's click the `clear` button
clear.click();
// now let's test the <div> against the expected value
// we can do this by testing its `outerHTML`
assert_eq!(
div.outer_html(),
// here we spawn a mini reactive system, just to render the
// test case
run_scope(create_runtime(), |cx| {
// it's as if we're creating it with a value of 0, right?
let (value, set_value) = create_signal(cx, 0);
// we can remove the event listeners because they're not rendered to HTML
view! { cx,
<div>
<button>"Clear"</button>
<button>"-1"</button>
<span>"Value: " {value} "!"</span>
<button>"+1"</button>
</div>
}
// the view returned an HtmlElement<Div>, which is a smart pointer for
// a DOM element. So we can still just call .outer_html()
.outer_html()
})
);
// There's actually an easier way to do this...
// We can just test against a <SimpleCounter/> with the initial value 0
assert_eq!(test_wrapper.inner_html(), {
let comparison_wrapper = document.create_element("section").unwrap();
leptos::mount_to(
comparison_wrapper.clone().unchecked_into(),
|cx| view! { cx, <SimpleCounter initial_value=0 step=1/>},
);
comparison_wrapper.inner_html()
});
}
#[wasm_bindgen_test]
fn inc() {
mount_to_body(|cx| view! { cx, <SimpleCounter initial_value=0 step=1/> });
let document = leptos::document();
let div = document.query_selector("div").unwrap().unwrap();
let test_wrapper = document.create_element("section").unwrap();
document.body().unwrap().append_child(&test_wrapper);
mount_to(
test_wrapper.clone().unchecked_into(),
|cx| view! { cx, <SimpleCounter initial_value=0 step=1/> },
);
// You can do testing with vanilla DOM operations
let document = leptos::document();
let div = test_wrapper.query_selector("div").unwrap().unwrap();
let clear = div
.first_child()
.unwrap()
@@ -47,4 +117,40 @@ fn inc() {
clear.click();
assert_eq!(text.text_content(), Some("Value: 0!".to_string()));
// Or you can test against a sample view!
assert_eq!(
div.outer_html(),
run_scope(create_runtime(), |cx| {
let (value, _) = create_signal(cx, 0);
view! { cx,
<div>
<button>"Clear"</button>
<button>"-1"</button>
<span>"Value: " {value} "!"</span>
<button>"+1"</button>
</div>
}
}
.outer_html())
);
inc.click();
assert_eq!(
div.outer_html(),
run_scope(create_runtime(), |cx| {
// because we've clicked, it's as if the signal is starting at 1
let (value, _) = create_signal(cx, 1);
view! { cx,
<div>
<button>"Clear"</button>
<button>"-1"</button>
<span>"Value: " {value} "!"</span>
<button>"+1"</button>
</div>
}
}
.outer_html())
);
}

View File

@@ -25,6 +25,7 @@ leptos_router = { path = "../../router", default-features = false }
log = "0.4"
simple_logger = "4.0.0"
gloo-net = { git = "https://github.com/rustwasm/gloo" }
wasm-bindgen = "0.2"
[features]
default = []

View File

@@ -194,16 +194,16 @@ pub fn MultiuserCounter(cx: Scope) -> impl IntoView {
use futures::StreamExt;
let mut source = gloo_net::eventsource::futures::EventSource::new("/api/events")
.expect_throw("couldn't connect to SSE stream");
.expect("couldn't connect to SSE stream");
let s = create_signal_from_stream(
cx,
source.subscribe("message").unwrap().map(|value| {
value
.expect_throw("no message event")
.expect("no message event")
.1
.data()
.as_string()
.expect_throw("expected string value")
.expect("expected string value")
}),
);

View File

@@ -1,4 +1,4 @@
use leptos::{ev, *};
use leptos::{ev, html::*, *};
pub struct Props {
/// The starting value for the counter
@@ -25,7 +25,9 @@ pub fn view(cx: Scope, props: Props) -> impl IntoView {
.child((
cx,
button(cx)
.on(ev::click, move |_| set_value.update(|value| *value -= step))
.on(ev::click, move |_| {
set_value.update(|value| *value -= step)
})
.child((cx, "-1")),
))
.child((
@@ -38,7 +40,9 @@ pub fn view(cx: Scope, props: Props) -> impl IntoView {
.child((
cx,
button(cx)
.on(ev::click, move |_| set_value.update(|value| *value += step))
.on(ev::click, move |_| {
set_value.update(|value| *value += step)
})
.child((cx, "+1")),
))
}

View File

@@ -1,5 +1,4 @@
use leptos::*;
use leptos::{For, ForProps};
use leptos::{For, ForProps, *};
const MANY_COUNTERS: usize = 1000;
@@ -84,9 +83,11 @@ fn Counter(
value: ReadSignal<i32>,
set_value: WriteSignal<i32>,
) -> impl IntoView {
let CounterUpdater { set_counters } = use_context(cx).unwrap_throw();
let CounterUpdater { set_counters } = use_context(cx).unwrap();
let input = move |ev| set_value(event_target_value(&ev).parse::<i32>().unwrap_or_default());
let input = move |ev| {
set_value(event_target_value(&ev).parse::<i32>().unwrap_or_default())
};
// just an example of how a cleanup function works
// this will run when the scope is disposed, i.e., when this row is deleted

View File

@@ -91,9 +91,12 @@ fn Counter(
value: ReadSignal<i32>,
set_value: WriteSignal<i32>,
) -> impl IntoView {
let CounterUpdater { set_counters } = use_context(cx).unwrap_throw();
let CounterUpdater { set_counters } = use_context(cx).unwrap();
let input = move |ev| set_value.set(event_target_value(&ev).parse::<i32>().unwrap_or_default());
let input = move |ev| {
set_value
.set(event_target_value(&ev).parse::<i32>().unwrap_or_default())
};
view! { cx,
<li>

View File

@@ -32,45 +32,49 @@ tokio = { version = "1.22.0", features = ["full"], optional = true }
http = { version = "0.2.8" }
thiserror = "1.0.38"
tracing = "0.1.37"
wasm-bindgen = "0.2"
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = ["dep:axum", "dep:tower", "dep:tower-http", "dep:tokio", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", "dep:leptos_axum"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
"dep:leptos_axum",
]
[package.metadata.cargo-all-features]
denylist = [
"axum",
"tower",
"tower-http",
"tokio",
"leptos_axum",
]
denylist = ["axum", "tower", "tower-http", "tokio", "leptos_axum"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
[package.metadata.leptos]
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
output-name = "errors_axum"
output-name = "errors_axum"
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
site-root = "target/site"
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
# Defaults to pkg
site-pkg-dir = "pkg"
site-pkg-dir = "pkg"
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
style-file = "./style.css"
# [Optional] Files in the asset-dir will be copied to the site-root directory
assets-dir = "public"
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
site-addr = "127.0.0.1:3000"
site-addr = "127.0.0.1:3000"
# The port to use for automatic reload monitoring
reload-port = 3001
reload-port = 3001
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
end2end-cmd = "npx playwright test"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
browserquery = "defaults"
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
watch = false
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"
# The features to use when compiling the bin target

View File

@@ -8,4 +8,4 @@ leptos = { path = "../../leptos" }
console_log = "0.2"
log = "0.4"
console_error_panic_hook = "0.1.7"
web-sys = "0.3"

View File

@@ -29,6 +29,7 @@ sqlx = { version = "0.6.2", features = [
"runtime-tokio-rustls",
"sqlite",
], optional = true }
wasm-bindgen = "0.2"
[features]
default = ["ssr"]

View File

@@ -36,22 +36,26 @@ sqlx = { version = "0.6.2", features = [
], optional = true }
thiserror = "1.0.38"
tracing = "0.1.37"
wasm-bindgen = "0.2"
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = ["dep:axum", "dep:tower", "dep:tower-http", "dep:tokio", "dep:sqlx", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", "dep:leptos_axum"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"dep:sqlx",
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
"dep:leptos_axum",
]
[package.metadata.cargo-all-features]
denylist = [
"axum",
"tower",
"tower-http",
"tokio",
"sqlx",
"leptos_axum",
]
denylist = ["axum", "tower", "tower-http", "tokio", "sqlx", "leptos_axum"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
[package.metadata.leptos]

View File

@@ -1,4 +1,4 @@
use leptos::{web_sys::HtmlInputElement, *};
use leptos::{html::Input, leptos_dom::helpers::location_hash, *};
use storage::TodoSerialized;
use uuid::Uuid;
@@ -143,17 +143,18 @@ pub fn TodoMVC(cx: Scope) -> impl IntoView {
});
// Callback to add a todo on pressing the `Enter` key, if the field isn't empty
let input_ref = NodeRef::<Input>::new(cx);
let add_todo = move |ev: web_sys::KeyboardEvent| {
let target = event_target::<HtmlInputElement>(&ev);
let input = input_ref.get().unwrap();
ev.stop_propagation();
let key_code = ev.key_code();
if key_code == ENTER_KEY {
let title = event_target_value(&ev);
let title = input.value();
let title = title.trim();
if !title.is_empty() {
let new = Todo::new(cx, Uuid::new_v4(), title.to_string());
set_todos.update(|t| t.add(new));
target.set_value("");
input.set_value("");
}
}
};
@@ -211,6 +212,7 @@ pub fn TodoMVC(cx: Scope) -> impl IntoView {
placeholder="What needs to be done?"
autofocus
on:keydown=add_todo
node_ref=input_ref
/>
</header>
<section

View File

@@ -15,7 +15,11 @@ use actix_web::{
};
use futures::{Future, StreamExt};
use http::StatusCode;
use leptos::*;
use leptos::{
leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context,
leptos_server::{server_fn_by_path, Payload},
*,
};
use leptos_meta::*;
use leptos_router::*;
use parking_lot::RwLock;
@@ -93,13 +97,14 @@ impl ResponseOptions {
/// it sets a [StatusCode] of 302 and a [LOCATION](header::LOCATION) header with the provided value.
/// If looking to redirect from the client, `leptos_router::use_navigate()` should be used instead.
pub fn redirect(cx: leptos::Scope, path: &str) {
let response_options = use_context::<ResponseOptions>(cx).unwrap();
response_options.set_status(StatusCode::FOUND);
response_options.insert_header(
header::LOCATION,
header::HeaderValue::from_str(path)
.expect("Failed to create HeaderValue"),
);
if let Some(response_options) = use_context::<ResponseOptions>(cx) {
response_options.set_status(StatusCode::FOUND);
response_options.insert_header(
header::LOCATION,
header::HeaderValue::from_str(path)
.expect("Failed to create HeaderValue"),
);
}
}
/// An Actix [Route](actix_web::Route) that listens for a `POST` request with

View File

@@ -19,7 +19,10 @@ use axum::{
use futures::{Future, SinkExt, Stream, StreamExt};
use http::{header, method::Method, uri::Uri, version::Version, Response};
use hyper::body;
use leptos::*;
use leptos::{
leptos_server::{server_fn_by_path, Payload},
*,
};
use leptos_meta::MetaContext;
use leptos_router::*;
use parking_lot::RwLock;
@@ -91,13 +94,14 @@ impl ResponseOptions {
/// it sets a StatusCode of 302 and a LOCATION header with the provided value.
/// If looking to redirect from the client, `leptos_router::use_navigate()` should be used instead
pub fn redirect(cx: leptos::Scope, path: &str) {
let response_options = use_context::<ResponseOptions>(cx).unwrap();
response_options.set_status(StatusCode::FOUND);
response_options.insert_header(
header::LOCATION,
header::HeaderValue::from_str(path)
.expect("Failed to create HeaderValue"),
);
if let Some(response_options) = use_context::<ResponseOptions>(cx) {
response_options.set_status(StatusCode::FOUND);
response_options.insert_header(
header::LOCATION,
header::HeaderValue::from_str(path)
.expect("Failed to create HeaderValue"),
);
}
}
/// Decomposes an HTTP request into its parts, allowing you to read its headers
@@ -491,7 +495,7 @@ where
};
let (bundle, runtime, scope) =
render_to_stream_with_prefix_undisposed_with_context(
leptos::leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context(
app,
|cx| {
let head = use_context::<MetaContext>(cx)

21
leptos/Makefile.toml Normal file
View File

@@ -0,0 +1,21 @@
[tasks.build-wasm]
clear = true
dependencies = ["build-hydrate", "build-csr"]
[tasks.build-hydrate]
command = "cargo"
args = [
"build",
"--no-default-features",
"--features=hydrate",
"--target=wasm32-unknown-unknown",
]
[tasks.build-csr]
command = "cargo"
args = [
"build",
"--no-default-features",
"--features=csr",
"--target=wasm32-unknown-unknown",
]

View File

@@ -141,16 +141,29 @@
//! # }
//! ```
pub use leptos_config::*;
pub use leptos_config::{self, get_configuration, LeptosOptions};
#[cfg(not(all(
target_arch = "wasm32",
any(feature = "csr", feature = "hydrate")
)))]
pub use leptos_dom::ssr::{self, render_to_string};
pub use leptos_dom::{
self,
wasm_bindgen::{JsCast, UnwrapThrowExt},
*,
self, create_node_ref, debug_warn, document, error, ev,
helpers::{
event_target, event_target_checked, event_target_value,
request_animation_frame, request_idle_callback, set_interval,
set_timeout, window_event_listener,
},
html, log, math, mount_to, mount_to_body, svg, warn, window, Attribute,
Class, Errors, Fragment, HtmlElement, IntoAttribute, IntoClass,
IntoProperty, IntoView, NodeRef, Property, View,
};
pub use leptos_macro::*;
pub use leptos_reactive::*;
pub use leptos_server::{self, *};
pub use tracing;
pub use leptos_server::{
self, create_action, create_multi_action, create_server_action,
create_server_multi_action, Action, MultiAction, ServerFn, ServerFnError,
};
pub use typed_builder;
mod error_boundary;
pub use error_boundary::*;
@@ -161,7 +174,9 @@ pub use show::*;
mod suspense;
pub use suspense::*;
mod transition;
pub use leptos_dom::debug_warn;
#[cfg(debug_assertions)]
#[doc(hidden)]
pub use tracing;
pub use transition::*;
extern crate self as leptos;
@@ -179,9 +194,7 @@ pub type ChildrenFn = Box<dyn Fn(Scope) -> Fragment>;
pub type ChildrenFnMut = Box<dyn FnMut(Scope) -> Fragment>;
/// A type for taking anything that implements [`IntoAttribute`].
/// Very usefull inside components.
///
/// ## Example
/// ```rust
/// use leptos::*;
///

View File

@@ -1,3 +1,5 @@
//! A variety of DOM utility functions.
use crate::{is_server, window};
use std::time::Duration;
use wasm_bindgen::{prelude::Closure, JsCast, JsValue, UnwrapThrowExt};

View File

@@ -1,5 +1,4 @@
pub mod math;
pub mod svg;
//! Exports types for working with HTML elements.
use cfg_if::cfg_if;
@@ -25,7 +24,7 @@ cfg_if! {
use crate::hydration::HydrationKey;
use smallvec::{smallvec, SmallVec};
const HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG: &str =
pub(crate) const HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG: &str =
"`Deref<Target = web_sys::HtmlElement>` and `AsRef<web_sys::HtmlElement>` \
can only be used on web targets. \
This is for the same reason that normal `wasm_bindgen` methods can be used \
@@ -162,6 +161,7 @@ pub struct Custom {
}
impl Custom {
/// Creates a new custom element with the given tag name.
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
let name = name.into();
let id = HydrationCtx::id();
@@ -295,7 +295,7 @@ where
}
impl<El: ElementDescriptor + 'static> HtmlElement<El> {
fn new(cx: Scope, element: El) -> Self {
pub(crate) fn new(cx: Scope, element: El) -> Self {
cfg_if! {
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
Self {
@@ -443,7 +443,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
let waker = Rc::new(RefCell::new(None::<Waker>));
let ready = Rc::new(OnceCell::new());
crate::request_animation_frame({
crate::helpers::request_animation_frame({
let waker = waker.clone();
let ready = ready.clone();

View File

@@ -110,7 +110,7 @@ impl HydrationCtx {
ID.with(|id| *id.borrow_mut() = Default::default());
}
/// Resums hydration from the provided `id`. Usefull for
/// Resumes hydration from the provided `id`. Useful for
/// `Suspense` and other fancy things.
pub fn continue_from(id: HydrationKey) {
ID.with(|i| *i.borrow_mut() = id);

View File

@@ -5,30 +5,30 @@
//! The DOM implementation for `leptos`.
#[doc(hidden)]
#[cfg_attr(debug_assertions, macro_use)]
pub extern crate tracing;
mod components;
mod events;
mod helpers;
#[doc(hidden)]
pub mod helpers;
pub mod html;
mod hydration;
mod logging;
mod macro_helpers;
pub mod math;
mod node_ref;
mod ssr;
pub mod ssr;
pub mod svg;
mod transparent;
use cfg_if::cfg_if;
pub use components::*;
pub use events::typed as ev;
#[cfg(all(target_arch = "wasm32", feature = "web"))]
use events::{add_event_listener, add_event_listener_undelegated};
pub use helpers::*;
pub use html::*;
pub use html::HtmlElement;
use html::{AnyElement, ElementDescriptor};
pub use hydration::{HydrationCtx, HydrationKey};
pub use js_sys;
use leptos_reactive::Scope;
pub use logging::*;
pub use macro_helpers::*;
@@ -37,17 +37,13 @@ pub use node_ref::*;
use once_cell::unsync::Lazy as LazyCell;
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
use smallvec::SmallVec;
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
pub use ssr::*;
use std::{borrow::Cow, fmt};
#[cfg(all(target_arch = "wasm32", feature = "web"))]
use std::{cell::RefCell, rc::Rc};
pub use transparent::*;
pub use wasm_bindgen;
#[cfg(all(target_arch = "wasm32", feature = "web"))]
use wasm_bindgen::JsCast;
use wasm_bindgen::UnwrapThrowExt;
pub use web_sys;
#[cfg(all(target_arch = "wasm32", feature = "web"))]
thread_local! {
@@ -690,7 +686,7 @@ enum MountKind<'a> {
Append(&'a web_sys::Node),
}
/// Runs the provided closure and mounts the result to eht `<body>`.
/// Runs the provided closure and mounts the result to the `<body>`.
pub fn mount_to_body<F, N>(f: F)
where
F: FnOnce(Scope) -> N + 'static,

View File

@@ -6,7 +6,8 @@ use wasm_bindgen::UnwrapThrowExt;
/// Represents the different possible values an attribute node could have.
///
/// This mostly exists for the [`view`](https://docs.rs/leptos_macro/latest/leptos_macro/macro.view.html)
/// macros use. You usually won't need to interact with it directly.
/// macros use. You usually won't need to interact with it directly, but it can be useful for defining
/// permissive APIs for certain components.
#[derive(Clone)]
pub enum Attribute {
/// A plain string value.
@@ -103,7 +104,7 @@ 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>]
/// Helper function for dealing with `Box<dyn IntoAttribute>`.
fn into_attribute_boxed(self: Box<Self>, cx: Scope) -> Attribute;
}

View File

@@ -7,7 +7,8 @@ use wasm_bindgen::UnwrapThrowExt;
/// in [`Element.classList`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList).
///
/// This mostly exists for the [`view`](https://docs.rs/leptos_macro/latest/leptos_macro/macro.view.html)
/// macros use. You usually won't need to interact with it directly.
/// macros use. You usually won't need to interact with it directly, but it can be useful for defining
/// permissive APIs for certain components.
pub enum Class {
/// Whether the class is present.
Value(bool),

View File

@@ -7,7 +7,8 @@ use wasm_bindgen::UnwrapThrowExt;
/// allowing you to do fine-grained updates to single fields.
///
/// This mostly exists for the [`view`](https://docs.rs/leptos_macro/latest/leptos_macro/macro.view.html)
/// macros use. You usually won't need to interact with it directly.
/// macros use. You usually won't need to interact with it directly, but it can be useful for defining
/// permissive APIs for certain components.
pub enum Property {
/// A static JavaScript value.
Value(JsValue),

View File

@@ -1,4 +1,4 @@
//! MathML elements.
//! Exports types for working with MathML elements.
use super::{ElementDescriptor, HtmlElement};
use crate::HydrationCtx;
@@ -10,7 +10,7 @@ cfg_if! {
use once_cell::unsync::Lazy as LazyCell;
use wasm_bindgen::JsCast;
} else {
use super::{HydrationKey, HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG};
use super::{HydrationKey, html::HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG};
}
}

View File

@@ -1,15 +1,18 @@
use crate::{ElementDescriptor, HtmlElement};
use crate::{html::ElementDescriptor, HtmlElement};
use leptos_reactive::{create_effect, create_rw_signal, RwSignal, Scope};
use std::cell::Cell;
/// Contains a shared reference to a DOM node creating while using the `view`
/// Contains a shared reference to a DOM node created while using the `view`
/// macro to create your UI.
///
/// ```
/// # use leptos::*;
///
/// use leptos::html::Input;
///
/// #[component]
/// pub fn MyComponent(cx: Scope) -> impl IntoView {
/// let input_ref = NodeRef::<Input>::new(cx);
/// let input_ref = create_node_ref::<Input>(cx);
///
/// let on_click = move |_| {
/// let node =
@@ -34,8 +37,46 @@ pub struct NodeRef<T: ElementDescriptor + 'static>(
RwSignal<Option<HtmlElement<T>>>,
);
/// Creates a shared reference to a DOM node created while using the `view`
/// macro to create your UI.
///
/// ```
/// # use leptos::*;
///
/// use leptos::html::Input;
///
/// #[component]
/// pub fn MyComponent(cx: Scope) -> impl IntoView {
/// let input_ref = create_node_ref::<Input>(cx);
///
/// let on_click = move |_| {
/// let node =
/// input_ref.get().expect("input_ref should be loaded by now");
/// // `node` is strongly typed
/// // it is dereferenced to an `HtmlInputElement` automatically
/// log!("value is {:?}", node.value())
/// };
///
/// view! {
/// cx,
/// <div>
/// // `node_ref` loads the input
/// <input _ref=input_ref type="text"/>
/// // the button consumes it
/// <button on:click=on_click>"Click me"</button>
/// </div>
/// }
/// }
/// ```
pub fn create_node_ref<T: ElementDescriptor + 'static>(
cx: Scope,
) -> NodeRef<T> {
NodeRef(create_rw_signal(cx, None))
}
impl<T: ElementDescriptor + 'static> NodeRef<T> {
/// Creates an empty reference.
#[deprecated = "Use `create_node_ref` instead of `NodeRef::new()`."]
pub fn new(cx: Scope) -> Self {
Self(create_rw_signal(cx, None))
}

View File

@@ -1,5 +1,7 @@
#![cfg(not(all(target_arch = "wasm32", feature = "web")))]
//! Server-side HTML rendering utilities.
use crate::{CoreComponent, HydrationCtx, IntoView, View};
use cfg_if::cfg_if;
use futures::{stream::FuturesUnordered, Stream, StreamExt};

View File

@@ -1,8 +1,8 @@
//! SVG elements.
//! Exports types for working with SVG elements.
use super::{ElementDescriptor, HtmlElement};
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
use super::{HydrationKey, HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG};
use super::{html::HTML_ELEMENT_DEREF_UNIMPLEMENTED_MSG, HydrationKey};
use super::{ElementDescriptor, HtmlElement};
use crate::HydrationCtx;
use leptos_reactive::Scope;
#[cfg(all(target_arch = "wasm32", feature = "web"))]

View File

@@ -178,7 +178,7 @@ impl ToTokens for Model {
}
} else {
quote! {
::leptos::Component::new(
::leptos::leptos_dom::Component::new(
stringify!(#name),
move |cx| {
#tracing_guard_expr

View File

@@ -8,7 +8,7 @@ use proc_macro::TokenStream;
use proc_macro2::TokenTree;
use quote::ToTokens;
use server::server_macro_impl;
use syn::{parse_macro_input, DeriveInput};
use syn::parse_macro_input;
use syn_rsx::{parse, NodeElement};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -34,7 +34,6 @@ mod params;
mod view;
use view::render_view;
mod component;
mod props;
mod server;
/// The `view` macro uses RSX (like JSX, but Rust!) It follows most of the
@@ -121,7 +120,7 @@ mod server;
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
/// view! {
/// cx,
/// <button on:click=|ev: web_sys::MouseEvent| {
/// <button on:click=|ev| {
/// log::debug!("click event: {ev:#?}");
/// }>
/// "Click me"
@@ -260,9 +259,9 @@ mod server;
///
/// // create event handlers for our buttons
/// // note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures
/// let clear = move |_ev: web_sys::MouseEvent| set_value(0);
/// let decrement = move |_ev: web_sys::MouseEvent| set_value.update(|value| *value -= 1);
/// let increment = move |_ev: web_sys::MouseEvent| set_value.update(|value| *value += 1);
/// let clear = move |_ev| set_value(0);
/// let decrement = move |_ev| set_value.update(|value| *value -= 1);
/// let increment = move |_ev| set_value.update(|value| *value += 1);
///
/// // this JSX is compiled to an HTML template string for performance
/// view! {
@@ -468,6 +467,8 @@ pub fn view(tokens: TokenStream) -> TokenStream {
/// ```compile_error
/// // ❌ This won't work.
/// # use leptos::*;
/// use leptos::html::Div;
///
/// #[component]
/// fn MyComponent<T: Fn() -> HtmlElement<Div>>(cx: Scope, render_prop: T) -> impl IntoView {
/// todo!()
@@ -477,6 +478,8 @@ pub fn view(tokens: TokenStream) -> TokenStream {
/// ```
/// // ✅ Do this instead
/// # use leptos::*;
/// use leptos::html::Div;
///
/// #[component]
/// fn MyComponent<T>(cx: Scope, render_prop: T) -> impl IntoView
/// where
@@ -629,7 +632,9 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
/// - **Return types must be [Serializable](leptos_reactive::Serializable).**
/// This should be fairly obvious: we have to serialize arguments to send them to the server, and we
/// need to deserialize the result to return it to the client.
/// - **Arguments must be implement [serde::Serialize].** They are serialized as an `application/x-www-form-urlencoded`
/// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)
/// and [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html).**
/// They are serialized as an `application/x-www-form-urlencoded`
/// form data using [`serde_urlencoded`](https://docs.rs/serde_urlencoded/latest/serde_urlencoded/) or as `application/cbor`
/// using [`cbor`](https://docs.rs/cbor/latest/cbor/).
/// - **The [Scope](leptos_reactive::Scope) comes from the server.** Optionally, the first argument of a server function
@@ -643,16 +648,8 @@ pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
}
}
#[proc_macro_derive(Props, attributes(builder))]
pub fn derive_prop(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
props::impl_derive_prop(&input)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
// Derive Params trait for routing
/// Derives a trait that parses a map of string keys and values into a typed
/// data structure, e.g., for route params.
#[proc_macro_derive(Params, attributes(params))]
pub fn params_derive(
input: proc_macro::TokenStream,
@@ -662,9 +659,7 @@ pub fn params_derive(
}
pub(crate) fn is_component_node(node: &NodeElement) -> bool {
let name = node.name.to_string();
let first_char = name.chars().next();
first_char
.map(|first_char| first_char.is_ascii_uppercase())
.unwrap_or(false)
node.name
.to_string()
.starts_with(|c: char| c.is_ascii_uppercase())
}

File diff suppressed because it is too large Load Diff

View File

@@ -35,8 +35,8 @@ pub fn server_macro_impl(
} = syn::parse::<ServerFnName>(args)?;
let prefix = prefix.unwrap_or_else(|| Literal::string(""));
let encoding = match encoding {
Encoding::Cbor => quote! { ::leptos::Encoding::Cbor },
Encoding::Url => quote! { ::leptos::Encoding::Url },
Encoding::Cbor => quote! { ::leptos::leptos_server::Encoding::Cbor },
Encoding::Url => quote! { ::leptos::leptos_server::Encoding::Url },
};
let body = syn::parse::<ServerFnBody>(s.into())?;
@@ -166,7 +166,7 @@ pub fn server_macro_impl(
#url
}
fn encoding() -> ::leptos::Encoding {
fn encoding() -> ::leptos::leptos_server::Encoding {
#encoding
}
@@ -192,7 +192,7 @@ pub fn server_macro_impl(
#vis async fn #fn_name(#(#fn_args_2),*) #output_arrow #return_ty {
let prefix = #struct_name::prefix().to_string();
let url = prefix + "/" + #struct_name::url();
::leptos::call_server_fn(&url, #struct_name { #(#field_names_5),* }, #encoding).await
::leptos::leptos_server::call_server_fn(&url, #struct_name { #(#field_names_5),* }, #encoding).await
}
})
}

View File

@@ -200,7 +200,7 @@ fn root_node_to_tokens_ssr(
Node::Text(node) => {
let value = node.value.as_ref();
quote! {
leptos::text(#value)
leptos::leptos_dom::html::text(#value)
}
}
Node::Block(node) => {
@@ -291,11 +291,11 @@ fn root_element_to_tokens_ssr(
};
let full_name = if is_custom_element {
quote! {
leptos::leptos_dom::Custom::new(#tag_name)
leptos::leptos_dom::html::Custom::new(#tag_name)
}
} else {
quote! {
leptos::leptos_dom::#typed_element_name::default()
leptos::leptos_dom::html::#typed_element_name::default()
}
};
quote! {
@@ -347,9 +347,9 @@ fn element_to_tokens_ssr(
// insert hydration ID
let hydration_id = if is_root {
quote! { leptos::HydrationCtx::peek(), }
quote! { leptos::leptos_dom::HydrationCtx::peek(), }
} else {
quote! { leptos::HydrationCtx::id(), }
quote! { leptos::leptos_dom::HydrationCtx::id(), }
};
match node
.attributes
@@ -456,7 +456,7 @@ fn attribute_to_tokens_ssr<'a>(
} else if name.strip_prefix("on:").is_some() {
let (event_type, handler) = event_from_attribute_node(node, false);
exprs_for_compiler.push(quote! {
leptos::ssr_event_listener(#event_type, #handler);
leptos::leptos_dom::helpers::ssr_event_listener(#event_type, #handler);
})
} else if name.strip_prefix("prop:").is_some()
|| name.strip_prefix("class:").is_some()
@@ -483,7 +483,7 @@ fn attribute_to_tokens_ssr<'a>(
holes.push(quote! {
&{#value}.into_attribute(#cx)
.as_nameless_value_string()
.map(|a| format!("{}=\"{}\"", #name, leptos::escape_attr(&a)))
.map(|a| format!("{}=\"{}\"", #name, leptos::leptos_dom::ssr::escape_attr(&a)))
.unwrap_or_default(),
})
}
@@ -607,7 +607,7 @@ fn set_class_attribute_ssr(
let value = value.as_ref();
holes.push(quote! {
&(cx, #value).into_attribute(#cx).as_nameless_value_string()
.map(|a| leptos::escape_attr(&a).to_string())
.map(|a| leptos::leptos_dom::ssr::escape_attr(&a).to_string())
.unwrap_or_default(),
});
}
@@ -682,7 +682,7 @@ fn node_to_tokens(
Node::Text(node) => {
let value = node.value.as_ref();
quote! {
leptos::text(#value)
leptos::leptos_dom::html::text(#value)
}
}
Node::Block(node) => {
@@ -708,7 +708,7 @@ fn element_to_tokens(
let tag = node.name.to_string();
let name = if is_custom_element(&tag) {
let name = node.name.to_string();
quote! { leptos::leptos_dom::custom(#cx, leptos::leptos_dom::Custom::new(#name)) }
quote! { leptos::leptos_dom::html::custom(#cx, leptos::leptos_dom::html::Custom::new(#name)) }
} else if is_svg_element(&tag) {
let name = &node.name;
parent_type = TagType::Svg;
@@ -725,10 +725,12 @@ fn element_to_tokens(
/* proc_macro_error::emit_warning!(name.span(), "The view macro is assuming this is an HTML element, \
but it is ambiguous; if it is an SVG or MathML element, prefix with svg:: or math::"); */
quote! {
leptos::leptos_dom::#name(#cx)
leptos::leptos_dom::html::#name(#cx)
}
}
TagType::Html => quote! { leptos::leptos_dom::#name(#cx) },
TagType::Html => {
quote! { leptos::leptos_dom::html::#name(#cx) }
}
TagType::Svg => quote! { leptos::leptos_dom::svg::#name(#cx) },
TagType::Math => {
quote! { leptos::leptos_dom::math::#name(#cx) }
@@ -737,7 +739,7 @@ fn element_to_tokens(
} else {
let name = &node.name;
parent_type = TagType::Html;
quote! { leptos::leptos_dom::#name(#cx) }
quote! { leptos::leptos_dom::html::#name(#cx) }
};
let attrs = node.attributes.iter().filter_map(|node| {
if let Node::Attribute(node) = node {
@@ -830,7 +832,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
.expect("couldn't parse event name");
let event_type = if is_custom {
quote! { Custom::new(#name) }
quote! { leptos::leptos_dom::leptos_dom::events::Custom::new(#name) }
} else {
event_type
};
@@ -885,9 +887,9 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
} else {
quote! { undelegated }
};
quote! { ::leptos::ev::#undelegated(::leptos::ev::#event_type) }
quote! { ::leptos::leptos_dom::ev::#undelegated(::leptos::leptos_dom::ev::#event_type) }
} else {
quote! { ::leptos::ev::#event_type }
quote! { ::leptos::leptos_dom::ev::#event_type }
};
quote! {
@@ -1105,9 +1107,9 @@ fn event_from_attribute_node(
.expect("couldn't parse event name");
let event_type = if force_undelegated || name_undelegated {
quote! { ::leptos::ev::undelegated(::leptos::ev::#event_type) }
quote! { ::leptos::leptos_dom::ev::undelegated(::leptos::leptos_dom::ev::#event_type) }
} else {
quote! { ::leptos::ev::#event_type }
quote! { ::leptos::leptos_dom::ev::#event_type }
};
(event_type, handler)
}

View File

@@ -67,7 +67,7 @@
//! ```
#[cfg_attr(debug_assertions, macro_use)]
pub extern crate tracing;
extern crate tracing;
mod context;
mod effect;

View File

@@ -373,7 +373,7 @@ where
/// Calling [WriteSignal::update] will mutate the signals value in place,
/// and notify all subscribers that the signals value has changed.
///
/// `ReadSignal` implements [Fn], such that `set_value(new_value)` is equivalent to
/// `WriteSignal` implements [Fn], such that `set_value(new_value)` is equivalent to
/// `set_value.update(|value| *value = new_value)`.
///
/// `WriteSignal` is [Copy] and `'static`, so it can very easily moved into closures

View File

@@ -1,10 +1,10 @@
//#[cfg(not(feature = "stable"))]
#[cfg(not(feature = "stable"))]
use leptos_reactive::{
create_isomorphic_effect, create_runtime, create_scope, create_signal,
UntrackedGettableSignal, UntrackedSettableSignal,
};
//#[cfg(not(feature = "stable"))]
#[cfg(not(feature = "stable"))]
#[test]
fn untracked_set_doesnt_trigger_effect() {
use std::{cell::RefCell, rc::Rc};
@@ -36,6 +36,7 @@ fn untracked_set_doesnt_trigger_effect() {
.dispose()
}
#[cfg(not(feature = "stable"))]
#[test]
fn untracked_get_doesnt_trigger_effect() {
use std::{cell::RefCell, rc::Rc};

View File

@@ -13,6 +13,7 @@ leptos_dom = { workspace = true }
leptos_reactive = { workspace = true }
form_urlencoded = "1"
gloo-net = "0.2"
js-sys = "0.3"
lazy_static = "1"
serde = { version = "1", features = ["derive"] }
serde_urlencoded = "0.7"

View File

@@ -78,7 +78,6 @@
//! can be a Leptos [Scope](leptos_reactive::Scope). This scope can be used to inject dependencies like the HTTP request
//! or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client.
pub use form_urlencoded;
use leptos_reactive::*;
use proc_macro2::{Literal, TokenStream};
use quote::TokenStreamExt;
@@ -384,7 +383,7 @@ where
T: serde::Serialize + serde::de::DeserializeOwned + Sized,
{
use ciborium::ser::into_writer;
use leptos_dom::js_sys::Uint8Array;
use js_sys::Uint8Array;
use serde_json::Deserializer as JSONDeserializer;
#[derive(Debug)]

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.1.3"
version = "0.2.0-alpha"
edition = "2021"
authors = ["Greg Johnston"]
license = "MIT"
@@ -12,6 +12,7 @@ cfg-if = "1"
leptos = { workspace = true }
tracing = "0.1"
typed-builder = "0.12"
wasm-bindgen = "0.2"
[dependencies.web-sys]
version = "0.3"

View File

@@ -45,13 +45,18 @@
//! which mode your app is operating in.
use cfg_if::cfg_if;
use leptos::{leptos_dom::debug_warn, *};
use leptos::{
leptos_dom::{debug_warn, html::AnyElement},
*,
};
use std::{
cell::{Cell, RefCell},
collections::HashMap,
fmt::Debug,
rc::Rc,
};
#[cfg(any(feature = "csr", feature = "hydrate"))]
use wasm_bindgen::{JsCast, UnwrapThrowExt};
mod body;
mod html;
@@ -255,6 +260,8 @@ impl MetaContext {
/// # }
/// ```
pub fn dehydrate(&self) -> String {
use leptos::leptos_dom::HydrationCtx;
let prev_key = HydrationCtx::peek();
let mut tags = String::new();

View File

@@ -85,7 +85,7 @@ pub fn Link(
let next_id = meta.tags.get_next_id();
let id = id.unwrap_or_else(|| format!("leptos-link-{}", next_id.0));
let builder_el = leptos::link(cx)
let builder_el = leptos::leptos_dom::html::link(cx)
.attr("id", &id)
.attr("as_", as_)
.attr("crossorigin", crossorigin)

View File

@@ -41,7 +41,7 @@ pub fn Meta(
let next_id = meta.tags.get_next_id();
let id = format!("leptos-link-{}", next_id.0);
let builder_el = leptos::meta(cx)
let builder_el = leptos::leptos_dom::html::meta(cx)
.attr("charset", move || charset.as_ref().map(|v| v.get()))
.attr("name", move || name.as_ref().map(|v| v.get()))
.attr("http-equiv", move || http_equiv.as_ref().map(|v| v.get()))

View File

@@ -67,7 +67,7 @@ pub fn Script(
let next_id = meta.tags.get_next_id();
let id = id.unwrap_or_else(|| format!("leptos-link-{}", next_id.0));
let builder_el = leptos::script(cx)
let builder_el = leptos::leptos_dom::html::script(cx)
.attr("id", &id)
.attr("async", async_)
.attr("crossorigin", crossorigin)

View File

@@ -46,7 +46,7 @@ pub fn Style(
let next_id = meta.tags.get_next_id();
let id = id.unwrap_or_else(|| format!("leptos-link-{}", next_id.0));
let builder_el = leptos::style(cx)
let builder_el = leptos::leptos_dom::html::style(cx)
.attr("id", &id)
.attr("media", media)
.attr("nonce", nonce)

View File

@@ -2,6 +2,8 @@ use crate::{use_head, TextProp};
use cfg_if::cfg_if;
use leptos::*;
use std::{cell::RefCell, rc::Rc};
#[cfg(any(feature = "csr", feature = "hydrate"))]
use wasm_bindgen::{JsCast, UnwrapThrowExt};
/// Contains the current state of the document's `<title>`.
#[derive(Clone, Default)]

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.1.3"
version = "0.2.0-alpha"
edition = "2021"
authors = ["Greg Johnston"]
license = "MIT"

View File

@@ -1,7 +1,7 @@
use crate::{use_navigate, use_resolved_path, ToHref};
use leptos::*;
use std::{error::Error, rc::Rc};
use wasm_bindgen::JsCast;
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use wasm_bindgen_futures::JsFuture;
type OnFormData = Rc<dyn Fn(&web_sys::FormData)>;
@@ -58,7 +58,7 @@ where
on_response: Option<OnResponse>,
class: Option<Attribute>,
children: Children,
) -> HtmlElement<Form> {
) -> HtmlElement<html::Form> {
let action_version = version;
let on_submit = move |ev: web_sys::SubmitEvent| {
if ev.default_prevented() {

View File

@@ -76,7 +76,7 @@ where
replace: bool,
class: Option<AttributeValue>,
children: Children,
) -> HtmlElement<A> {
) -> HtmlElement<leptos::html::A> {
let location = use_location(cx);
let is_active = create_memo(cx, move |_| match href.get() {
None => false,

View File

@@ -1,5 +1,5 @@
use crate::use_route;
use leptos::*;
use leptos::{leptos_dom::HydrationCtx, *};
use std::{cell::Cell, rc::Rc};
/// Displays the child route nested in a parent route, allowing you to control exactly where
@@ -37,5 +37,5 @@ pub fn Outlet(cx: Scope) -> impl IntoView {
}
});
leptos::DynChild::new_with_id(id, move || outlet.get())
leptos::leptos_dom::DynChild::new_with_id(id, move || outlet.get())
}

View File

@@ -2,7 +2,7 @@ use crate::{
matching::{resolve_path, PathMatch, RouteDefinition, RouteMatch},
ParamsMap, RouterContext,
};
use leptos::*;
use leptos::{leptos_dom::Transparent, *};
use std::{
cell::{Cell, RefCell},
rc::Rc,

View File

@@ -166,7 +166,7 @@ impl RouterContext {
// handle all click events on anchor tags
#[cfg(not(feature = "ssr"))]
leptos_dom::window_event_listener("click", {
leptos::window_event_listener("click", {
let inner = Rc::clone(&inner);
move |ev| inner.clone().handle_anchor_click(ev)
});
@@ -340,7 +340,8 @@ impl RouterContextInner {
// let browser handle this event if it leaves our domain
// or our base path
if url.origin != leptos_dom::location().origin().unwrap_or_default()
if url.origin
!= leptos_dom::helpers::location().origin().unwrap_or_default()
|| (!self.base_path.is_empty()
&& !path_name.is_empty()
&& !path_name
@@ -351,22 +352,24 @@ impl RouterContextInner {
}
let to = path_name + &unescape(&url.search) + &unescape(&url.hash);
let state = get_property(a.unchecked_ref(), "state").ok().and_then(
|value| {
if value == wasm_bindgen::JsValue::UNDEFINED {
None
} else {
Some(value)
}
},
);
let state =
leptos_dom::helpers::get_property(a.unchecked_ref(), "state")
.ok()
.and_then(|value| {
if value == wasm_bindgen::JsValue::UNDEFINED {
None
} else {
Some(value)
}
});
ev.prevent_default();
let replace = get_property(a.unchecked_ref(), "replace")
.ok()
.and_then(|value| value.as_bool())
.unwrap_or(false);
let replace =
leptos_dom::helpers::get_property(a.unchecked_ref(), "replace")
.ok()
.and_then(|value| value.as_bool())
.unwrap_or(false);
if let Err(e) = self.navigate_from_route(
&to,
&NavigateOptions {

View File

@@ -5,7 +5,7 @@ use crate::{
},
RouteContext, RouterContext,
};
use leptos::*;
use leptos::{leptos_dom::HydrationCtx, *};
use std::{
cell::{Cell, RefCell},
cmp::Reverse,
@@ -203,8 +203,7 @@ pub fn Routes(
})
});
//HydrationCtx::continue_from(id_before);
leptos::DynChild::new_with_id(id, move || root.get())
leptos::leptos_dom::DynChild::new_with_id(id, move || root.get())
}
#[derive(Clone, Debug, PartialEq)]

View File

@@ -1,5 +1,6 @@
use leptos::*;
use std::rc::Rc;
use wasm_bindgen::UnwrapThrowExt;
mod location;
mod params;
@@ -35,7 +36,7 @@ pub struct BrowserIntegration {}
impl BrowserIntegration {
fn current() -> LocationChange {
let loc = leptos_dom::location();
let loc = leptos_dom::helpers::location();
LocationChange {
value: loc.pathname().unwrap_or_default()
+ &loc.search().unwrap_or_default()
@@ -53,7 +54,7 @@ impl History for BrowserIntegration {
let (location, set_location) = create_signal(cx, Self::current());
leptos_dom::window_event_listener("popstate", move |_| {
leptos::window_event_listener("popstate", move |_| {
let router = use_context::<RouterContext>(cx);
if let Some(router) = router {
let change = Self::current();
@@ -98,7 +99,7 @@ impl History for BrowserIntegration {
.unwrap_throw();
}
// scroll to el
if let Ok(hash) = leptos_dom::location().hash() {
if let Ok(hash) = leptos_dom::helpers::location().hash() {
if !hash.is_empty() {
let hash = js_sys::decode_uri(&hash[1..])
.ok()

View File

@@ -1,4 +1,4 @@
use leptos::wasm_bindgen::JsValue;
use wasm_bindgen::JsValue;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct State(pub Option<JsValue>);