mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 11:21:55 -05:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c317bc1e5c | ||
|
|
b3a4c95dad | ||
|
|
de44b1f91f | ||
|
|
689022661d | ||
|
|
905d46a09d | ||
|
|
5585f20940 | ||
|
|
5c3ed3f018 | ||
|
|
03cabf6ea3 | ||
|
|
2798dc455f | ||
|
|
db20be5576 | ||
|
|
495862e9f9 | ||
|
|
2ca1c51fdc | ||
|
|
70e1ad41e2 | ||
|
|
53ec7ed272 | ||
|
|
d98a577740 | ||
|
|
fd834f48c2 | ||
|
|
7be65a37c6 | ||
|
|
3b5e2d86fb | ||
|
|
716b9fb50b | ||
|
|
006ca13797 | ||
|
|
6e008343c8 | ||
|
|
2ca24883ac | ||
|
|
4a43983f4e | ||
|
|
d9e83121c1 | ||
|
|
f5b4b97c9b | ||
|
|
bcfa430a40 | ||
|
|
7c51815cf5 | ||
|
|
fee2fb953b | ||
|
|
8ecb7f59c4 | ||
|
|
b85cb9fb3b | ||
|
|
a631c5ca1c | ||
|
|
bee9bd8f67 | ||
|
|
8d3874f8a9 | ||
|
|
bade16d227 | ||
|
|
e0a132bde3 | ||
|
|
4d7e1f4d26 | ||
|
|
700eee6604 | ||
|
|
694ed61e4c | ||
|
|
d7330097ba | ||
|
|
c65a3a6ca3 | ||
|
|
793c191619 | ||
|
|
6c3e2fe53e | ||
|
|
08c419e3ee | ||
|
|
736f4185b5 | ||
|
|
9cc0fc8c49 |
21
.github/workflows/run-cargo-make-task.yml
vendored
21
.github/workflows/run-cargo-make-task.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Setup environment
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -95,14 +95,17 @@ jobs:
|
||||
|
||||
- name: Maybe install playwright browser dependencies
|
||||
run: |
|
||||
playwright_count=$(find ${{inputs.directory}} -name playwright.config.ts | wc -l)
|
||||
if [ $playwright_count -eq 1 ]; then
|
||||
echo playwright required
|
||||
sudo apt-get update
|
||||
sudo apt-get install libegl1 libvpx7 libevent-2.1-7 libopus0 libopengl0 libwoff1 libharfbuzz-icu0 libgstreamer-plugins-base1.0-0 libgstreamer-gl1.0-0 libhyphen0 libmanette-0.2-0 libgles2 gstreamer1.0-libav
|
||||
else
|
||||
echo playwright is not required
|
||||
fi
|
||||
for pw_path in $(find ${{inputs.directory}} -name playwright.config.ts)
|
||||
do
|
||||
pw_dir=$(dirname $pw_path)
|
||||
if [ ! -v $pw_dir ]; then
|
||||
echo "Playwright required in $pw_dir"
|
||||
cd $pw_dir
|
||||
pnpm dlx playwright install --with-deps
|
||||
else
|
||||
echo Playwright is not required
|
||||
fi
|
||||
done
|
||||
|
||||
# Run Cargo Make Task
|
||||
- name: ${{ inputs.cargo_make_task }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,3 +9,5 @@ Cargo.lock
|
||||
.idea
|
||||
.direnv
|
||||
.envrc
|
||||
|
||||
.vscode
|
||||
28
Cargo.toml
28
Cargo.toml
@@ -26,22 +26,22 @@ members = [
|
||||
exclude = ["benchmarks", "examples"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.5.0"
|
||||
version = "0.5.0-beta2"
|
||||
|
||||
[workspace.dependencies]
|
||||
leptos = { path = "./leptos", version = "0.5.0" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.5.0" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.5.0" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.5.0" }
|
||||
leptos_reactive = { path = "./leptos_reactive", version = "0.5.0" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.5.0" }
|
||||
server_fn = { path = "./server_fn", version = "0.5.0" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.5.0" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.5.0" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.5.0" }
|
||||
leptos_router = { path = "./router", version = "0.5.0" }
|
||||
leptos_meta = { path = "./meta", version = "0.5.0" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.5.0" }
|
||||
leptos = { path = "./leptos", version = "0.5.0-beta2" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.5.0-beta2" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.5.0-beta2" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.5.0-beta2" }
|
||||
leptos_reactive = { path = "./leptos_reactive", version = "0.5.0-beta2" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.5.0-beta2" }
|
||||
server_fn = { path = "./server_fn", version = "0.5.0-beta2" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.5.0-beta2" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.5.0-beta2" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.5.0-beta2" }
|
||||
leptos_router = { path = "./router", version = "0.5.0-beta2" }
|
||||
leptos_meta = { path = "./meta", version = "0.5.0-beta2" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.5.0-beta2" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
13
SECURITY.md
Normal file
13
SECURITY.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a suspected security issue, please contact security@leptos.dev rather than opening
|
||||
a public issue.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The most-recently-released version of the library is supported with security updates.
|
||||
For example, if a security issue is discovered that affects 0.3.2 and all later releases,
|
||||
a 0.4.x patch will be released but a new 0.3.x patch release will not be made. You should
|
||||
plan to update to the latest version to receive any new features or bugfixes of any kind.
|
||||
@@ -65,7 +65,7 @@ And add a simple “Hello, world!” to your `main.rs`
|
||||
use leptos::*;
|
||||
|
||||
fn main() {
|
||||
mount_to_body(|| view! { <p>"Hello, world!"</p> })
|
||||
mount_to_body(|| view! { <p>"Hello, world!"</p> })
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -367,7 +367,6 @@ fn GlobalStateInput() -> impl IntoView {
|
||||
// that we created in the other component
|
||||
// neither of them will cause the other to rerun
|
||||
let (name, set_name) = create_slice(
|
||||
|
||||
// we take a slice *from* `state`
|
||||
state,
|
||||
// our getter returns a "slice" of the data
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
- [Error Handling](./view/07_errors.md)
|
||||
- [Parent-Child Communication](./view/08_parent_child.md)
|
||||
- [Passing Children to Components](./view/09_component_children.md)
|
||||
- [No Macros: The View Builder Syntax](./view/builder.md)
|
||||
- [Reactivity](./reactivity/README.md)
|
||||
- [Working with Signals](./reactivity/working_with_signals.md)
|
||||
- [Responding to Changes with `create_effect`](./reactivity/14_create_effect.md)
|
||||
|
||||
@@ -235,9 +235,9 @@ At the very most, you might consider memoizing the final node before running som
|
||||
|
||||
```rust
|
||||
let text = create_memo(move |_| {
|
||||
d()
|
||||
d()
|
||||
});
|
||||
create_effect(move |_| {
|
||||
engrave_text_into_bar_of_gold(&text());
|
||||
engrave_text_into_bar_of_gold(&text());
|
||||
});
|
||||
```
|
||||
|
||||
@@ -18,7 +18,7 @@ let async_data = create_resource(
|
||||
count,
|
||||
// every time `count` changes, this will run
|
||||
|value| async move {
|
||||
log!("loading data from API");
|
||||
logging::log!("loading data from API");
|
||||
load_data(value).await
|
||||
},
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ In the previous chapter, we showed how you can create a simple loading screen to
|
||||
|
||||
```rust
|
||||
let (count, set_count) = create_signal(0);
|
||||
let a = create_resource(count, |count| async move { load_a(count).await });
|
||||
let once = create_resource(count, |count| async move { load_a(count).await });
|
||||
|
||||
view! {
|
||||
<h1>"My Data"</h1>
|
||||
|
||||
@@ -54,7 +54,7 @@ RUN cargo leptos build --release -vv
|
||||
|
||||
FROM rustlang/rust:nightly-bullseye as runner
|
||||
# Copy the server binary to the /app directory
|
||||
COPY --from=builder /app/target/server/release/leptos_website /app/
|
||||
COPY --from=builder /app/target/server/release/leptos_start /app/
|
||||
# /target/site contains our JS/WASM/CSS, etc.
|
||||
COPY --from=builder /app/target/site /app/site
|
||||
# Copy Cargo.toml if it’s needed at runtime
|
||||
@@ -68,7 +68,7 @@ ENV LEPTOS_SITE_ADDR="0.0.0.0:8080"
|
||||
ENV LEPTOS_SITE_ROOT="site"
|
||||
EXPOSE 8080
|
||||
# Run the server
|
||||
CMD ["/app/leptos_website"]
|
||||
CMD ["/app/leptos_start"]
|
||||
```
|
||||
|
||||
> Read more: [`gnu` and `musl` build files for Leptos apps](https://github.com/leptos-rs/leptos/issues/1152#issuecomment-1634916088).
|
||||
|
||||
@@ -50,7 +50,6 @@ If you want to really understand the issue here, it may help to look at the expa
|
||||
|
||||
```rust
|
||||
Suspense(
|
||||
|
||||
::leptos::component_props_builder(&Suspense)
|
||||
.fallback(|| ())
|
||||
.children({
|
||||
@@ -61,7 +60,6 @@ Suspense(
|
||||
leptos::Fragment::lazy(|| {
|
||||
vec![
|
||||
(Show(
|
||||
|
||||
::leptos::component_props_builder(&Show)
|
||||
.when(|| true)
|
||||
// but fallback is moved into Show here
|
||||
|
||||
@@ -50,7 +50,6 @@ let (use_last, set_use_last) = create_signal(true);
|
||||
// any time one of the source signals changes
|
||||
create_effect(move |_| {
|
||||
log(
|
||||
|
||||
if use_last() {
|
||||
format!("{} {}", first(), last())
|
||||
} else {
|
||||
@@ -122,7 +121,6 @@ Like `create_resource`, `watch` takes a first argument, which is reactively trac
|
||||
let (num, set_num) = create_signal(0);
|
||||
|
||||
let stop = watch(
|
||||
|
||||
move || num.get(),
|
||||
move |num, prev_num, _| {
|
||||
log::debug!("Number: {}; Prev: {:?}", num, prev_num);
|
||||
|
||||
@@ -20,7 +20,7 @@ let text = move || if count_is_odd() {
|
||||
// an effect automatically tracks the signals it depends on
|
||||
// and reruns when they change
|
||||
create_effect(move |_| {
|
||||
log!("text = {}", text());
|
||||
logging::log!("text = {}", text());
|
||||
});
|
||||
|
||||
view! {
|
||||
|
||||
@@ -16,7 +16,7 @@ Calling a `ReadSignal` as a function is syntax sugar for `.get()`. Calling a `Wr
|
||||
```rust
|
||||
let (count, set_count) = create_signal(0);
|
||||
set_count(1);
|
||||
log!(count());
|
||||
logging::log!(count());
|
||||
```
|
||||
|
||||
is the same as
|
||||
@@ -24,7 +24,7 @@ is the same as
|
||||
```rust
|
||||
let (count, set_count) = create_signal(0);
|
||||
set_count.set(1);
|
||||
log!(count.get());
|
||||
logging::log!(count.get());
|
||||
```
|
||||
|
||||
You might notice that `.get()` and `.set()` can be implemented in terms of `.with()` and `.update()`. In other words, `count.get()` is identical with `count.with(|n| n.clone())`, and `count.set(1)` is implemented by doing `count.update(|n| *n = 1)`.
|
||||
@@ -63,7 +63,7 @@ if names.with(Vec::is_empty) {
|
||||
}
|
||||
```
|
||||
|
||||
After all, `.with()` simply takes a function that takes the value by reference. Since `Vec::is_empty` takes `&self`, we can pass it in directly and avoid the unncessary closure.
|
||||
After all, `.with()` simply takes a function that takes the value by reference. Since `Vec::is_empty` takes `&self`, we can pass it in directly and avoid the unnecessary closure.
|
||||
|
||||
## Making signals depend on each other
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
|
||||
#[component]
|
||||
pub fn BusyButton() -> impl IntoView {
|
||||
view! {
|
||||
|
||||
<button on:click=move |_| {
|
||||
spawn_local(async {
|
||||
add_todo("So much to do!".to_string()).await;
|
||||
@@ -70,6 +69,18 @@ There are a few things to note about the way you define a server function, too.
|
||||
- We provide the macro a path. This is a prefix for the path at which we’ll mount a server function handler on our server. (See examples for [Actix](https://github.com/leptos-rs/leptos/blob/main/examples/todo_app_sqlite/src/main.rs#L44) and [Axum](https://github.com/leptos-rs/leptos/blob/598523cd9d0d775b017cb721e41ebae9349f01e2/examples/todo_app_sqlite_axum/src/main.rs#L51).)
|
||||
- You’ll need to have `serde` as a dependency with the `derive` featured enabled for the macro to work properly. You can easily add it to `Cargo.toml` with `cargo add serde --features=derive`.
|
||||
|
||||
## Server Function URL Prefixes
|
||||
|
||||
You can optionally define a specific URL prefix to be used in the definition of the server function.
|
||||
This is done by providing an optional 2nd argument to the `#[server]` macro.
|
||||
By default the URL prefix will be `/api`, if not specified.
|
||||
Here are some examples:
|
||||
|
||||
```rust
|
||||
#[server(AddTodo)] // will use the default URL prefix of `/api`
|
||||
#[server(AddTodo, "/foo")] // will use the URL prefix of `/foo`
|
||||
```
|
||||
|
||||
## Server Function Encodings
|
||||
|
||||
By default, the server function call is a `POST` request that serializes the arguments as URL-encoded form data in the body of the request. (This means that server functions can be called from HTML forms, which we’ll see in a future chapter.) But there are a few other methods supported. Optionally, we can provide another argument to the `#[server]` macro to specify an alternate encoding:
|
||||
@@ -105,6 +116,21 @@ In other words, you have two choices:
|
||||
>
|
||||
> The CBOR encoding is suported for historical reasons; an earlier version of server functions used a URL encoding that didn’t support nested objects like structs or vectors as server function arguments, which CBOR did. But note that the CBOR forms encounter the same issue as `PUT`, `DELETE`, or JSON: they do not degrade gracefully if the WASM version of your app is not available.
|
||||
|
||||
|
||||
## Server Functions Endpoint Paths
|
||||
|
||||
By default, a unique path will be generated. You can optionally define a specific endpoint path to be used in the URL. This is done by providing an optional 4th argument to the `#[server]` macro. Leptos will generate the complete path by concatenating the URL prefix (2nd argument) and the endpoint path (4th argument).
|
||||
For example,
|
||||
|
||||
```rust
|
||||
#[server(MyServerFnType, "/api", "Url", "hello")]
|
||||
```
|
||||
will generate a server function endpoint at `/api/hello` that accepts a POST request.
|
||||
|
||||
> **Can I use the same server function endpoint path with multiple encodings?**
|
||||
>
|
||||
> No. Different server functions must have unique paths. The `#[server]` macro automatically generates unique paths, but you need to be careful if you choose to specify the complete path manually, as the server looks up server functions by their path.
|
||||
|
||||
## An Important Note on Security
|
||||
|
||||
Server functions are a cool technology, but it’s very important to remember. **Server functions are not magic; they’re syntax sugar for defining a public API.** The _body_ of a server function is never made public; it’s just part of your server binary. But the server function is a publicly accessible API endpoint, and it’s return value is just a JSON or similar blob. You should _never_ return something sensitive from a server function.
|
||||
|
||||
@@ -35,7 +35,6 @@ Here’s a simplified example from our [`session_auth_axum` example](https://git
|
||||
```rust
|
||||
#[server(Login, "/api")]
|
||||
pub async fn login(
|
||||
|
||||
username: String,
|
||||
password: String,
|
||||
remember: Option<String>,
|
||||
|
||||
@@ -9,7 +9,7 @@ Put a log somewhere in your root component. (I usually call mine `<App/>`, but a
|
||||
```rust
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
leptos::log!("where do I run?");
|
||||
logging::log!("where do I run?");
|
||||
// ... whatever
|
||||
}
|
||||
```
|
||||
@@ -166,7 +166,7 @@ For example, say that I want to store something in the browser’s `localStorage
|
||||
pub fn App() -> impl IntoView {
|
||||
use gloo_storage::Storage;
|
||||
let storage = gloo_storage::LocalStorage::raw();
|
||||
leptos::log!("{storage:?}");
|
||||
logging::log!("{storage:?}");
|
||||
}
|
||||
```
|
||||
|
||||
@@ -180,7 +180,7 @@ pub fn App() -> impl IntoView {
|
||||
use gloo_storage::Storage;
|
||||
create_effect(move |_| {
|
||||
let storage = gloo_storage::LocalStorage::raw();
|
||||
leptos::log!("{storage:?}");
|
||||
logging::log!("{storage:?}");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
@@ -37,7 +37,7 @@ impl Todos {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_remaining {
|
||||
fn test_remaining() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ Derived signals let you create reactive computed values that can be used in mult
|
||||
places in your application with minimal overhead.
|
||||
|
||||
Note: Using a derived signal like this means that the calculation runs once per
|
||||
signal change per place we access `double_count`; in other words, twice. This is a
|
||||
signal change and once per place we access `double_count`; in other words, twice. This is a
|
||||
very cheap calculation, so that’s fine. We’ll look at memos in a later chapter, which
|
||||
are designed to solve this problem for expensive calculations.
|
||||
|
||||
|
||||
@@ -35,9 +35,7 @@ Instead, let’s create a `<ProgressBar/>` component.
|
||||
|
||||
```rust
|
||||
#[component]
|
||||
fn ProgressBar(
|
||||
|
||||
) -> impl IntoView {
|
||||
fn ProgressBar() -> impl IntoView {
|
||||
view! {
|
||||
<progress
|
||||
max="50"
|
||||
@@ -64,7 +62,6 @@ In Leptos, you define props by giving additional arguments to the component func
|
||||
```rust
|
||||
#[component]
|
||||
fn ProgressBar(
|
||||
|
||||
progress: ReadSignal<i32>
|
||||
) -> impl IntoView {
|
||||
view! {
|
||||
@@ -118,7 +115,6 @@ argument to the component function with `#[prop(optional)]`.
|
||||
```rust
|
||||
#[component]
|
||||
fn ProgressBar(
|
||||
|
||||
// mark this prop optional
|
||||
// you can specify it or not when you use <ProgressBar/>
|
||||
#[prop(optional)]
|
||||
@@ -149,7 +145,6 @@ with `#[prop(default = ...)`.
|
||||
```rust
|
||||
#[component]
|
||||
fn ProgressBar(
|
||||
|
||||
#[prop(default = 100)]
|
||||
max: u16,
|
||||
progress: ReadSignal<i32>
|
||||
@@ -199,7 +194,6 @@ implement the trait `Fn() -> i32`. So you could use a generic component:
|
||||
```rust
|
||||
#[component]
|
||||
fn ProgressBar<F>(
|
||||
|
||||
#[prop(default = 100)]
|
||||
max: u16,
|
||||
progress: F
|
||||
@@ -254,7 +248,6 @@ reactive value.
|
||||
```rust
|
||||
#[component]
|
||||
fn ProgressBar(
|
||||
|
||||
#[prop(default = 100)]
|
||||
max: u16,
|
||||
#[prop(into)]
|
||||
@@ -373,7 +366,6 @@ component function, and each one of the props:
|
||||
/// Shows progress toward a goal.
|
||||
#[component]
|
||||
fn ProgressBar(
|
||||
|
||||
/// The maximum value of the progress bar.
|
||||
#[prop(default = 100)]
|
||||
max: u16,
|
||||
|
||||
@@ -148,10 +148,10 @@ This _works_, for sure. But if you added a log, you might be surprised
|
||||
|
||||
```rust
|
||||
let message = move || if value() > 5 {
|
||||
log!("{}: rendering Big", value());
|
||||
logging::log!("{}: rendering Big", value());
|
||||
"Big"
|
||||
} else {
|
||||
log!("{}: rendering Small", value());
|
||||
logging::log!("{}: rendering Small", value());
|
||||
"Small"
|
||||
};
|
||||
```
|
||||
|
||||
@@ -72,10 +72,7 @@ pub fn App() -> impl IntoView {
|
||||
|
||||
|
||||
#[component]
|
||||
pub fn ButtonB<F>(
|
||||
|
||||
on_click: F,
|
||||
) -> impl IntoView
|
||||
pub fn ButtonB<F>(on_click: F) -> impl IntoView
|
||||
where
|
||||
F: Fn(MouseEvent) + 'static,
|
||||
{
|
||||
|
||||
@@ -47,7 +47,6 @@ Let’s define a component that takes some children and a render prop.
|
||||
```rust
|
||||
#[component]
|
||||
pub fn TakesChildren<F, IV>(
|
||||
|
||||
/// Takes a function (type F) that returns anything that can be
|
||||
/// converted into a View (type IV)
|
||||
render_prop: F,
|
||||
|
||||
98
docs/book/src/view/builder.md
Normal file
98
docs/book/src/view/builder.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# No Macros: The View Builder Syntax
|
||||
|
||||
> If you’re perfectly happy with the `view!` macro syntax described so far, you’re welcome to skip this chapter. The builder syntax described in this section is always available, but never required.
|
||||
|
||||
For one reason or another, many developers would prefer to avoid macros. Perhaps you don’t like the limited `rustfmt` support. (Although, you should check out [`leptosfmt`](https://github.com/bram209/leptosfmt), which is an excellent tool!) Perhaps you worry about the effect of macros on compile time. Perhaps you prefer the aesthetics of pure Rust syntax, or you have trouble context-switching between an HTML-like syntax and your Rust code. Or perhaps you want more flexibility in how you create and manipulate HTML elements than the `view` macro provides.
|
||||
|
||||
If you fall into any of those camps, the builder syntax may be for you.
|
||||
|
||||
The `view` macro expands an HTML-like syntax to a series of Rust functions and method calls. If you’d rather not use the `view` macro, you can simply use that expanded syntax yourself. And it’s actually pretty nice!
|
||||
|
||||
First off, if you want you can even drop the `#[component]` macro: a component is just a setup function that creates your view, so you can define a component as a simple function call:
|
||||
|
||||
```rust
|
||||
pub fn counter(initial_value: i32, step: u32) -> impl IntoView { }
|
||||
```
|
||||
|
||||
Elements are created by calling a function with the same name as the HTML element:
|
||||
|
||||
```rust
|
||||
p()
|
||||
```
|
||||
|
||||
You can add children to the element with [`.child()`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.child), which takes a single child or a tuple or array of types that implement [`IntoView`](https://docs.rs/leptos/latest/leptos/trait.IntoView.html).
|
||||
|
||||
```rust
|
||||
p().child((em().child("Big, "), strong().child("bold "), "text"))
|
||||
```
|
||||
|
||||
Attributes are added with [`.attr()`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.attr). This can take any of the same types that you could pass as an attribute into the view macro (types that implement [`IntoAttribute`](https://docs.rs/leptos/latest/leptos/trait.IntoAttribute.html)).
|
||||
|
||||
```rust
|
||||
p().attr("id", "foo").attr("data-count", move || count().to_string())
|
||||
```
|
||||
|
||||
Similarly, the `class:`, `prop:`, and `style:` syntaxes map directly onto [`.class()`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.class), [`.prop()`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.prop), and [`.style()`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.style) methods.
|
||||
|
||||
Event listeners can be added with [`.on()`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.on). Typed events found in [`leptos::ev`](https://docs.rs/leptos/latest/leptos/ev/index.html) prevent typos in event names and allow for correct type inference in the callback function.
|
||||
|
||||
```rust
|
||||
button()
|
||||
.on(ev::click, move |_| set_count.update(|count| count.clear()))
|
||||
.child("Clear")
|
||||
```
|
||||
|
||||
> Many additional methods can be found in the [`HtmlElement`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.child) docs, including some methods that are not directly available in the `view` macro.
|
||||
|
||||
All of this adds up to a very Rusty syntax to build full-featured views, if you prefer this style.
|
||||
|
||||
```rust
|
||||
/// A simple counter view.
|
||||
// A component is really just a function call: it runs once to create the DOM and reactive system
|
||||
pub fn counter(initial_value: i32, step: u32) -> impl IntoView {
|
||||
let (count, set_count) = create_signal(0);
|
||||
|
||||
div()
|
||||
.child((
|
||||
button()
|
||||
// typed events found in leptos::ev
|
||||
// 1) prevent typos in event names
|
||||
// 2) allow for correct type inference in callbacks
|
||||
.on(ev::click, move |_| set_count.update(|count| count.clear()))
|
||||
.child("Clear"),
|
||||
button()
|
||||
.on(ev::click, move |_| {
|
||||
set_count.update(|count| count.decrease())
|
||||
})
|
||||
.child("-1"),
|
||||
span().child(("Value: ", move || count.get().value(), "!")),
|
||||
button()
|
||||
.on(ev::click, move |_| {
|
||||
set_count.update(|count| count.increase())
|
||||
})
|
||||
.child("+1"),
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
This also has the benefit of being more flexible: because these are all plain Rust functions and methods, it’s easier to use them in things like iterator adapters without any additional “magic”:
|
||||
|
||||
```rust
|
||||
// take some set of attribute names and values
|
||||
let attrs: Vec<(&str, AttributeValue)> = todo!();
|
||||
// you can use the builder syntax to “spread” these onto the
|
||||
// element in a way that’s not possible with the view macro
|
||||
let p = attrs
|
||||
.into_iter()
|
||||
.fold(p(), |el, (name, value)| el.attr(name, value));
|
||||
|
||||
```
|
||||
|
||||
> ## Performance Note
|
||||
>
|
||||
> One caveat: the `view` macro applies significant optimizations in server-side-rendering (SSR) mode to improve HTML rendering performance significantly (think 2-4x faster, depending on the characteristics of any given app). It does this by analyzing your `view` at compile time and converting the static parts into simple HTML strings, rather than expanding them into the builder syntax.
|
||||
>
|
||||
> This means two things:
|
||||
>
|
||||
> 1. The builder syntax and `view` macro should not be mixed, or should only be mixed very carefully: at least in SSR mode, the output of the `view` should be treated as a “black box” that can’t have additional builder methods applied to it without causing inconsistencies.
|
||||
> 2. Using the builder syntax will result in less-than-optimal SSR performance. It won’t be slow, by any means (and it’s worth running your own benchmarks in any case), just slower than the `view`-optimized version.
|
||||
@@ -49,10 +49,12 @@ jq -R -s -c 'split("\n")[:-1]')
|
||||
echo "CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = $examples"
|
||||
'''
|
||||
|
||||
[tasks.test-runner-report]
|
||||
[tasks.test-report]
|
||||
workspace = false
|
||||
description = "report ci test runners for each example - OPTION: [all]"
|
||||
description = "report web testing technology used by examples - OPTION: [all]"
|
||||
script = '''
|
||||
set -emu
|
||||
|
||||
BOLD="\e[1m"
|
||||
GREEN="\e[0;32m"
|
||||
ITALIC="\e[3m"
|
||||
@@ -60,11 +62,10 @@ YELLOW="\e[0;33m"
|
||||
RESET="\e[0m"
|
||||
|
||||
echo
|
||||
echo "${YELLOW}Test Runner Report${RESET}"
|
||||
echo "${ITALIC}Pass the option \"all\" to show all the examples${RESET}"
|
||||
echo "${YELLOW}Web Test Technology${RESET}"
|
||||
echo
|
||||
|
||||
makefile_paths=$(find . -name Makefile.toml -not -path '*/target/*' |
|
||||
makefile_paths=$(find . -name Makefile.toml -not -path '*/target/*' -not -path '*/node_modules/*' |
|
||||
sed 's%./%%' |
|
||||
sed 's%/Makefile.toml%%' |
|
||||
grep -v Makefile.toml |
|
||||
@@ -75,38 +76,78 @@ start_path=$(pwd)
|
||||
for path in $makefile_paths; do
|
||||
cd $path
|
||||
|
||||
test_runner=
|
||||
crate_symbols=
|
||||
|
||||
test_count=$(grep -rl -E "#\[(test|rstest)\]" | wc -l)
|
||||
if [ $test_count -gt 0 ]; then
|
||||
test_runner="-C"
|
||||
fi
|
||||
pw_count=$(find . -name playwright.config.ts | wc -l)
|
||||
|
||||
while read -r line; do
|
||||
case $line in
|
||||
*"cucumber"*)
|
||||
crate_symbols=$crate_symbols"C"
|
||||
;;
|
||||
*"fantoccini"*)
|
||||
crate_symbols=$crate_symbols"D"
|
||||
;;
|
||||
esac
|
||||
done <"./Cargo.toml"
|
||||
|
||||
while read -r line; do
|
||||
case $line in
|
||||
*"wasm-test.toml"*)
|
||||
test_runner=$test_runner"-W"
|
||||
*"cargo-make/wasm-test.toml"*)
|
||||
crate_symbols=$crate_symbols"W"
|
||||
;;
|
||||
*"playwright-test.toml"*)
|
||||
test_runner=$test_runner"-P"
|
||||
*"cargo-make/playwright-test.toml"*)
|
||||
crate_symbols=$crate_symbols"P"
|
||||
crate_symbols=$crate_symbols"N"
|
||||
;;
|
||||
*"cargo-leptos-test.toml"*)
|
||||
test_runner=$test_runner"-L"
|
||||
*"cargo-make/playwright-trunk-test.toml"*)
|
||||
crate_symbols=$crate_symbols"P"
|
||||
crate_symbols=$crate_symbols"T"
|
||||
;;
|
||||
*"cargo-make/trunk_server.toml"*)
|
||||
crate_symbols=$crate_symbols"T"
|
||||
;;
|
||||
*"cargo-make/cargo-leptos-webdriver-test.toml"*)
|
||||
crate_symbols=$crate_symbols"L"
|
||||
;;
|
||||
*"cargo-make/cargo-leptos-test.toml"*)
|
||||
crate_symbols=$crate_symbols"L"
|
||||
if [ $pw_count -gt 0 ]; then
|
||||
crate_symbols=$crate_symbols"P"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done <"./Makefile.toml"
|
||||
|
||||
if [ ! -z "$1" ]; then
|
||||
# Sort list of tools
|
||||
sorted_crate_symbols=$(echo ${crate_symbols} | grep -o . | sort | tr -d "\n")
|
||||
|
||||
formatted_crate_symbols=" ➤ ${BOLD}${YELLOW}${sorted_crate_symbols}${RESET}"
|
||||
crate_line=$path
|
||||
if [ ! -z ${1+x} ]; then
|
||||
# Show all examples
|
||||
echo "$path ${BOLD}${test_runner}${RESET}"
|
||||
elif [ ! -z $test_runner ]; then
|
||||
if [ ! -z $crate_symbols ]; then
|
||||
crate_line=$crate_line$formatted_crate_symbols
|
||||
fi
|
||||
echo $crate_line
|
||||
elif [ ! -z $crate_symbols ]; then
|
||||
# Filter out examples that do not run tests in `ci`
|
||||
echo "$path ${BOLD}${test_runner}${RESET}"
|
||||
crate_line=$crate_line$formatted_crate_symbols
|
||||
echo $crate_line
|
||||
fi
|
||||
|
||||
cd ${start_path}
|
||||
done
|
||||
|
||||
c="${BOLD}${YELLOW}C${RESET} = Cucumber"
|
||||
d="${BOLD}${YELLOW}D${RESET} = WebDriver"
|
||||
l="${BOLD}${YELLOW}L${RESET} = Cargo Leptos"
|
||||
n="${BOLD}${YELLOW}N${RESET} = Node"
|
||||
p="${BOLD}${YELLOW}P${RESET} = Playwright"
|
||||
t="${BOLD}${YELLOW}T${RESET} = Trunk"
|
||||
w="${BOLD}${YELLOW}W${RESET} = WASM"
|
||||
|
||||
echo
|
||||
echo "${ITALIC}Runners: C = Cargo Test, L = Cargo Leptos Test, P = Playwright Test, W = WASM Test${RESET}"
|
||||
echo "${ITALIC}Keys:${RESET} $c, $d, $l, $n, $p, $t, $w"
|
||||
echo
|
||||
'''
|
||||
|
||||
@@ -4,4 +4,4 @@ The examples in this directory are all built and tested against the current `mai
|
||||
|
||||
To the extent that new features have been released or breaking changes have been made since the previous release, the examples are compatible with the `main` branch and not the current release.
|
||||
|
||||
To see the examples as they were at the time of the `0.3.0` release, [click here](https://github.com/leptos-rs/leptos/tree/v0.3.0/examples).
|
||||
To see the examples as they were at the time of the `0.3.0` release, [click here](https://github.com/leptos-rs/leptos/tree/v0.3.0/examples).
|
||||
|
||||
@@ -2,7 +2,3 @@ extend = { path = "./cargo-leptos.toml" }
|
||||
|
||||
[tasks.integration-test]
|
||||
dependencies = ["install-cargo-leptos", "cargo-leptos-e2e"]
|
||||
|
||||
[tasks.cargo-leptos-e2e]
|
||||
command = "cargo"
|
||||
args = ["leptos", "end-to-end"]
|
||||
|
||||
7
examples/cargo-make/cargo-leptos-webdriver-test.toml
Normal file
7
examples/cargo-make/cargo-leptos-webdriver-test.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
extend = [
|
||||
{ path = "./cargo-leptos.toml" },
|
||||
{ path = "../cargo-make/webdriver.toml" },
|
||||
]
|
||||
|
||||
[tasks.integration-test]
|
||||
dependencies = ["install-cargo-leptos", "start-webdriver", "cargo-leptos-e2e"]
|
||||
@@ -1,6 +1,12 @@
|
||||
extend = { path = "../cargo-make/client-process.toml" }
|
||||
|
||||
[tasks.install-cargo-leptos]
|
||||
install_crate = { crate_name = "cargo-leptos", binary = "cargo-leptos", test_arg = "--help" }
|
||||
|
||||
[tasks.cargo-leptos-e2e]
|
||||
command = "cargo"
|
||||
args = ["leptos", "end-to-end"]
|
||||
|
||||
[tasks.build]
|
||||
clear = true
|
||||
command = "cargo"
|
||||
@@ -25,31 +31,3 @@ install_crate = "cargo-all-features"
|
||||
[tasks.start-client]
|
||||
command = "cargo"
|
||||
args = ["leptos", "watch"]
|
||||
|
||||
[tasks.stop-client]
|
||||
condition = { env_set = ["APP_PROCESS_NAME"] }
|
||||
script = '''
|
||||
if [ ! -z $(pidof ${APP_PROCESS_NAME}) ]; then
|
||||
pkill -f todo_app_sqlite
|
||||
fi
|
||||
|
||||
if [ ! -z $(pidof ${APP_PROCESS_NAME}) ]; then
|
||||
pkill -f cargo-leptos
|
||||
fi
|
||||
'''
|
||||
|
||||
[tasks.client-status]
|
||||
condition = { env_set = ["APP_PROCESS_NAME"] }
|
||||
script = '''
|
||||
if [ -z $(pidof ${APP_PROCESS_NAME}) ]; then
|
||||
echo " ${APP_PROCESS_NAME} is not running"
|
||||
else
|
||||
echo " ${APP_PROCESS_NAME} is up"
|
||||
fi
|
||||
|
||||
if [ -z $(pidof cargo-leptos) ]; then
|
||||
echo " cargo-leptos is not running"
|
||||
else
|
||||
echo " cargo-leptos is up"
|
||||
fi
|
||||
'''
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
[tasks.clean]
|
||||
dependencies = [
|
||||
"clean-cargo",
|
||||
"clean-trunk",
|
||||
"clean-node_modules",
|
||||
"clean-playwright",
|
||||
"clean-cargo",
|
||||
"clean-trunk",
|
||||
"clean-node_modules",
|
||||
"clean-playwright",
|
||||
]
|
||||
|
||||
[tasks.clean-cargo]
|
||||
command = "cargo"
|
||||
args = ["clean"]
|
||||
command = "rm"
|
||||
args = ["-rf", "target"]
|
||||
|
||||
[tasks.clean-trunk]
|
||||
command = "trunk"
|
||||
args = ["clean"]
|
||||
script = '''
|
||||
find . -type d -name dist | xargs rm -rf
|
||||
'''
|
||||
|
||||
[tasks.clean-node_modules]
|
||||
script = '''
|
||||
|
||||
35
examples/cargo-make/client-process.toml
Normal file
35
examples/cargo-make/client-process.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
[tasks.start-client]
|
||||
|
||||
[tasks.stop-client]
|
||||
condition = { env_set = ["CLIENT_PROCESS_NAME"] }
|
||||
script = '''
|
||||
if [ ! -z $(pidof ${CLIENT_PROCESS_NAME}) ]; then
|
||||
pkill -ef ${CLIENT_PROCESS_NAME}
|
||||
fi
|
||||
'''
|
||||
|
||||
[tasks.client-status]
|
||||
condition = { env_set = ["CLIENT_PROCESS_NAME"] }
|
||||
script = '''
|
||||
if [ -z $(pidof ${CLIENT_PROCESS_NAME}) ]; then
|
||||
echo " ${CLIENT_PROCESS_NAME} is not running"
|
||||
else
|
||||
echo " ${CLIENT_PROCESS_NAME} is up"
|
||||
fi
|
||||
'''
|
||||
|
||||
[tasks.maybe-start-client]
|
||||
condition = { env_set = ["CLIENT_PROCESS_NAME"] }
|
||||
script = '''
|
||||
if [ -z $(pidof ${CLIENT_PROCESS_NAME}) ]; then
|
||||
echo " Starting ${CLIENT_PROCESS_NAME}"
|
||||
cargo make start-client ${@} &
|
||||
else
|
||||
echo " ${CLIENT_PROCESS_NAME} is already started"
|
||||
fi
|
||||
'''
|
||||
|
||||
# ALIASES
|
||||
|
||||
[tasks.dev]
|
||||
alias = "maybe-start-client"
|
||||
17
examples/cargo-make/playwright-trunk-test.toml
Normal file
17
examples/cargo-make/playwright-trunk-test.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/playwright.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
]
|
||||
|
||||
[tasks.integration-test]
|
||||
dependencies = [
|
||||
"maybe-start-client",
|
||||
"wait-one",
|
||||
"test-playwright",
|
||||
"stop-client",
|
||||
]
|
||||
|
||||
[tasks.wait-one]
|
||||
script = '''
|
||||
sleep 1
|
||||
'''
|
||||
@@ -1,18 +1,12 @@
|
||||
extend = { path = "../cargo-make/client-process.toml" }
|
||||
|
||||
[env]
|
||||
CLIENT_PROCESS_NAME = "trunk"
|
||||
|
||||
[tasks.build]
|
||||
command = "trunk"
|
||||
args = ["build"]
|
||||
|
||||
[tasks.start-trunk]
|
||||
[tasks.start-client]
|
||||
command = "trunk"
|
||||
args = ["serve", "${@}"]
|
||||
|
||||
[tasks.stop-trunk]
|
||||
script = '''
|
||||
pkill -f "cargo-make"
|
||||
pkill -f "trunk"
|
||||
'''
|
||||
|
||||
# ALIASES
|
||||
|
||||
[tasks.dev]
|
||||
dependencies = ["start-trunk"]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use leptos::*;
|
||||
use leptos::{SignalWrite, *};
|
||||
use std::cell::{Ref, RefMut};
|
||||
|
||||
/// A simple counter component.
|
||||
///
|
||||
@@ -12,12 +13,20 @@ pub fn SimpleCounter(
|
||||
) -> impl IntoView {
|
||||
let (value, set_value) = create_signal(initial_value);
|
||||
|
||||
let something: Ref<'_, i32> = value.read();
|
||||
|
||||
spawn_local(async move {
|
||||
let mut something_else: RefMut<'_, i32> = set_value.write();
|
||||
async {}.await;
|
||||
*something_else = 30;
|
||||
});
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<button on:click=move |_| set_value(0)>"Clear"</button>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= step)>"-1"</button>
|
||||
<button on:click=move |_| *set_value.write() -= step>"-1"</button>
|
||||
<span>"Value: " {value} "!"</span>
|
||||
<button on:click=move |_| set_value.update(|value| *value += step)>"+1"</button>
|
||||
<button on:click=move |_| *set_value.write() += step>"+1"</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ use leptos::{ev, html::*, *};
|
||||
/// A simple counter view.
|
||||
// A component is really just a function call: it runs once to create the DOM and reactive system
|
||||
pub fn counter(initial_value: i32, step: u32) -> impl IntoView {
|
||||
let (count, set_count) = create_signal(Count::new(initial_value, step));
|
||||
let count = RwSignal::new(Count::new(initial_value, step));
|
||||
|
||||
// elements are created by calling a function with a Scope argument
|
||||
// the function name is the same as the HTML tag name
|
||||
div()
|
||||
// children can be added with .child()
|
||||
@@ -18,27 +17,16 @@ pub fn counter(initial_value: i32, step: u32) -> impl IntoView {
|
||||
// typed events found in leptos::ev
|
||||
// 1) prevent typos in event names
|
||||
// 2) allow for correct type inference in callbacks
|
||||
.on(ev::click, move |_| set_count.update(|count| count.clear()))
|
||||
.on(ev::click, move |_| count.update(Count::clear))
|
||||
.child("Clear"),
|
||||
button()
|
||||
.on(ev::click, move |_| {
|
||||
set_count.update(|count| count.decrease())
|
||||
})
|
||||
.on(ev::click, move |_| count.update(Count::decrease))
|
||||
.child("-1"),
|
||||
span()
|
||||
.child("Value: ")
|
||||
// reactive values are passed to .child() as a tuple
|
||||
// (Scope, [child function]) so an effect can be created
|
||||
.child(move || count.get().value())
|
||||
.child("!"),
|
||||
))
|
||||
.child(
|
||||
span().child(("Value: ", move || count.get().value(), "!")),
|
||||
button()
|
||||
.on(ev::click, move |_| {
|
||||
set_count.update(|count| count.increase())
|
||||
})
|
||||
.on(ev::click, move |_| count.update(Count::increase))
|
||||
.child("+1"),
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
20
examples/error_boundary/.gitignore
vendored
Normal file
20
examples/error_boundary/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# Support playwright testing
|
||||
node_modules/
|
||||
test-results/
|
||||
end2end/playwright-report/
|
||||
playwright/.cache/
|
||||
pnpm-lock.yaml
|
||||
|
||||
# Support trunk
|
||||
dist
|
||||
@@ -1 +1,4 @@
|
||||
extend = [{ path = "../cargo-make/main.toml" }]
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/playwright-trunk-test.toml" },
|
||||
]
|
||||
|
||||
4
examples/error_boundary/e2e/.gitignore
vendored
Normal file
4
examples/error_boundary/e2e/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
83
examples/error_boundary/e2e/package-lock.json
generated
Normal file
83
examples/error_boundary/e2e/package-lock.json
generated
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"name": "grip",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "grip",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.35.1"
|
||||
}
|
||||
},
|
||||
"node_modules/.pnpm/@playwright+test@1.33.0": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/.pnpm/@types+node@20.2.1/node_modules/@types/node": {
|
||||
"version": "20.2.1",
|
||||
"extraneous": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/.pnpm/playwright-core@1.33.0/node_modules/playwright-core": {
|
||||
"version": "1.33.0",
|
||||
"extraneous": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.35.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.35.1.tgz",
|
||||
"integrity": "sha512-b5YoFe6J9exsMYg0pQAobNDR85T1nLumUYgUTtKm4d21iX2L7WqKq9dW8NGJ+2vX0etZd+Y7UeuqsxDXm9+5ZA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.35.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz",
|
||||
"integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.35.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.35.1.tgz",
|
||||
"integrity": "sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
examples/error_boundary/e2e/package.json
Normal file
7
examples/error_boundary/e2e/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"private": "true",
|
||||
"scripts": {},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.35.1"
|
||||
}
|
||||
}
|
||||
77
examples/error_boundary/e2e/playwright.config.ts
Normal file
77
examples/error_boundary/e2e/playwright.config.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !process.env.DEV,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.DEV ? 0 : 2,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.DEV ? 1 : 1,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [["html", { open: "never" }], ["list"]],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://127.0.0.1:8080",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: "firefox",
|
||||
// use: { ...devices["Desktop Firefox"] },
|
||||
// },
|
||||
|
||||
// {
|
||||
// name: "webkit",
|
||||
// use: { ...devices["Desktop Safari"] },
|
||||
// },
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ..devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: "cd ../ && trunk serve",
|
||||
// url: "http://127.0.0.1:8080",
|
||||
// reuseExistingServer: false, //!process.env.CI,
|
||||
// },
|
||||
});
|
||||
23
examples/error_boundary/e2e/tests/clear_number.spec.ts
Normal file
23
examples/error_boundary/e2e/tests/clear_number.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { HomePage } from "./fixtures/home_page";
|
||||
|
||||
test.describe("Clear Number", () => {
|
||||
test("should see the error message", async ({ page }) => {
|
||||
const ui = new HomePage(page);
|
||||
await ui.goto();
|
||||
|
||||
await ui.clearInput();
|
||||
|
||||
await expect(ui.errorMessage).toHaveText("Not a number! Errors: ");
|
||||
});
|
||||
test("should see the error list", async ({ page }) => {
|
||||
const ui = new HomePage(page);
|
||||
await ui.goto();
|
||||
|
||||
await ui.clearInput();
|
||||
|
||||
await expect(ui.errorList).toHaveText(
|
||||
"cannot parse integer from empty string"
|
||||
);
|
||||
});
|
||||
});
|
||||
17
examples/error_boundary/e2e/tests/click_down_arrow.spec.ts
Normal file
17
examples/error_boundary/e2e/tests/click_down_arrow.spec.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { HomePage } from "./fixtures/home_page";
|
||||
|
||||
test.describe("Click Down Arrow", () => {
|
||||
test("should see the negative number", async ({ page }) => {
|
||||
const ui = new HomePage(page);
|
||||
await ui.goto();
|
||||
|
||||
await ui.clickDownArrow();
|
||||
await ui.clickDownArrow();
|
||||
await ui.clickDownArrow();
|
||||
await ui.clickDownArrow();
|
||||
await ui.clickDownArrow();
|
||||
|
||||
await expect(ui.successMessage).toHaveText("You entered -5");
|
||||
});
|
||||
});
|
||||
15
examples/error_boundary/e2e/tests/click_up_arrow.spec.ts
Normal file
15
examples/error_boundary/e2e/tests/click_up_arrow.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { HomePage } from "./fixtures/home_page";
|
||||
|
||||
test.describe("Click Up Arrow", () => {
|
||||
test("should see the positive number", async ({ page }) => {
|
||||
const ui = new HomePage(page);
|
||||
await ui.goto();
|
||||
|
||||
await ui.clickUpArrow();
|
||||
await ui.clickUpArrow();
|
||||
await ui.clickUpArrow();
|
||||
|
||||
await expect(ui.successMessage).toHaveText("You entered 3");
|
||||
});
|
||||
});
|
||||
56
examples/error_boundary/e2e/tests/fixtures/home_page.ts
vendored
Normal file
56
examples/error_boundary/e2e/tests/fixtures/home_page.ts
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
import { expect, Locator, Page } from "@playwright/test";
|
||||
|
||||
export class HomePage {
|
||||
readonly page: Page;
|
||||
readonly pageTitle: Locator;
|
||||
readonly numberInput: Locator;
|
||||
readonly successMessage: Locator;
|
||||
readonly errorMessage: Locator;
|
||||
|
||||
readonly errorList: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
|
||||
this.pageTitle = page.locator("h1");
|
||||
this.numberInput = page.getByLabel(
|
||||
"Type a number (or something that's not a number!)"
|
||||
);
|
||||
this.successMessage = page.locator("label p");
|
||||
this.errorMessage = page.locator("div p");
|
||||
this.errorList = page.getByRole("list");
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto("/");
|
||||
}
|
||||
|
||||
async enterNumber(count: string, index: number = 0) {
|
||||
await Promise.all([
|
||||
this.numberInput.waitFor(),
|
||||
this.numberInput.fill(count),
|
||||
]);
|
||||
}
|
||||
|
||||
async clickUpArrow() {
|
||||
await Promise.all([
|
||||
this.numberInput.waitFor(),
|
||||
this.numberInput.press("ArrowUp"),
|
||||
]);
|
||||
}
|
||||
|
||||
async clickDownArrow() {
|
||||
await Promise.all([
|
||||
this.numberInput.waitFor(),
|
||||
this.numberInput.press("ArrowDown"),
|
||||
]);
|
||||
}
|
||||
|
||||
async clearInput() {
|
||||
await Promise.all([
|
||||
this.numberInput.waitFor(),
|
||||
this.clickUpArrow(),
|
||||
this.numberInput.press("Backspace"),
|
||||
]);
|
||||
}
|
||||
}
|
||||
11
examples/error_boundary/e2e/tests/open_app.spec.ts
Normal file
11
examples/error_boundary/e2e/tests/open_app.spec.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { HomePage } from "./fixtures/home_page";
|
||||
|
||||
test.describe("Open App", () => {
|
||||
test("should see the page title", async ({ page }) => {
|
||||
const ui = new HomePage(page);
|
||||
await ui.goto();
|
||||
|
||||
await expect(ui.pageTitle).toHaveText("Error Handling");
|
||||
});
|
||||
});
|
||||
13
examples/error_boundary/e2e/tests/type_number.spec.ts
Normal file
13
examples/error_boundary/e2e/tests/type_number.spec.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { HomePage } from "./fixtures/home_page";
|
||||
|
||||
test.describe("Type Number", () => {
|
||||
test("should see the typed number", async ({ page }) => {
|
||||
const ui = new HomePage(page);
|
||||
await ui.goto();
|
||||
|
||||
await ui.enterNumber("7");
|
||||
|
||||
await expect(ui.successMessage).toHaveText("You entered 7");
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::errors::AppError;
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::{Errors, *};
|
||||
use leptos::{logging::log, Errors, *};
|
||||
#[cfg(feature = "ssr")]
|
||||
use leptos_axum::ResponseOptions;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
Router,
|
||||
};
|
||||
use errors_axum::*;
|
||||
use leptos::*;
|
||||
use leptos::{logging::log, *};
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
}}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use leptos::{logging::log, *};
|
||||
|
||||
// boilerplate to run in different modes
|
||||
cfg_if! {
|
||||
|
||||
@@ -51,8 +51,11 @@ 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"
|
||||
# [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/output.css"
|
||||
# The tailwind input file.
|
||||
#
|
||||
# Optional, Activates the tailwind build
|
||||
tailwind-input-file = "style/tailwind.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.
|
||||
|
||||
@@ -1,642 +0,0 @@
|
||||
/*
|
||||
! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com
|
||||
*/
|
||||
|
||||
/*
|
||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||
*/
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
border-width: 0;
|
||||
/* 2 */
|
||||
border-style: solid;
|
||||
/* 2 */
|
||||
border-color: #e5e7eb;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
::before,
|
||||
::after {
|
||||
--tw-content: '';
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use a consistent sensible line-height in all browsers.
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
3. Use a more readable tab size.
|
||||
4. Use the user's configured `sans` font-family by default.
|
||||
5. Use the user's configured `sans` font-feature-settings by default.
|
||||
6. Use the user's configured `sans` font-variation-settings by default.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.5;
|
||||
/* 1 */
|
||||
-webkit-text-size-adjust: 100%;
|
||||
/* 2 */
|
||||
-moz-tab-size: 4;
|
||||
/* 3 */
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
/* 3 */
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
/* 4 */
|
||||
font-feature-settings: normal;
|
||||
/* 5 */
|
||||
font-variation-settings: normal;
|
||||
/* 6 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove the margin in all browsers.
|
||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
/* 1 */
|
||||
line-height: inherit;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Add the correct height in Firefox.
|
||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||
3. Ensure horizontal rules are visible by default.
|
||||
*/
|
||||
|
||||
hr {
|
||||
height: 0;
|
||||
/* 1 */
|
||||
color: inherit;
|
||||
/* 2 */
|
||||
border-top-width: 1px;
|
||||
/* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
abbr:where([title]) {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the default font size and weight for headings.
|
||||
*/
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset links to optimize for opt-in styling instead of opt-out.
|
||||
*/
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font weight in Edge and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use the user's configured `mono` font family by default.
|
||||
2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
pre {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
/* 1 */
|
||||
font-size: 1em;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||
3. Remove gaps between table borders by default.
|
||||
*/
|
||||
|
||||
table {
|
||||
text-indent: 0;
|
||||
/* 1 */
|
||||
border-color: inherit;
|
||||
/* 2 */
|
||||
border-collapse: collapse;
|
||||
/* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Change the font styles in all browsers.
|
||||
2. Remove the margin in Firefox and Safari.
|
||||
3. Remove default padding in all browsers.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
/* 1 */
|
||||
font-size: 100%;
|
||||
/* 1 */
|
||||
font-weight: inherit;
|
||||
/* 1 */
|
||||
line-height: inherit;
|
||||
/* 1 */
|
||||
color: inherit;
|
||||
/* 1 */
|
||||
margin: 0;
|
||||
/* 2 */
|
||||
padding: 0;
|
||||
/* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inheritance of text transform in Edge and Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Remove default button styles.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button;
|
||||
/* 1 */
|
||||
background-color: transparent;
|
||||
/* 2 */
|
||||
background-image: none;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Use the modern Firefox focus style for all focusable elements.
|
||||
*/
|
||||
|
||||
:-moz-focusring {
|
||||
outline: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||
*/
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct vertical alignment in Chrome and Firefox.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the cursor style of increment and decrement buttons in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-inner-spin-button,
|
||||
::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the odd appearance in Chrome and Safari.
|
||||
2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: textfield;
|
||||
/* 1 */
|
||||
outline-offset: -2px;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
/* 1 */
|
||||
font: inherit;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct display in Chrome and Safari.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/*
|
||||
Removes the default spacing and border for appropriate elements.
|
||||
*/
|
||||
|
||||
blockquote,
|
||||
dl,
|
||||
dd,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
hr,
|
||||
figure,
|
||||
p,
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent resizing textareas horizontally by default.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||
*/
|
||||
|
||||
input::-moz-placeholder, textarea::-moz-placeholder {
|
||||
opacity: 1;
|
||||
/* 1 */
|
||||
color: #9ca3af;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
opacity: 1;
|
||||
/* 1 */
|
||||
color: #9ca3af;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Set the default cursor for buttons.
|
||||
*/
|
||||
|
||||
button,
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
Make sure disabled buttons don't get the pointer cursor.
|
||||
*/
|
||||
|
||||
:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||
*/
|
||||
|
||||
img,
|
||||
svg,
|
||||
video,
|
||||
canvas,
|
||||
audio,
|
||||
iframe,
|
||||
embed,
|
||||
object {
|
||||
display: block;
|
||||
/* 1 */
|
||||
vertical-align: middle;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
*/
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
*, ::before, ::after {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-pan-x: ;
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-gradient-from-position: ;
|
||||
--tw-gradient-via-position: ;
|
||||
--tw-gradient-to-position: ;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
--tw-numeric-spacing: ;
|
||||
--tw-numeric-fraction: ;
|
||||
--tw-ring-inset: ;
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
--tw-blur: ;
|
||||
--tw-brightness: ;
|
||||
--tw-contrast: ;
|
||||
--tw-grayscale: ;
|
||||
--tw-hue-rotate: ;
|
||||
--tw-invert: ;
|
||||
--tw-saturate: ;
|
||||
--tw-sepia: ;
|
||||
--tw-drop-shadow: ;
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia: ;
|
||||
}
|
||||
|
||||
::backdrop {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-pan-x: ;
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-gradient-from-position: ;
|
||||
--tw-gradient-via-position: ;
|
||||
--tw-gradient-to-position: ;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
--tw-numeric-spacing: ;
|
||||
--tw-numeric-fraction: ;
|
||||
--tw-ring-inset: ;
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
--tw-blur: ;
|
||||
--tw-brightness: ;
|
||||
--tw-contrast: ;
|
||||
--tw-grayscale: ;
|
||||
--tw-hue-rotate: ;
|
||||
--tw-invert: ;
|
||||
--tw-saturate: ;
|
||||
--tw-sepia: ;
|
||||
--tw-drop-shadow: ;
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia: ;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.m-1 {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
|
||||
.m-auto {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.h-5 {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.w-5 {
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
.flex-row-reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.border-b-4 {
|
||||
border-bottom-width: 4px;
|
||||
}
|
||||
|
||||
.border-l-2 {
|
||||
border-left-width: 2px;
|
||||
}
|
||||
|
||||
.border-blue-800 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(30 64 175 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-blue-900 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(30 58 138 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-blue-700 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-blue-800 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(30 64 175 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gradient-to-tl {
|
||||
background-image: linear-gradient(to top left, var(--tw-gradient-stops));
|
||||
}
|
||||
|
||||
.from-blue-800 {
|
||||
--tw-gradient-from: #1e40af var(--tw-gradient-from-position);
|
||||
--tw-gradient-to: rgb(30 64 175 / 0) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
|
||||
.to-blue-500 {
|
||||
--tw-gradient-to: #3b82f6 var(--tw-gradient-to-position);
|
||||
}
|
||||
|
||||
.fill-current {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.px-3 {
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
}
|
||||
|
||||
.py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
.text-white {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.shadow-lg {
|
||||
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
@@ -38,7 +38,7 @@ where
|
||||
}
|
||||
api::Error::Api(err) => err.message,
|
||||
};
|
||||
error!(
|
||||
log::error!(
|
||||
"Unable to login with {}: {msg}",
|
||||
credentials.email
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
Page,
|
||||
};
|
||||
use api_boundary::*;
|
||||
use leptos::*;
|
||||
use leptos::{logging::log, *};
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mod api;
|
||||
use crate::api::*;
|
||||
use leptos::*;
|
||||
use leptos::{logging::log, *};
|
||||
use leptos_router::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
@@ -122,9 +122,9 @@ pub struct ContactParams {
|
||||
|
||||
#[component]
|
||||
pub fn Contact() -> impl IntoView {
|
||||
log::debug!("rendering <Contact/>");
|
||||
log!("rendering <Contact/>");
|
||||
|
||||
log::debug!(
|
||||
log!(
|
||||
"ExampleContext should be Some(42). It is {:?}",
|
||||
use_context::<ExampleContext>()
|
||||
);
|
||||
@@ -178,13 +178,13 @@ pub fn Contact() -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
pub fn About() -> impl IntoView {
|
||||
log::debug!("rendering <About/>");
|
||||
log!("rendering <About/>");
|
||||
|
||||
on_cleanup(|| {
|
||||
log!("cleaning up <About/>");
|
||||
});
|
||||
|
||||
log::debug!(
|
||||
log!(
|
||||
"ExampleContext should be Some(0). It is {:?}",
|
||||
use_context::<ExampleContext>()
|
||||
);
|
||||
@@ -209,7 +209,7 @@ pub fn About() -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
pub fn Settings() -> impl IntoView {
|
||||
log::debug!("rendering <Settings/>");
|
||||
log!("rendering <Settings/>");
|
||||
|
||||
on_cleanup(|| {
|
||||
log!("cleaning up <Settings/>");
|
||||
|
||||
@@ -16,7 +16,7 @@ if #[cfg(feature = "ssr")] {
|
||||
use session_auth_axum::state::AppState;
|
||||
use session_auth_axum::fallback::file_and_error_handler;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes, handle_server_fns_with_context};
|
||||
use leptos::{log, view, provide_context, get_configuration};
|
||||
use leptos::{logging::log, view, provide_context, get_configuration};
|
||||
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions};
|
||||
use axum_session::{SessionConfig, SessionLayer, SessionStore};
|
||||
use axum_session_auth::{AuthSessionLayer, AuthConfig, SessionSqlitePool};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::{routing::post, Router};
|
||||
use leptos::*;
|
||||
use leptos::{logging::log, *};
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use ssr_modes_axum::{app::*, fallback::file_and_error_handler};
|
||||
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/webdriver.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos-webdriver-test.toml" },
|
||||
]
|
||||
|
||||
[env]
|
||||
APP_PROCESS_NAME = "suspense_tests"
|
||||
|
||||
[tasks.integration-test]
|
||||
dependencies = [
|
||||
"install-cargo-leptos",
|
||||
"start-webdriver",
|
||||
"test-e2e-with-auto-start",
|
||||
]
|
||||
|
||||
[tasks.test-e2e-with-auto-start]
|
||||
command = "cargo"
|
||||
args = ["leptos", "end-to-end"]
|
||||
CLIENT_PROCESS_NAME = "suspense_tests"
|
||||
|
||||
[tasks.test-ui]
|
||||
cwd = "./e2e"
|
||||
|
||||
@@ -156,7 +156,7 @@ fn NestedResourceInside() -> impl IntoView {
|
||||
{move || {
|
||||
one_second.get().map(|_| {
|
||||
let two_second = create_resource(|| (), move |_| async move {
|
||||
leptos::log!("creating two_second resource");
|
||||
logging::log!("creating two_second resource");
|
||||
second_wait_fn(WAIT_TWO_SECONDS).await
|
||||
});
|
||||
view! {
|
||||
|
||||
@@ -84,9 +84,10 @@ 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"
|
||||
# [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/output.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
# The tailwind input file.
|
||||
#
|
||||
# Optional, Activates the tailwind build
|
||||
tailwind-input-file = "style/tailwind.css"
|
||||
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"
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/playwright.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos-test.toml" },
|
||||
]
|
||||
|
||||
[env]
|
||||
CLIENT_PROCESS_NAME = "tailwind"
|
||||
|
||||
@@ -8,10 +8,6 @@ If you don't have `cargo-leptos` installed you can install it with
|
||||
|
||||
Then run
|
||||
|
||||
`npx tailwindcss -i ./input.css -o ./style/output.css --watch`
|
||||
|
||||
and
|
||||
|
||||
`cargo leptos watch`
|
||||
|
||||
in this directory.
|
||||
|
||||
2
examples/tailwind/rust-toolchain.toml
Normal file
2
examples/tailwind/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
@@ -12,7 +12,7 @@ cfg_if! {
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
log!("hydrate mode - hydrating");
|
||||
logging::log!("hydrate mode - hydrating");
|
||||
|
||||
leptos::mount_to_body(|| {
|
||||
view! { <App/> }
|
||||
@@ -29,7 +29,7 @@ cfg_if! {
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
log!("csr mode - mounting to body");
|
||||
logging::log!("csr mode - mounting to body");
|
||||
|
||||
mount_to_body(|| {
|
||||
view! { <App /> }
|
||||
|
||||
@@ -1,650 +0,0 @@
|
||||
/*
|
||||
! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com
|
||||
*/
|
||||
|
||||
/*
|
||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||
*/
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
border-width: 0;
|
||||
/* 2 */
|
||||
border-style: solid;
|
||||
/* 2 */
|
||||
border-color: #e5e7eb;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
::before,
|
||||
::after {
|
||||
--tw-content: '';
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use a consistent sensible line-height in all browsers.
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
3. Use a more readable tab size.
|
||||
4. Use the user's configured `sans` font-family by default.
|
||||
5. Use the user's configured `sans` font-feature-settings by default.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.5;
|
||||
/* 1 */
|
||||
-webkit-text-size-adjust: 100%;
|
||||
/* 2 */
|
||||
-moz-tab-size: 4;
|
||||
/* 3 */
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
/* 3 */
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
/* 4 */
|
||||
font-feature-settings: normal;
|
||||
/* 5 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove the margin in all browsers.
|
||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
/* 1 */
|
||||
line-height: inherit;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Add the correct height in Firefox.
|
||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||
3. Ensure horizontal rules are visible by default.
|
||||
*/
|
||||
|
||||
hr {
|
||||
height: 0;
|
||||
/* 1 */
|
||||
color: inherit;
|
||||
/* 2 */
|
||||
border-top-width: 1px;
|
||||
/* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
abbr:where([title]) {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the default font size and weight for headings.
|
||||
*/
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset links to optimize for opt-in styling instead of opt-out.
|
||||
*/
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font weight in Edge and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use the user's configured `mono` font family by default.
|
||||
2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
pre {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
/* 1 */
|
||||
font-size: 1em;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||
3. Remove gaps between table borders by default.
|
||||
*/
|
||||
|
||||
table {
|
||||
text-indent: 0;
|
||||
/* 1 */
|
||||
border-color: inherit;
|
||||
/* 2 */
|
||||
border-collapse: collapse;
|
||||
/* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Change the font styles in all browsers.
|
||||
2. Remove the margin in Firefox and Safari.
|
||||
3. Remove default padding in all browsers.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
/* 1 */
|
||||
font-size: 100%;
|
||||
/* 1 */
|
||||
font-weight: inherit;
|
||||
/* 1 */
|
||||
line-height: inherit;
|
||||
/* 1 */
|
||||
color: inherit;
|
||||
/* 1 */
|
||||
margin: 0;
|
||||
/* 2 */
|
||||
padding: 0;
|
||||
/* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inheritance of text transform in Edge and Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Remove default button styles.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button;
|
||||
/* 1 */
|
||||
background-color: transparent;
|
||||
/* 2 */
|
||||
background-image: none;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Use the modern Firefox focus style for all focusable elements.
|
||||
*/
|
||||
|
||||
:-moz-focusring {
|
||||
outline: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||
*/
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct vertical alignment in Chrome and Firefox.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the cursor style of increment and decrement buttons in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-inner-spin-button,
|
||||
::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the odd appearance in Chrome and Safari.
|
||||
2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: textfield;
|
||||
/* 1 */
|
||||
outline-offset: -2px;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
/* 1 */
|
||||
font: inherit;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct display in Chrome and Safari.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/*
|
||||
Removes the default spacing and border for appropriate elements.
|
||||
*/
|
||||
|
||||
blockquote,
|
||||
dl,
|
||||
dd,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
hr,
|
||||
figure,
|
||||
p,
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent resizing textareas horizontally by default.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||
*/
|
||||
|
||||
input::-moz-placeholder, textarea::-moz-placeholder {
|
||||
opacity: 1;
|
||||
/* 1 */
|
||||
color: #9ca3af;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
opacity: 1;
|
||||
/* 1 */
|
||||
color: #9ca3af;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Set the default cursor for buttons.
|
||||
*/
|
||||
|
||||
button,
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
Make sure disabled buttons don't get the pointer cursor.
|
||||
*/
|
||||
|
||||
:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||
*/
|
||||
|
||||
img,
|
||||
svg,
|
||||
video,
|
||||
canvas,
|
||||
audio,
|
||||
iframe,
|
||||
embed,
|
||||
object {
|
||||
display: block;
|
||||
/* 1 */
|
||||
vertical-align: middle;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
*/
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
*, ::before, ::after {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-pan-x: ;
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
--tw-numeric-spacing: ;
|
||||
--tw-numeric-fraction: ;
|
||||
--tw-ring-inset: ;
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
--tw-blur: ;
|
||||
--tw-brightness: ;
|
||||
--tw-contrast: ;
|
||||
--tw-grayscale: ;
|
||||
--tw-hue-rotate: ;
|
||||
--tw-invert: ;
|
||||
--tw-saturate: ;
|
||||
--tw-sepia: ;
|
||||
--tw-drop-shadow: ;
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia: ;
|
||||
}
|
||||
|
||||
::backdrop {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-pan-x: ;
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
--tw-numeric-spacing: ;
|
||||
--tw-numeric-fraction: ;
|
||||
--tw-ring-inset: ;
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
--tw-blur: ;
|
||||
--tw-brightness: ;
|
||||
--tw-contrast: ;
|
||||
--tw-grayscale: ;
|
||||
--tw-hue-rotate: ;
|
||||
--tw-invert: ;
|
||||
--tw-saturate: ;
|
||||
--tw-sepia: ;
|
||||
--tw-drop-shadow: ;
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia: ;
|
||||
}
|
||||
|
||||
.my-0 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.max-w-3xl {
|
||||
max-width: 48rem;
|
||||
}
|
||||
|
||||
.rounded-lg {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.rounded-md {
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.rounded-sm {
|
||||
border-radius: 0.125rem;
|
||||
}
|
||||
|
||||
.bg-amber-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(217 119 6 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-sky-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(2 132 199 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-sky-300 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(125 211 252 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-sky-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(14 165 233 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-amber-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(245 158 11 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.p-6 {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.px-10 {
|
||||
padding-left: 2.5rem;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
.px-5 {
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 1.25rem;
|
||||
}
|
||||
|
||||
.py-3 {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.pb-10 {
|
||||
padding-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-4xl {
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
|
||||
.text-white {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-red-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(239 68 68 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-red-200 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(254 202 202 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-sky-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(14 165 233 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-sky-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(125 211 252 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-sky-700 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(3 105 161 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-sky-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(7 89 133 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-red-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(153 27 27 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-sky-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(3 105 161 / var(--tw-bg-opacity));
|
||||
}
|
||||
@@ -7,7 +7,7 @@ pub fn main() {
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
log!("csr mode - mounting to body");
|
||||
logging::log!("csr mode - mounting to body");
|
||||
|
||||
mount_to_body(|| {
|
||||
view! { <App /> }
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/webdriver.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos-webdriver-test.toml" },
|
||||
]
|
||||
|
||||
[env]
|
||||
APP_PROCESS_NAME = "todo_app_sqlite"
|
||||
|
||||
[tasks.integration-test]
|
||||
dependencies = [
|
||||
"install-cargo-leptos",
|
||||
"start-webdriver",
|
||||
"test-e2e-with-auto-start",
|
||||
]
|
||||
|
||||
[tasks.test-e2e-with-auto-start]
|
||||
command = "cargo"
|
||||
args = ["leptos", "end-to-end"]
|
||||
CLIENT_PROCESS_NAME = "todo_app_sqlite"
|
||||
|
||||
[tasks.test-ui]
|
||||
cwd = "./e2e"
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/webdriver.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos-webdriver-test.toml" },
|
||||
]
|
||||
|
||||
[env]
|
||||
APP_PROCESS_NAME = "todo_app_sqlite_axum"
|
||||
|
||||
[tasks.integration-test]
|
||||
dependencies = [
|
||||
"install-cargo-leptos",
|
||||
"start-webdriver",
|
||||
"test-e2e-with-auto-start",
|
||||
]
|
||||
|
||||
[tasks.test-e2e-with-auto-start]
|
||||
command = "cargo"
|
||||
args = ["leptos", "end-to-end"]
|
||||
CLIENT_PROCESS_NAME = "todo_app_sqlite_axum"
|
||||
|
||||
[tasks.test-ui]
|
||||
cwd = "./e2e"
|
||||
|
||||
@@ -60,7 +60,7 @@ cfg_if! {
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
log!("listening on http://{}", &addr);
|
||||
logging::log!("listening on http://{}", &addr);
|
||||
axum::Server::bind(&addr)
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
|
||||
@@ -67,7 +67,7 @@ cfg_if! {
|
||||
|
||||
// run our app with hyper
|
||||
// `viz::Server` is a re-export of `hyper::Server`
|
||||
log!("listening on http://{}", &addr);
|
||||
logging::log!("listening on http://{}", &addr);
|
||||
viz::Server::bind(&addr)
|
||||
.serve(ServiceMaker::from(app))
|
||||
.await
|
||||
|
||||
@@ -11,14 +11,17 @@ fn autoreload(nonce_str: &str, options: &LeptosOptions) -> String {
|
||||
Some(val) => val,
|
||||
None => options.reload_port,
|
||||
};
|
||||
let protocol = match options.reload_ws_protocol {
|
||||
leptos_config::ReloadWSProtocol::WS => "'ws://'",
|
||||
leptos_config::ReloadWSProtocol::WSS => "'wss://'",
|
||||
};
|
||||
match std::env::var("LEPTOS_WATCH").is_ok() {
|
||||
true => format!(
|
||||
r#"
|
||||
<script crossorigin=""{nonce_str}>(function () {{
|
||||
{}
|
||||
let protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||
let host = window.location.hostname;
|
||||
let ws = new WebSocket(protocol + host + ':{reload_port}/live_reload');
|
||||
let ws = new WebSocket({protocol} + host + ':{reload_port}/live_reload');
|
||||
ws.onmessage = (ev) => {{
|
||||
let msg = JSON.parse(ev.data);
|
||||
if (msg.all) window.location.reload();
|
||||
|
||||
@@ -16,14 +16,12 @@ leptos_reactive = { workspace = true }
|
||||
leptos_server = { workspace = true }
|
||||
leptos_config = { workspace = true }
|
||||
tracing = "0.1"
|
||||
typed-builder = "0.14"
|
||||
typed-builder = "0.16"
|
||||
typed-builder-macro = "0.16"
|
||||
server_fn = { workspace = true }
|
||||
web-sys = { version = "0.3.63", optional = true }
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
leptos = { path = "." }
|
||||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
template_macro = ["leptos_dom/web", "web-sys", "wasm-bindgen"]
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::TextProp;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A collection of additional HTML attributes to be applied to an element,
|
||||
/// each of which may or may not be reactive.
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct AdditionalAttributes(pub(crate) Vec<(String, TextProp)>);
|
||||
pub struct AdditionalAttributes(pub(crate) Rc<[(String, TextProp)]>);
|
||||
|
||||
impl<I, T, U> From<I> for AdditionalAttributes
|
||||
where
|
||||
@@ -22,6 +23,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AdditionalAttributes {
|
||||
fn default() -> Self {
|
||||
Self([].into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over additional HTML attributes.
|
||||
#[repr(transparent)]
|
||||
pub struct AdditionalAttributesIter<'a>(
|
||||
|
||||
62
leptos/src/children.rs
Normal file
62
leptos/src/children.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use leptos_dom::Fragment;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// The most common type for the `children` property on components,
|
||||
/// which can only be called once.
|
||||
pub type Children = Box<dyn FnOnce() -> Fragment>;
|
||||
|
||||
/// A type for the `children` property on components that can be called
|
||||
/// more than once.
|
||||
pub type ChildrenFn = Rc<dyn Fn() -> 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() -> Fragment>;
|
||||
|
||||
// This is to still support components that accept `Box<dyn Fn() -> Fragment>` as a children.
|
||||
type BoxedChildrenFn = Box<dyn Fn() -> Fragment>;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait ToChildren<F> {
|
||||
fn to_children(f: F) -> Self;
|
||||
}
|
||||
|
||||
impl<F> ToChildren<F> for Children
|
||||
where
|
||||
F: FnOnce() -> Fragment + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(f: F) -> Self {
|
||||
Box::new(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> ToChildren<F> for ChildrenFn
|
||||
where
|
||||
F: Fn() -> Fragment + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(f: F) -> Self {
|
||||
Rc::new(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> ToChildren<F> for ChildrenFnMut
|
||||
where
|
||||
F: FnMut() -> Fragment + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(f: F) -> Self {
|
||||
Box::new(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> ToChildren<F> for BoxedChildrenFn
|
||||
where
|
||||
F: Fn() -> Fragment + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(f: F) -> Self {
|
||||
Box::new(f)
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ where
|
||||
matches!(child, View::Suspense(_, _))
|
||||
|| matches!(child, View::Component(repr) if repr.name() == "Transition")
|
||||
}) {
|
||||
crate::debug_warn!("You are using a <Suspense/> or \
|
||||
leptos_dom::logging::console_warn("You are using a <Suspense/> or \
|
||||
<Transition/> as the direct child of an <ErrorBoundary/>. To ensure correct \
|
||||
hydration, these should be reorganized so that the <ErrorBoundary/> is a child \
|
||||
of the <Suspense/> or <Transition/> instead: \n\
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::hash::Hash;
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Counters() -> impl IntoView {
|
||||
/// let (counters, set_counters) = create_signal::<Vec<Counter>>( vec![]);
|
||||
/// let (counters, set_counters) = create_signal::<Vec<Counter>>(vec![]);
|
||||
///
|
||||
/// view! {
|
||||
/// <div>
|
||||
@@ -28,7 +28,7 @@ use std::hash::Hash;
|
||||
/// // a unique key for each item
|
||||
/// key=|counter| counter.id
|
||||
/// // renders each item to a view
|
||||
/// view=move | counter: Counter| {
|
||||
/// view=move |counter: Counter| {
|
||||
/// view! {
|
||||
/// <button>"Value: " {move || counter.count.get()}</button>
|
||||
/// }
|
||||
|
||||
@@ -155,11 +155,15 @@ pub mod ssr {
|
||||
pub use leptos_dom::{ssr::*, ssr_in_order::*};
|
||||
}
|
||||
pub use leptos_dom::{
|
||||
self, create_node_ref, debug_warn, document, error, ev, helpers::*, html,
|
||||
log, math, mount_to, mount_to_body, nonce, svg, warn, window, Attribute,
|
||||
Class, CollectView, Errors, Fragment, HtmlElement, IntoAttribute,
|
||||
IntoClass, IntoProperty, IntoStyle, IntoView, NodeRef, Property, View,
|
||||
self, create_node_ref, document, ev, helpers::*, html, math, mount_to,
|
||||
mount_to_body, nonce, svg, window, Attribute, Class, CollectView, Errors,
|
||||
Fragment, HtmlElement, IntoAttribute, IntoClass, IntoProperty, IntoStyle,
|
||||
IntoView, NodeRef, Property, View,
|
||||
};
|
||||
/// Utilities for simple isomorphic logging to the console or terminal.
|
||||
pub mod logging {
|
||||
pub use leptos_dom::{debug_warn, error, log, warn};
|
||||
}
|
||||
|
||||
/// Types to make it easier to handle errors in your application.
|
||||
pub mod error {
|
||||
@@ -175,7 +179,6 @@ pub use leptos_server::{
|
||||
ServerFnErrorErr,
|
||||
};
|
||||
pub use server_fn::{self, ServerFn as _};
|
||||
pub use typed_builder;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "template_macro"))]
|
||||
pub use {leptos_macro::template, wasm_bindgen, web_sys};
|
||||
mod error_boundary;
|
||||
@@ -195,20 +198,16 @@ pub use text_prop::TextProp;
|
||||
#[doc(hidden)]
|
||||
pub use tracing;
|
||||
pub use transition::*;
|
||||
#[doc(hidden)]
|
||||
pub use typed_builder;
|
||||
#[doc(hidden)]
|
||||
pub use typed_builder::Optional;
|
||||
#[doc(hidden)]
|
||||
pub use typed_builder_macro;
|
||||
mod children;
|
||||
pub use children::*;
|
||||
extern crate self as leptos;
|
||||
|
||||
/// The most common type for the `children` property on components,
|
||||
/// which can only be called once.
|
||||
pub type Children = Box<dyn FnOnce() -> Fragment>;
|
||||
|
||||
/// A type for the `children` property on components that can be called
|
||||
/// more than once.
|
||||
pub type ChildrenFn = Box<dyn Fn() -> 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() -> Fragment>;
|
||||
|
||||
/// A type for taking anything that implements [`IntoAttribute`].
|
||||
///
|
||||
/// ```rust
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use leptos::component;
|
||||
use leptos_dom::{Fragment, IntoView};
|
||||
use leptos::{component, ChildrenFn};
|
||||
use leptos_dom::IntoView;
|
||||
use leptos_reactive::{create_memo, signal_prelude::*};
|
||||
|
||||
/// A component that will show its children when the `when` condition is `true`,
|
||||
@@ -38,7 +38,7 @@ pub fn Show<F, W, IV>(
|
||||
/// The scope the component is running in
|
||||
|
||||
/// The components Show wraps
|
||||
children: Box<dyn Fn() -> Fragment>,
|
||||
children: ChildrenFn,
|
||||
/// A closure that returns a bool that determines whether this thing runs
|
||||
when: W,
|
||||
/// A closure that returns what gets rendered if the when statement is false
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use leptos_dom::{DynChild, HydrationCtx, IntoView};
|
||||
use leptos_macro::component;
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
use leptos_reactive::SignalGet;
|
||||
use leptos_reactive::{
|
||||
create_memo, provide_context, SignalGetUntracked, SuspenseContext,
|
||||
};
|
||||
@@ -59,14 +61,14 @@ pub fn Suspense<F, E, V>(
|
||||
/// Returns a fallback UI that will be shown while `async` [`Resource`](leptos_reactive::Resource)s are still loading.
|
||||
fallback: F,
|
||||
/// Children will be displayed once all `async` [`Resource`](leptos_reactive::Resource)s have resolved.
|
||||
children: Box<dyn Fn() -> V>,
|
||||
children: Rc<dyn Fn() -> V>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
F: Fn() -> E + 'static,
|
||||
E: IntoView,
|
||||
V: IntoView + 'static,
|
||||
{
|
||||
let orig_children = Rc::new(children);
|
||||
let orig_children = children;
|
||||
let context = SuspenseContext::new();
|
||||
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
@@ -93,6 +95,9 @@ where
|
||||
|
||||
let current_id = HydrationCtx::next_component();
|
||||
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
let ready = context.ready();
|
||||
|
||||
let child = DynChild::new({
|
||||
move || {
|
||||
// pull lazy memo before checking if context is ready
|
||||
@@ -100,7 +105,7 @@ where
|
||||
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
{
|
||||
if context.ready() {
|
||||
if ready.get() {
|
||||
children_rendered
|
||||
} else {
|
||||
fallback.get_untracked()
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use leptos_reactive::Oco;
|
||||
use std::{fmt::Debug, rc::Rc};
|
||||
|
||||
/// Describes a value that is either a static or a reactive string, i.e.,
|
||||
/// a [`String`], a [`&str`], or a reactive `Fn() -> String`.
|
||||
#[derive(Clone)]
|
||||
pub struct TextProp(Rc<dyn Fn() -> String>);
|
||||
pub struct TextProp(Rc<dyn Fn() -> Oco<'static, str>>);
|
||||
|
||||
impl TextProp {
|
||||
/// Accesses the current value of the property.
|
||||
#[inline(always)]
|
||||
pub fn get(&self) -> String {
|
||||
pub fn get(&self) -> Oco<'static, str> {
|
||||
(self.0)()
|
||||
}
|
||||
}
|
||||
@@ -21,23 +22,38 @@ impl Debug for TextProp {
|
||||
|
||||
impl From<String> for TextProp {
|
||||
fn from(s: String) -> Self {
|
||||
let s: Oco<'_, str> = Oco::Counted(Rc::from(s));
|
||||
TextProp(Rc::new(move || s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for TextProp {
|
||||
fn from(s: &str) -> Self {
|
||||
let s = s.to_string();
|
||||
impl From<&'static str> for TextProp {
|
||||
fn from(s: &'static str) -> Self {
|
||||
let s: Oco<'_, str> = s.into();
|
||||
TextProp(Rc::new(move || s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> From<F> for TextProp
|
||||
impl From<Rc<str>> for TextProp {
|
||||
fn from(s: Rc<str>) -> Self {
|
||||
let s: Oco<'_, str> = s.into();
|
||||
TextProp(Rc::new(move || s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Oco<'static, str>> for TextProp {
|
||||
fn from(s: Oco<'static, str>) -> Self {
|
||||
TextProp(Rc::new(move || s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, S> From<F> for TextProp
|
||||
where
|
||||
F: Fn() -> String + 'static,
|
||||
F: Fn() -> S + 'static,
|
||||
S: Into<Oco<'static, str>>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn from(s: F) -> Self {
|
||||
TextProp(Rc::new(s))
|
||||
TextProp(Rc::new(move || s().into()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,9 @@ where
|
||||
cfg!(feature = "csr") && first_run.get();
|
||||
let is_first_run =
|
||||
is_first_run(first_run, &suspense_context);
|
||||
first_run.set(false);
|
||||
if was_first_run {
|
||||
first_run.set(false)
|
||||
}
|
||||
|
||||
if let Some(prev_children) = &*prev_child.borrow() {
|
||||
if is_first_run || was_first_run {
|
||||
@@ -112,7 +114,7 @@ where
|
||||
}
|
||||
}
|
||||
})
|
||||
.children(Box::new(move || {
|
||||
.children(Rc::new(move || {
|
||||
let frag = children().into_view();
|
||||
|
||||
if let Some(suspense_context) = use_context::<SuspenseContext>()
|
||||
|
||||
@@ -13,7 +13,7 @@ config = "0.13.3"
|
||||
regex = "1.7.0"
|
||||
serde = { version = "1.0.151", features = ["derive"] }
|
||||
thiserror = "1.0.38"
|
||||
typed-builder = "0.14"
|
||||
typed-builder = "0.16"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["rt", "macros"] }
|
||||
|
||||
@@ -61,6 +61,11 @@ pub struct LeptosOptions {
|
||||
#[builder(default)]
|
||||
#[serde(default)]
|
||||
pub reload_external_port: Option<u32>,
|
||||
/// The protocol the Websocket watcher uses on the client: `ws` in most cases, `wss` when behind a reverse https proxy.
|
||||
/// Defaults to `ws`
|
||||
#[builder(default)]
|
||||
#[serde(default)]
|
||||
pub reload_ws_protocol: ReloadWSProtocol,
|
||||
}
|
||||
|
||||
impl LeptosOptions {
|
||||
@@ -84,7 +89,7 @@ impl LeptosOptions {
|
||||
output_name,
|
||||
site_root: env_w_default("LEPTOS_SITE_ROOT", "target/site")?,
|
||||
site_pkg_dir: env_w_default("LEPTOS_SITE_PKG_DIR", "pkg")?,
|
||||
env: Env::default(),
|
||||
env: env_from_str(env_w_default("LEPTOS_ENV", "DEV")?.as_str())?,
|
||||
site_addr: env_w_default("LEPTOS_SITE_ADDR", "127.0.0.1:3000")?
|
||||
.parse()?,
|
||||
reload_port: env_w_default("LEPTOS_RELOAD_PORT", "3001")?
|
||||
@@ -95,6 +100,9 @@ impl LeptosOptions {
|
||||
Some(val) => Some(val.parse()?),
|
||||
None => None,
|
||||
},
|
||||
reload_ws_protocol: ws_from_str(
|
||||
env_w_default("LEPTOS_RELOAD_WS_PROTOCOL", "ws")?.as_str(),
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -151,45 +159,103 @@ impl Default for Env {
|
||||
}
|
||||
}
|
||||
|
||||
fn from_str(input: &str) -> Result<Env, String> {
|
||||
fn env_from_str(input: &str) -> Result<Env, LeptosConfigError> {
|
||||
let sanitized = input.to_lowercase();
|
||||
match sanitized.as_ref() {
|
||||
"dev" | "development" => Ok(Env::DEV),
|
||||
"prod" | "production" => Ok(Env::PROD),
|
||||
_ => Err(format!(
|
||||
_ => Err(LeptosConfigError::EnvVarError(format!(
|
||||
"{input} is not a supported environment. Use either `dev` or \
|
||||
`production`.",
|
||||
)),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Env {
|
||||
type Err = ();
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
from_str(input).or_else(|_| Ok(Self::default()))
|
||||
env_from_str(input).or_else(|_| Ok(Self::default()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Env {
|
||||
fn from(str: &str) -> Self {
|
||||
from_str(str).unwrap_or_else(|err| panic!("{}", err))
|
||||
env_from_str(str).unwrap_or_else(|err| panic!("{}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Result<String, VarError>> for Env {
|
||||
fn from(input: &Result<String, VarError>) -> Self {
|
||||
match input {
|
||||
Ok(str) => from_str(str).unwrap_or_else(|err| panic!("{}", err)),
|
||||
Ok(str) => {
|
||||
env_from_str(str).unwrap_or_else(|err| panic!("{}", err))
|
||||
}
|
||||
Err(_) => Self::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Env {
|
||||
type Error = String;
|
||||
type Error = LeptosConfigError;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
from_str(s.as_str())
|
||||
env_from_str(s.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum that can be used to define the websocket protocol Leptos uses for hotreloading
|
||||
/// Defaults to `ws`.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||
pub enum ReloadWSProtocol {
|
||||
WS,
|
||||
WSS,
|
||||
}
|
||||
|
||||
impl Default for ReloadWSProtocol {
|
||||
fn default() -> Self {
|
||||
Self::WS
|
||||
}
|
||||
}
|
||||
|
||||
fn ws_from_str(input: &str) -> Result<ReloadWSProtocol, LeptosConfigError> {
|
||||
let sanitized = input.to_lowercase();
|
||||
match sanitized.as_ref() {
|
||||
"ws" | "WS" => Ok(ReloadWSProtocol::WS),
|
||||
"wss" | "WSS" => Ok(ReloadWSProtocol::WSS),
|
||||
_ => Err(LeptosConfigError::EnvVarError(format!(
|
||||
"{input} is not a supported websocket protocol. Use only `ws` or \
|
||||
`wss`.",
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ReloadWSProtocol {
|
||||
type Err = ();
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
ws_from_str(input).or_else(|_| Ok(Self::default()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ReloadWSProtocol {
|
||||
fn from(str: &str) -> Self {
|
||||
ws_from_str(str).unwrap_or_else(|err| panic!("{}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Result<String, VarError>> for ReloadWSProtocol {
|
||||
fn from(input: &Result<String, VarError>) -> Self {
|
||||
match input {
|
||||
Ok(str) => ws_from_str(str).unwrap_or_else(|err| panic!("{}", err)),
|
||||
Err(_) => Self::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ReloadWSProtocol {
|
||||
type Error = LeptosConfigError;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
ws_from_str(s.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
use crate::{env_w_default, env_wo_default, from_str, Env, LeptosOptions};
|
||||
use crate::{
|
||||
env_from_str, env_w_default, env_wo_default, ws_from_str, Env,
|
||||
LeptosOptions, ReloadWSProtocol,
|
||||
};
|
||||
use std::{net::SocketAddr, str::FromStr};
|
||||
|
||||
#[test]
|
||||
fn from_str_env() {
|
||||
assert!(matches!(from_str("dev").unwrap(), Env::DEV));
|
||||
assert!(matches!(from_str("development").unwrap(), Env::DEV));
|
||||
assert!(matches!(from_str("DEV").unwrap(), Env::DEV));
|
||||
assert!(matches!(from_str("DEVELOPMENT").unwrap(), Env::DEV));
|
||||
assert!(matches!(from_str("prod").unwrap(), Env::PROD));
|
||||
assert!(matches!(from_str("production").unwrap(), Env::PROD));
|
||||
assert!(matches!(from_str("PROD").unwrap(), Env::PROD));
|
||||
assert!(matches!(from_str("PRODUCTION").unwrap(), Env::PROD));
|
||||
assert!(from_str("TEST").is_err());
|
||||
assert!(from_str("?").is_err());
|
||||
fn env_from_str_test() {
|
||||
assert!(matches!(env_from_str("dev").unwrap(), Env::DEV));
|
||||
assert!(matches!(env_from_str("development").unwrap(), Env::DEV));
|
||||
assert!(matches!(env_from_str("DEV").unwrap(), Env::DEV));
|
||||
assert!(matches!(env_from_str("DEVELOPMENT").unwrap(), Env::DEV));
|
||||
assert!(matches!(env_from_str("prod").unwrap(), Env::PROD));
|
||||
assert!(matches!(env_from_str("production").unwrap(), Env::PROD));
|
||||
assert!(matches!(env_from_str("PROD").unwrap(), Env::PROD));
|
||||
assert!(matches!(env_from_str("PRODUCTION").unwrap(), Env::PROD));
|
||||
assert!(env_from_str("TEST").is_err());
|
||||
assert!(env_from_str("?").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ws_from_str_test() {
|
||||
assert!(matches!(ws_from_str("ws").unwrap(), ReloadWSProtocol::WS));
|
||||
assert!(matches!(ws_from_str("WS").unwrap(), ReloadWSProtocol::WS));
|
||||
assert!(matches!(ws_from_str("wss").unwrap(), ReloadWSProtocol::WSS));
|
||||
assert!(matches!(ws_from_str("WSS").unwrap(), ReloadWSProtocol::WSS));
|
||||
assert!(ws_from_str("TEST").is_err());
|
||||
assert!(ws_from_str("?").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -49,6 +62,8 @@ fn try_from_env_test() {
|
||||
std::env::set_var("LEPTOS_SITE_ADDR", "0.0.0.0:80");
|
||||
std::env::set_var("LEPTOS_RELOAD_PORT", "8080");
|
||||
std::env::set_var("LEPTOS_RELOAD_EXTERNAL_PORT", "8080");
|
||||
std::env::set_var("LEPTOS_ENV", "PROD");
|
||||
std::env::set_var("LEPTOS_RELOAD_WS_PROTOCOL", "WSS");
|
||||
|
||||
let config = LeptosOptions::try_from_env().unwrap();
|
||||
assert_eq!(config.output_name, "app_test");
|
||||
@@ -61,4 +76,6 @@ fn try_from_env_test() {
|
||||
);
|
||||
assert_eq!(config.reload_port, 8080);
|
||||
assert_eq!(config.reload_external_port, Some(8080));
|
||||
assert_eq!(config.env, Env::PROD);
|
||||
assert_eq!(config.reload_ws_protocol, ReloadWSProtocol::WSS)
|
||||
}
|
||||
|
||||
323
leptos_dom/src/callback.rs
Normal file
323
leptos_dom/src/callback.rs
Normal file
@@ -0,0 +1,323 @@
|
||||
//! Callbacks define a standard way to store functions and closures,
|
||||
//! in particular for component properties.
|
||||
//!
|
||||
//! # How to use them
|
||||
//! You can always create a callback from a closure, but the prefered way is to use `prop(into)`
|
||||
//! when you define your component:
|
||||
//! ```
|
||||
//! # use leptos::*;
|
||||
//! # use leptos::leptos_dom::{Callback, Callable};
|
||||
//! #[component]
|
||||
//! fn MyComponent(
|
||||
//! #[prop(into)] render_number: Callback<i32, String>,
|
||||
//! ) -> impl IntoView {
|
||||
//! view! {
|
||||
//! <div>
|
||||
//! {render_number.call(42)}
|
||||
//! </div>
|
||||
//! }
|
||||
//! }
|
||||
//! // now you can use it from a closure directly:
|
||||
//! fn test() -> impl IntoView {
|
||||
//! view! {
|
||||
//! <MyComponent render_number = |x: i32| x.to_string()/>
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! *Notes*:
|
||||
//! - in this example, you should use a generic type that implements `Fn(i32) -> String`.
|
||||
//! Callbacks are more usefull when you want optional generic props.
|
||||
//! - All callbacks implement the `Callable` trait. You have to write `my_callback.call(input)`
|
||||
//!
|
||||
//!
|
||||
//! # Types
|
||||
//! This modules defines:
|
||||
//! - [Callback], the most basic callback type
|
||||
//! - [SyncCallback] for scenarios when you need `Send` and `Sync`
|
||||
//! - [HtmlCallback] for a function that returns a [HtmlElement]
|
||||
//! - [ViewCallback] for a function that returns some kind of [view][IntoView]
|
||||
//!
|
||||
//! # Copying vs cloning
|
||||
//! All callbacks type defined in this module are [Clone] but not [Copy].
|
||||
//! To solve this issue, use [StoredValue]; see [StoredCallback] for more
|
||||
//! ```
|
||||
//! # use leptos::*;
|
||||
//! # use leptos::leptos_dom::{Callback, Callable};
|
||||
//! fn test() -> impl IntoView {
|
||||
//! let callback: Callback<i32, String> =
|
||||
//! Callback::new(|x: i32| x.to_string());
|
||||
//! let stored_callback = store_value(callback);
|
||||
//!
|
||||
//! view! {
|
||||
//! <div>
|
||||
//! // `stored_callback` can be moved multiple times
|
||||
//! {move || stored_callback.call(1)}
|
||||
//! {move || stored_callback.call(42)}
|
||||
//! </div>
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Note that for each callback type `T`, `StoredValue<T>` implements `Call`, so you can call them
|
||||
//! without even thinking about it.
|
||||
|
||||
use crate::{AnyElement, ElementDescriptor, HtmlElement, IntoView, View};
|
||||
use leptos_reactive::StoredValue;
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
/// A wrapper trait for calling callbacks.
|
||||
pub trait Callable<In, Out = ()> {
|
||||
/// calls the callback with the specified argument.
|
||||
fn call(&self, input: In) -> Out;
|
||||
}
|
||||
|
||||
/// The most basic leptos callback type.
|
||||
/// For how to use callbacks, see [here][crate::callback]
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// # use leptos::leptos_dom::{Callable, Callback};
|
||||
/// #[component]
|
||||
/// fn MyComponent(
|
||||
/// #[prop(into)] render_number: Callback<i32, String>,
|
||||
/// ) -> impl IntoView {
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// {render_number.call(42)}
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn test() -> impl IntoView {
|
||||
/// view! {
|
||||
/// <MyComponent render_number=move |x: i32| x.to_string()/>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Cloning
|
||||
/// See [StoredCallback]
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Callback<In, Out = ()>(Rc<dyn Fn(In) -> Out>);
|
||||
|
||||
impl<In, Out> Callback<In, Out> {
|
||||
/// creates a new callback from the function or closure
|
||||
pub fn new<F>(f: F) -> Callback<In, Out>
|
||||
where
|
||||
F: Fn(In) -> Out + 'static,
|
||||
{
|
||||
Self(Rc::new(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<In, Out> Callable<In, Out> for Callback<In, Out> {
|
||||
fn call(&self, input: In) -> Out {
|
||||
(self.0)(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, In, Out> From<F> for Callback<In, Out>
|
||||
where
|
||||
F: Fn(In) -> Out + 'static,
|
||||
{
|
||||
fn from(f: F) -> Callback<In, Out> {
|
||||
Callback::new(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// A callback type that implements `Copy`.
|
||||
/// `StoredCallback<In,Out>` is an alias for `StoredValue<Callback<In, Out>>`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// # use leptos::leptos_dom::{Callback, StoredCallback, Callable};
|
||||
/// fn test() -> impl IntoView {
|
||||
/// let callback: Callback<i32, String> =
|
||||
/// Callback::new(|x: i32| x.to_string());
|
||||
/// let stored_callback: StoredCallback<i32, String> =
|
||||
/// store_value(callback);
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// {move || stored_callback.call(1)}
|
||||
/// {move || stored_callback.call(42)}
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Note that in this example, you can replace `Callback` by `SyncCallback` or `ViewCallback`, and
|
||||
/// it will work in the same way.
|
||||
///
|
||||
///
|
||||
/// Note that a prop should never be a [StoredCallback]:
|
||||
/// you have to call [store_value][leptos_reactive::store_value] inside your component code.
|
||||
pub type StoredCallback<In, Out> = StoredValue<Callback<In, Out>>;
|
||||
|
||||
impl<F, In, Out> Callable<In, Out> for StoredValue<F>
|
||||
where
|
||||
F: Callable<In, Out>,
|
||||
{
|
||||
fn call(&self, input: In) -> Out {
|
||||
self.with_value(|cb| cb.call(input))
|
||||
}
|
||||
}
|
||||
|
||||
/// a callback type that is `Send` and `Sync` if the input type is
|
||||
#[derive(Clone)]
|
||||
pub struct SyncCallback<In, Out = ()>(Arc<dyn Fn(In) -> Out>);
|
||||
|
||||
impl<In: 'static, Out: 'static> SyncCallback<In, Out> {
|
||||
/// creates a new callback from the function or closure
|
||||
pub fn new<F>(fun: F) -> Self
|
||||
where
|
||||
F: Fn(In) -> Out + 'static,
|
||||
{
|
||||
Self(Arc::new(fun))
|
||||
}
|
||||
}
|
||||
|
||||
/// A special callback type that returns any Html element.
|
||||
/// You can use it exactly the same way as a classic callback.
|
||||
///
|
||||
/// For how to use callbacks, see [here][crate::callback]
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// # use leptos::leptos_dom::{Callable, HtmlCallback};
|
||||
/// #[component]
|
||||
/// fn MyComponent(
|
||||
/// #[prop(into)] render_number: HtmlCallback<i32>,
|
||||
/// ) -> impl IntoView {
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// {render_number.call(42)}
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// fn test() -> impl IntoView {
|
||||
/// view! {
|
||||
/// <MyComponent render_number=move |x: i32| view!{<span>{x}</span>}/>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # `HtmlCallback` with empty input type.
|
||||
/// Note that when `my_html_callback` is `HtmlCallback<()>`, you can use it more easily because it
|
||||
/// implements [IntoView]
|
||||
///
|
||||
/// view!{
|
||||
/// <div>
|
||||
/// {render_number}
|
||||
/// </div>
|
||||
/// }
|
||||
#[derive(Clone)]
|
||||
pub struct HtmlCallback<In = ()>(Rc<dyn Fn(In) -> HtmlElement<AnyElement>>);
|
||||
|
||||
impl<In> HtmlCallback<In> {
|
||||
/// creates a new callback from the function or closure
|
||||
pub fn new<F, H>(f: F) -> Self
|
||||
where
|
||||
F: Fn(In) -> HtmlElement<H> + 'static,
|
||||
H: ElementDescriptor + 'static,
|
||||
{
|
||||
Self(Rc::new(move |x| f(x).into_any()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<In> Callable<In, HtmlElement<AnyElement>> for HtmlCallback<In> {
|
||||
fn call(&self, input: In) -> HtmlElement<AnyElement> {
|
||||
(self.0)(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<In, F, H> From<F> for HtmlCallback<In>
|
||||
where
|
||||
F: Fn(In) -> HtmlElement<H> + 'static,
|
||||
H: ElementDescriptor + 'static,
|
||||
{
|
||||
fn from(f: F) -> Self {
|
||||
HtmlCallback(Rc::new(move |x| f(x).into_any()))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoView for HtmlCallback<()> {
|
||||
fn into_view(self) -> View {
|
||||
self.call(()).into_view()
|
||||
}
|
||||
}
|
||||
|
||||
/// A special callback type that returns any [`View`].
|
||||
///
|
||||
/// You can use it exactly the same way as a classic callback.
|
||||
/// For how to use callbacks, see [here][crate::callback]
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// # use leptos::leptos_dom::{ViewCallback, Callable};
|
||||
/// #[component]
|
||||
/// fn MyComponent(
|
||||
/// #[prop(into)] render_number: ViewCallback<i32>,
|
||||
/// ) -> impl IntoView {
|
||||
/// view! {
|
||||
/// <div>
|
||||
/// {render_number.call(42)}
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// fn test() -> impl IntoView {
|
||||
/// view! {
|
||||
/// <MyComponent render_number=move |x: i32| view!{<span>{x}</span>}/>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # `ViewCallback` with empty input type.
|
||||
/// Note that when `my_view_callback` is `ViewCallback<()>`, you can use it more easily because it
|
||||
/// implements [IntoView]
|
||||
///
|
||||
/// view!{
|
||||
/// <div>
|
||||
/// {render_number}
|
||||
/// </div>
|
||||
/// }
|
||||
#[derive(Clone)]
|
||||
pub struct ViewCallback<In>(Rc<dyn Fn(In) -> View>);
|
||||
|
||||
impl<In> ViewCallback<In> {
|
||||
/// creates a new callback from the function or closure
|
||||
fn new<F, V>(f: F) -> Self
|
||||
where
|
||||
F: Fn(In) -> V + 'static,
|
||||
V: IntoView + 'static,
|
||||
{
|
||||
ViewCallback(Rc::new(move |x| f(x).into_view()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<In> Callable<In, View> for ViewCallback<In> {
|
||||
fn call(&self, input: In) -> View {
|
||||
(self.0)(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<In, F, V> From<F> for ViewCallback<In>
|
||||
where
|
||||
F: Fn(In) -> V + 'static,
|
||||
V: IntoView + 'static,
|
||||
{
|
||||
fn from(f: F) -> Self {
|
||||
Self::new(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoView for ViewCallback<()> {
|
||||
fn into_view(self) -> View {
|
||||
self.call(()).into_view()
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,12 @@ pub use dyn_child::*;
|
||||
pub use each::*;
|
||||
pub use errors::*;
|
||||
pub use fragment::*;
|
||||
use leptos_reactive::untrack_with_diagnostics;
|
||||
use leptos_reactive::{untrack_with_diagnostics, Oco};
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use once_cell::unsync::OnceCell;
|
||||
use std::fmt;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use std::rc::Rc;
|
||||
use std::{borrow::Cow, fmt};
|
||||
pub use unit::*;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use wasm_bindgen::JsCast;
|
||||
@@ -55,7 +55,7 @@ pub struct ComponentRepr {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
mounted: Rc<OnceCell<()>>,
|
||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||
pub(crate) name: Cow<'static, str>,
|
||||
pub(crate) name: Oco<'static, str>,
|
||||
#[cfg(debug_assertions)]
|
||||
_opening: Comment,
|
||||
/// The children of the component.
|
||||
@@ -163,24 +163,24 @@ impl IntoView for ComponentRepr {
|
||||
impl ComponentRepr {
|
||||
/// Creates a new [`Component`].
|
||||
#[inline(always)]
|
||||
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn new(name: impl Into<Oco<'static, str>>) -> Self {
|
||||
Self::new_with_id_concrete(name.into(), HydrationCtx::id())
|
||||
}
|
||||
|
||||
/// Creates a new [`Component`] with the given hydration ID.
|
||||
#[inline(always)]
|
||||
pub fn new_with_id(
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
name: impl Into<Oco<'static, str>>,
|
||||
id: HydrationKey,
|
||||
) -> Self {
|
||||
Self::new_with_id_concrete(name.into(), id)
|
||||
}
|
||||
|
||||
fn new_with_id_concrete(name: Cow<'static, str>, id: HydrationKey) -> Self {
|
||||
fn new_with_id_concrete(name: Oco<'static, str>, id: HydrationKey) -> Self {
|
||||
let markers = (
|
||||
Comment::new(Cow::Owned(format!("</{name}>")), &id, true),
|
||||
Comment::new(format!("</{name}>"), &id, true),
|
||||
#[cfg(debug_assertions)]
|
||||
Comment::new(Cow::Owned(format!("<{name}>")), &id, false),
|
||||
Comment::new(format!("<{name}>"), &id, false),
|
||||
);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@@ -236,7 +236,7 @@ where
|
||||
V: IntoView,
|
||||
{
|
||||
id: HydrationKey,
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
children_fn: F,
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ where
|
||||
V: IntoView,
|
||||
{
|
||||
/// Creates a new component.
|
||||
pub fn new(name: impl Into<Cow<'static, str>>, f: F) -> Self {
|
||||
pub fn new(name: impl Into<Oco<'static, str>>, f: F) -> Self {
|
||||
Self {
|
||||
id: HydrationCtx::id(),
|
||||
name: name.into(),
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
Comment, IntoView, View,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use std::{borrow::Cow, cell::RefCell, fmt, ops::Deref, rc::Rc};
|
||||
use std::{cell::RefCell, fmt, ops::Deref, rc::Rc};
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
use crate::{mount_child, prepare_to_move, unmount_child, MountKind, Mountable, Text};
|
||||
@@ -83,9 +83,9 @@ impl Mountable for DynChildRepr {
|
||||
impl DynChildRepr {
|
||||
fn new_with_id(id: HydrationKey) -> Self {
|
||||
let markers = (
|
||||
Comment::new(Cow::Borrowed("</DynChild>"), &id, true),
|
||||
Comment::new("</DynChild>", &id, true),
|
||||
#[cfg(debug_assertions)]
|
||||
Comment::new(Cow::Borrowed("<DynChild>"), &id, false),
|
||||
Comment::new("<DynChild>", &id, false),
|
||||
);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use crate::hydration::HydrationKey;
|
||||
use crate::{hydration::HydrationCtx, Comment, CoreComponent, IntoView, View};
|
||||
use leptos_reactive::{as_child_of_current_owner, Disposer};
|
||||
use std::{borrow::Cow, cell::RefCell, fmt, hash::Hash, ops::Deref, rc::Rc};
|
||||
use std::{cell::RefCell, fmt, hash::Hash, ops::Deref, rc::Rc};
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use web::*;
|
||||
|
||||
@@ -79,9 +79,9 @@ impl Default for EachRepr {
|
||||
let id = HydrationCtx::id();
|
||||
|
||||
let markers = (
|
||||
Comment::new(Cow::Borrowed("</Each>"), &id, true),
|
||||
Comment::new("</Each>", &id, true),
|
||||
#[cfg(debug_assertions)]
|
||||
Comment::new(Cow::Borrowed("<Each>"), &id, false),
|
||||
Comment::new("<Each>", &id, false),
|
||||
);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@@ -224,13 +224,13 @@ impl EachItem {
|
||||
|
||||
let markers = (
|
||||
if needs_closing {
|
||||
Some(Comment::new(Cow::Borrowed("</EachItem>"), &id, true))
|
||||
Some(Comment::new("</EachItem>", &id, true))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
#[cfg(debug_assertions)]
|
||||
if needs_closing {
|
||||
Some(Comment::new(Cow::Borrowed("<EachItem>"), &id, false))
|
||||
Some(Comment::new("<EachItem>", &id, false))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod typed;
|
||||
|
||||
use std::{borrow::Cow, cell::RefCell, collections::HashSet};
|
||||
use leptos_reactive::Oco;
|
||||
use std::{cell::RefCell, collections::HashSet};
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use wasm_bindgen::{
|
||||
convert::FromWasmAbi, intern, prelude::Closure, JsCast, JsValue,
|
||||
@@ -8,7 +9,7 @@ use wasm_bindgen::{
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
pub(crate) static GLOBAL_EVENTS: RefCell<HashSet<Cow<'static, str>>> = RefCell::new(HashSet::new());
|
||||
pub(crate) static GLOBAL_EVENTS: RefCell<HashSet<Oco<'static, str>>> = RefCell::new(HashSet::new());
|
||||
}
|
||||
|
||||
// Used in template macro
|
||||
@@ -47,8 +48,8 @@ pub fn add_event_helper<E: crate::ev::EventDescriptor + 'static>(
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
pub fn add_event_listener<E>(
|
||||
target: &web_sys::Element,
|
||||
key: Cow<'static, str>,
|
||||
event_name: Cow<'static, str>,
|
||||
key: Oco<'static, str>,
|
||||
event_name: Oco<'static, str>,
|
||||
#[cfg(debug_assertions)] mut cb: Box<dyn FnMut(E)>,
|
||||
#[cfg(not(debug_assertions))] cb: Box<dyn FnMut(E)>,
|
||||
options: &Option<web_sys::AddEventListenerOptions>,
|
||||
@@ -115,7 +116,7 @@ pub(crate) fn add_event_listener_undelegated<E>(
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
pub(crate) fn add_delegated_event_listener(
|
||||
key: &str,
|
||||
event_name: Cow<'static, str>,
|
||||
event_name: Oco<'static, str>,
|
||||
options: &Option<web_sys::AddEventListenerOptions>,
|
||||
) {
|
||||
GLOBAL_EVENTS.with(|global_events| {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Types for all DOM events.
|
||||
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
use leptos_reactive::Oco;
|
||||
use std::marker::PhantomData;
|
||||
use wasm_bindgen::convert::FromWasmAbi;
|
||||
|
||||
/// A trait for converting types into [web_sys events](web_sys).
|
||||
@@ -16,10 +17,10 @@ pub trait EventDescriptor: Clone {
|
||||
const BUBBLES: bool;
|
||||
|
||||
/// The name of the event, such as `click` or `mouseover`.
|
||||
fn name(&self) -> Cow<'static, str>;
|
||||
fn name(&self) -> Oco<'static, str>;
|
||||
|
||||
/// The key used for event delegation.
|
||||
fn event_delegation_key(&self) -> Cow<'static, str>;
|
||||
fn event_delegation_key(&self) -> Oco<'static, str>;
|
||||
|
||||
/// Return the options for this type. This is only used when you create a [`Custom`] event
|
||||
/// handler.
|
||||
@@ -39,12 +40,12 @@ impl<Ev: EventDescriptor> EventDescriptor for undelegated<Ev> {
|
||||
type EventType = Ev::EventType;
|
||||
|
||||
#[inline(always)]
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
self.0.name()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn event_delegation_key(&self) -> Cow<'static, str> {
|
||||
fn event_delegation_key(&self) -> Oco<'static, str> {
|
||||
self.0.event_delegation_key()
|
||||
}
|
||||
|
||||
@@ -54,7 +55,7 @@ impl<Ev: EventDescriptor> EventDescriptor for undelegated<Ev> {
|
||||
/// A custom event.
|
||||
#[derive(Debug)]
|
||||
pub struct Custom<E: FromWasmAbi = web_sys::Event> {
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
options: Option<web_sys::AddEventListenerOptions>,
|
||||
_event_type: PhantomData<E>,
|
||||
}
|
||||
@@ -72,11 +73,11 @@ impl<E: FromWasmAbi> Clone for Custom<E> {
|
||||
impl<E: FromWasmAbi> EventDescriptor for Custom<E> {
|
||||
type EventType = E;
|
||||
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn event_delegation_key(&self) -> Cow<'static, str> {
|
||||
fn event_delegation_key(&self) -> Oco<'static, str> {
|
||||
format!("$$${}", self.name).into()
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ impl<E: FromWasmAbi> Custom<E> {
|
||||
/// Creates a custom event type that can be used within
|
||||
/// [`HtmlElement::on`](crate::HtmlElement::on), for events
|
||||
/// which are not covered in the [`ev`](crate::ev) module.
|
||||
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn new(name: impl Into<Oco<'static, str>>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
options: None,
|
||||
@@ -299,12 +300,12 @@ macro_rules! generate_event_types {
|
||||
type EventType = web_sys::$web_event;
|
||||
|
||||
#[inline(always)]
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
stringify!([< $($event)+ >]).into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn event_delegation_key(&self) -> Cow<'static, str> {
|
||||
fn event_delegation_key(&self) -> Oco<'static, str> {
|
||||
concat!("$$$", stringify!([< $($event)+ >])).into()
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ pub fn set_property(
|
||||
}
|
||||
|
||||
/// Gets the value of a property set on a DOM element.
|
||||
#[doc(hidden)]
|
||||
pub fn get_property(
|
||||
el: &web_sys::Element,
|
||||
prop_name: &str,
|
||||
@@ -245,7 +246,7 @@ pub fn set_timeout_with_handle(
|
||||
/// listeners to prevent them from firing constantly as you type.
|
||||
///
|
||||
/// ```
|
||||
/// use leptos::{leptos_dom::helpers::debounce, *};
|
||||
/// use leptos::{leptos_dom::helpers::debounce, logging::log, *};
|
||||
///
|
||||
/// #[component]
|
||||
/// fn DebouncedButton() -> impl IntoView {
|
||||
@@ -429,7 +430,7 @@ pub fn window_event_listener_untyped(
|
||||
/// Creates a window event listener from a typed event, returning a
|
||||
/// cancelable handle.
|
||||
/// ```
|
||||
/// use leptos::{leptos_dom::helpers::window_event_listener, *};
|
||||
/// use leptos::{leptos_dom::helpers::window_event_listener, logging::log, *};
|
||||
///
|
||||
/// #[component]
|
||||
/// fn App() -> impl IntoView {
|
||||
|
||||
@@ -63,15 +63,18 @@ cfg_if! {
|
||||
use crate::{
|
||||
ev::EventDescriptor,
|
||||
hydration::HydrationCtx,
|
||||
macro_helpers::{IntoAttribute, IntoClass, IntoProperty, IntoStyle},
|
||||
macro_helpers::{
|
||||
Attribute, IntoAttribute, IntoClass, IntoProperty, IntoStyle,
|
||||
},
|
||||
Element, Fragment, IntoView, NodeRef, Text, View,
|
||||
};
|
||||
use std::{borrow::Cow, fmt};
|
||||
use leptos_reactive::Oco;
|
||||
use std::fmt;
|
||||
|
||||
/// Trait which allows creating an element tag.
|
||||
pub trait ElementDescriptor: ElementDescriptorBounds {
|
||||
/// The name of the element, i.e., `div`, `p`, `custom-element`.
|
||||
fn name(&self) -> Cow<'static, str>;
|
||||
fn name(&self) -> Oco<'static, str>;
|
||||
|
||||
/// Determines if the tag is void, i.e., `<input>` and `<br>`.
|
||||
#[inline(always)]
|
||||
@@ -126,7 +129,7 @@ where
|
||||
/// Represents potentially any element.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnyElement {
|
||||
pub(crate) name: Cow<'static, str>,
|
||||
pub(crate) name: Oco<'static, str>,
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
pub(crate) element: web_sys::HtmlElement,
|
||||
pub(crate) is_void: bool,
|
||||
@@ -159,7 +162,7 @@ impl std::convert::AsRef<web_sys::HtmlElement> for AnyElement {
|
||||
}
|
||||
|
||||
impl ElementDescriptor for AnyElement {
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
@@ -178,7 +181,7 @@ impl ElementDescriptor for AnyElement {
|
||||
/// Represents a custom HTML element, such as `<my-element>`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Custom {
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
element: web_sys::HtmlElement,
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
@@ -187,7 +190,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 {
|
||||
pub fn new(name: impl Into<Oco<'static, str>>) -> Self {
|
||||
let name = name.into();
|
||||
let id = HydrationCtx::id();
|
||||
|
||||
@@ -266,7 +269,7 @@ impl std::convert::AsRef<web_sys::HtmlElement> for Custom {
|
||||
}
|
||||
|
||||
impl ElementDescriptor for Custom {
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
@@ -294,12 +297,12 @@ cfg_if! {
|
||||
#[derive(educe::Educe, Clone)]
|
||||
#[educe(Debug)]
|
||||
pub struct HtmlElement<El: ElementDescriptor> {
|
||||
pub(crate) element: El,
|
||||
pub(crate) attrs: SmallVec<[(Cow<'static, str>, Cow<'static, str>); 4]>,
|
||||
#[educe(Debug(ignore))]
|
||||
pub(crate) children: ElementChildren,
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) view_marker: Option<String>
|
||||
pub(crate) element: El,
|
||||
pub(crate) attrs: SmallVec<[(Oco<'static, str>, Oco<'static, str>); 4]>,
|
||||
#[educe(Debug(ignore))]
|
||||
pub(crate) children: ElementChildren,
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) view_marker: Option<String>
|
||||
}
|
||||
|
||||
#[derive(Clone, educe::Educe, PartialEq, Eq)]
|
||||
@@ -308,14 +311,14 @@ cfg_if! {
|
||||
#[educe(Default)]
|
||||
Empty,
|
||||
Children(Vec<View>),
|
||||
InnerHtml(Cow<'static, str>),
|
||||
InnerHtml(Oco<'static, str>),
|
||||
Chunks(Vec<StringOrView>)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
pub enum StringOrView {
|
||||
String(Cow<'static, str>),
|
||||
String(Oco<'static, str>),
|
||||
View(std::rc::Rc<dyn Fn() -> View>)
|
||||
}
|
||||
|
||||
@@ -445,7 +448,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
/// Adds an `id` to the element.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn id(self, id: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn id(self, id: impl Into<Oco<'static, str>>) -> Self {
|
||||
let id = id.into();
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@@ -575,7 +578,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
#[cfg_attr(all(target_arch = "wasm32", feature = "web"), inline(always))]
|
||||
pub fn attr(
|
||||
self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
name: impl Into<Oco<'static, str>>,
|
||||
attr: impl IntoAttribute,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
@@ -592,8 +595,6 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
{
|
||||
use crate::macro_helpers::Attribute;
|
||||
|
||||
let mut this = self;
|
||||
|
||||
let mut attr = attr.into_attribute();
|
||||
@@ -621,6 +622,18 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds multiple attributes to the element
|
||||
#[track_caller]
|
||||
pub fn attrs(
|
||||
mut self,
|
||||
attrs: impl std::iter::IntoIterator<Item = (&'static str, Attribute)>,
|
||||
) -> Self {
|
||||
for (name, value) in attrs {
|
||||
self = self.attr(name, value);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a class to an element.
|
||||
///
|
||||
/// **Note**: In the builder syntax, this will be overwritten by the `class`
|
||||
@@ -634,7 +647,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
#[track_caller]
|
||||
pub fn class(
|
||||
self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
name: impl Into<Oco<'static, str>>,
|
||||
class: impl IntoClass,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
@@ -686,7 +699,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
/// Adds a list of classes separated by ASCII whitespace to an element.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn classes(self, classes: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn classes(self, classes: impl Into<Oco<'static, str>>) -> Self {
|
||||
self.classes_inner(&classes.into())
|
||||
}
|
||||
|
||||
@@ -698,7 +711,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = C>,
|
||||
C: Into<Cow<'static, str>>,
|
||||
C: Into<Oco<'static, str>>,
|
||||
{
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
{
|
||||
@@ -708,12 +721,12 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
|
||||
leptos_reactive::create_effect(
|
||||
move |prev_classes: Option<
|
||||
SmallVec<[Cow<'static, str>; 4]>,
|
||||
SmallVec<[Oco<'static, str>; 4]>,
|
||||
>| {
|
||||
let classes = classes_signal()
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<SmallVec<[Cow<'static, str>; 4]>>(
|
||||
.collect::<SmallVec<[Oco<'static, str>; 4]>>(
|
||||
);
|
||||
|
||||
let new_classes = classes
|
||||
@@ -797,7 +810,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
#[track_caller]
|
||||
pub fn style(
|
||||
self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
name: impl Into<Oco<'static, str>>,
|
||||
style: impl IntoStyle,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
@@ -856,7 +869,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
#[track_caller]
|
||||
pub fn prop(
|
||||
self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
name: impl Into<Oco<'static, str>>,
|
||||
value: impl IntoProperty,
|
||||
) -> Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@@ -1016,7 +1029,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
/// sanitize the input to avoid a cross-site scripting (XSS)
|
||||
/// vulnerability.
|
||||
#[inline(always)]
|
||||
pub fn inner_html(self, html: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn inner_html(self, html: impl Into<Oco<'static, str>>) -> Self {
|
||||
let html = html.into();
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@@ -1103,7 +1116,7 @@ pub fn custom<El: ElementDescriptor>(el: El) -> HtmlElement<Custom> {
|
||||
|
||||
/// Creates a text node.
|
||||
#[inline(always)]
|
||||
pub fn text(text: impl Into<Cow<'static, str>>) -> Text {
|
||||
pub fn text(text: impl Into<Oco<'static, str>>) -> Text {
|
||||
Text::new(text.into())
|
||||
}
|
||||
|
||||
@@ -1190,7 +1203,7 @@ macro_rules! generate_html_tags {
|
||||
|
||||
impl ElementDescriptor for [<$tag:camel $($trailing_)?>] {
|
||||
#[inline(always)]
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
fn name(&self) -> Oco<'static, str> {
|
||||
stringify!($tag).into()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
#[cfg_attr(any(debug_assertions, feature = "ssr"), macro_use)]
|
||||
pub extern crate tracing;
|
||||
|
||||
pub mod callback;
|
||||
mod components;
|
||||
mod events;
|
||||
pub mod helpers;
|
||||
pub mod html;
|
||||
mod hydration;
|
||||
mod logging;
|
||||
/// Utilities for simple isomorphic logging to the console or terminal.
|
||||
pub mod logging;
|
||||
mod macro_helpers;
|
||||
pub mod math;
|
||||
mod node_ref;
|
||||
@@ -24,6 +26,7 @@ pub mod ssr;
|
||||
pub mod ssr_in_order;
|
||||
pub mod svg;
|
||||
mod transparent;
|
||||
pub use callback::*;
|
||||
use cfg_if::cfg_if;
|
||||
pub use components::*;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@@ -34,11 +37,11 @@ pub use events::{typed as ev, typed::EventHandler};
|
||||
pub use html::HtmlElement;
|
||||
use html::{AnyElement, ElementDescriptor};
|
||||
pub use hydration::{HydrationCtx, HydrationKey};
|
||||
use leptos_reactive::Oco;
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
use leptos_reactive::{
|
||||
MaybeProp, MaybeSignal, Memo, ReadSignal, RwSignal, Signal, SignalGet,
|
||||
};
|
||||
pub use logging::*;
|
||||
pub use macro_helpers::*;
|
||||
pub use node_ref::*;
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@@ -240,7 +243,7 @@ cfg_if! {
|
||||
pub struct Element {
|
||||
#[doc(hidden)]
|
||||
#[cfg(debug_assertions)]
|
||||
pub name: Cow<'static, str>,
|
||||
pub name: Oco<'static, str>,
|
||||
#[doc(hidden)]
|
||||
pub element: web_sys::HtmlElement,
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -261,9 +264,9 @@ cfg_if! {
|
||||
/// HTML element.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Element {
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
is_void: bool,
|
||||
attrs: SmallVec<[(Cow<'static, str>, Cow<'static, str>); 4]>,
|
||||
attrs: SmallVec<[(Oco<'static, str>, Oco<'static, str>); 4]>,
|
||||
children: ElementChildren,
|
||||
id: HydrationKey,
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -396,13 +399,13 @@ impl Element {
|
||||
struct Comment {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
node: web_sys::Node,
|
||||
content: Cow<'static, str>,
|
||||
content: Oco<'static, str>,
|
||||
}
|
||||
|
||||
impl Comment {
|
||||
#[inline]
|
||||
fn new(
|
||||
content: impl Into<Cow<'static, str>>,
|
||||
content: impl Into<Oco<'static, str>>,
|
||||
id: &HydrationKey,
|
||||
closing: bool,
|
||||
) -> Self {
|
||||
@@ -410,7 +413,7 @@ impl Comment {
|
||||
}
|
||||
|
||||
fn new_inner(
|
||||
content: Cow<'static, str>,
|
||||
content: Oco<'static, str>,
|
||||
id: &HydrationKey,
|
||||
closing: bool,
|
||||
) -> Self {
|
||||
@@ -466,12 +469,13 @@ pub struct Text {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
node: web_sys::Node,
|
||||
/// The current contents of the text node.
|
||||
pub content: Cow<'static, str>,
|
||||
pub content: Oco<'static, str>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Text {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "\"{}\"", self.content)
|
||||
fmt::Debug::fmt(&self.content, f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +488,7 @@ impl IntoView for Text {
|
||||
|
||||
impl Text {
|
||||
/// Creates a new [`Text`].
|
||||
pub fn new(content: Cow<'static, str>) -> Self {
|
||||
pub fn new(content: Oco<'static, str>) -> Self {
|
||||
Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
node: crate::document()
|
||||
@@ -849,7 +853,7 @@ where
|
||||
N: IntoView,
|
||||
{
|
||||
#[cfg(all(feature = "web", feature = "ssr"))]
|
||||
crate::console_warn(
|
||||
crate::logging::console_warn(
|
||||
"You have both `csr` and `ssr` or `hydrate` and `ssr` enabled as \
|
||||
features, which may cause issues like <Suspense/>` failing to work \
|
||||
silently.",
|
||||
|
||||
@@ -6,21 +6,21 @@ use wasm_bindgen::JsValue;
|
||||
/// or via `println!()` (if not in the browser).
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($($t:tt)*) => ($crate::console_log(&format_args!($($t)*).to_string()))
|
||||
($($t:tt)*) => ($crate::logging::console_log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
/// Uses `println!()`-style formatting to log warnings to the console (in the browser)
|
||||
/// or via `eprintln!()` (if not in the browser).
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($t:tt)*) => ($crate::console_warn(&format_args!($($t)*).to_string()))
|
||||
($($t:tt)*) => ($crate::logging::console_warn(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
/// Uses `println!()`-style formatting to log errors to the console (in the browser)
|
||||
/// or via `eprintln!()` (if not in the browser).
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($t:tt)*) => ($crate::console_error(&format_args!($($t)*).to_string()))
|
||||
($($t:tt)*) => ($crate::logging::console_error(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
/// Uses `println!()`-style formatting to log warnings to the console (in the browser)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use leptos_reactive::Oco;
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
use leptos_reactive::{
|
||||
MaybeProp, MaybeSignal, Memo, ReadSignal, RwSignal, Signal, SignalGet,
|
||||
@@ -14,11 +15,11 @@ use wasm_bindgen::UnwrapThrowExt;
|
||||
#[derive(Clone)]
|
||||
pub enum Attribute {
|
||||
/// A plain string value.
|
||||
String(Cow<'static, str>),
|
||||
String(Oco<'static, str>),
|
||||
/// A (presumably reactive) function, which will be run inside an effect to do targeted updates to the attribute.
|
||||
Fn(Rc<dyn Fn() -> Attribute>),
|
||||
/// An optional string value, which sets the attribute to the value if `Some` and removes the attribute if `None`.
|
||||
Option(Option<Cow<'static, str>>),
|
||||
Option(Option<Oco<'static, str>>),
|
||||
/// A boolean attribute, which sets the attribute if `true` and removes the attribute if `false`.
|
||||
Bool(bool),
|
||||
}
|
||||
@@ -29,7 +30,7 @@ impl Attribute {
|
||||
pub fn as_value_string(
|
||||
&self,
|
||||
attr_name: &'static str,
|
||||
) -> Cow<'static, str> {
|
||||
) -> Oco<'static, str> {
|
||||
match self {
|
||||
Attribute::String(value) => {
|
||||
format!("{attr_name}=\"{value}\"").into()
|
||||
@@ -46,14 +47,14 @@ impl Attribute {
|
||||
.map(|value| format!("{attr_name}=\"{value}\"").into())
|
||||
.unwrap_or_default(),
|
||||
Attribute::Bool(include) => {
|
||||
Cow::Borrowed(if *include { attr_name } else { "" })
|
||||
Oco::Borrowed(if *include { attr_name } else { "" })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the attribute to its HTML value at that moment, not including
|
||||
/// the attribute name, so it can be rendered on the server.
|
||||
pub fn as_nameless_value_string(&self) -> Option<Cow<'static, str>> {
|
||||
pub fn as_nameless_value_string(&self) -> Option<Oco<'static, str>> {
|
||||
match self {
|
||||
Attribute::String(value) => Some(value.clone()),
|
||||
Attribute::Fn(f) => {
|
||||
@@ -104,6 +105,7 @@ impl std::fmt::Debug for Attribute {
|
||||
pub trait IntoAttribute {
|
||||
/// Converts the object into an [`Attribute`].
|
||||
fn into_attribute(self) -> Attribute;
|
||||
|
||||
/// Helper function for dealing with `Box<dyn IntoAttribute>`.
|
||||
fn into_attribute_boxed(self: Box<Self>) -> Attribute;
|
||||
}
|
||||
@@ -148,7 +150,7 @@ impl IntoAttribute for Option<Attribute> {
|
||||
impl IntoAttribute for String {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::String(Cow::Owned(self))
|
||||
Attribute::String(Oco::Owned(self))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
@@ -157,13 +159,22 @@ impl IntoAttribute for String {
|
||||
impl IntoAttribute for &'static str {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::String(Cow::Borrowed(self))
|
||||
Attribute::String(Oco::Borrowed(self))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Cow<'static, str> {
|
||||
impl IntoAttribute for Rc<str> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::String(Oco::Counted(self))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Oco<'static, str> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::String(self)
|
||||
@@ -184,7 +195,7 @@ impl IntoAttribute for bool {
|
||||
impl IntoAttribute for Option<String> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::Option(self.map(Cow::Owned))
|
||||
Attribute::Option(self.map(Oco::Owned))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
@@ -193,13 +204,31 @@ impl IntoAttribute for Option<String> {
|
||||
impl IntoAttribute for Option<&'static str> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::Option(self.map(Cow::Borrowed))
|
||||
Attribute::Option(self.map(Oco::Borrowed))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<Rc<str>> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::Option(self.map(Oco::Counted))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<Cow<'static, str>> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::Option(self.map(Oco::from))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<Oco<'static, str>> {
|
||||
#[inline(always)]
|
||||
fn into_attribute(self) -> Attribute {
|
||||
Attribute::Option(self)
|
||||
@@ -331,7 +360,7 @@ attr_signal_type_optional!(MaybeProp<T>);
|
||||
#[inline(never)]
|
||||
pub fn attribute_helper(
|
||||
el: &web_sys::Element,
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
value: Attribute,
|
||||
) {
|
||||
use leptos_reactive::create_render_effect;
|
||||
|
||||
@@ -21,6 +21,9 @@ pub enum Class {
|
||||
pub trait IntoClass {
|
||||
/// Converts the object into a [`Class`].
|
||||
fn into_class(self) -> Class;
|
||||
|
||||
/// Helper function for dealing with `Box<dyn IntoClass>`.
|
||||
fn into_class_boxed(self: Box<Self>) -> Class;
|
||||
}
|
||||
|
||||
impl IntoClass for bool {
|
||||
@@ -28,6 +31,10 @@ impl IntoClass for bool {
|
||||
fn into_class(self) -> Class {
|
||||
Class::Value(self)
|
||||
}
|
||||
|
||||
fn into_class_boxed(self: Box<Self>) -> Class {
|
||||
(*self).into_class()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoClass for T
|
||||
@@ -39,6 +46,10 @@ where
|
||||
let modified_fn = Box::new(self);
|
||||
Class::Fn(modified_fn)
|
||||
}
|
||||
|
||||
fn into_class_boxed(self: Box<Self>) -> Class {
|
||||
(*self).into_class()
|
||||
}
|
||||
}
|
||||
|
||||
impl Class {
|
||||
@@ -65,14 +76,14 @@ impl Class {
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use std::borrow::Cow;
|
||||
use leptos_reactive::Oco;
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
#[doc(hidden)]
|
||||
#[inline(never)]
|
||||
pub fn class_helper(
|
||||
el: &web_sys::Element,
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
value: Class,
|
||||
) {
|
||||
use leptos_reactive::create_render_effect;
|
||||
@@ -128,6 +139,10 @@ macro_rules! class_signal_type {
|
||||
let modified_fn = Box::new(move || self.get());
|
||||
Class::Fn(modified_fn)
|
||||
}
|
||||
|
||||
fn into_class_boxed(self: Box<Self>) -> Class {
|
||||
(*self).into_class()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -141,6 +156,10 @@ macro_rules! class_signal_type_optional {
|
||||
let modified_fn = Box::new(move || self.get().unwrap_or(false));
|
||||
Class::Fn(modified_fn)
|
||||
}
|
||||
|
||||
fn into_class_boxed(self: Box<Self>) -> Class {
|
||||
(*self).into_class()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use wasm_bindgen::UnwrapThrowExt;
|
||||
pub enum Property {
|
||||
/// A static JavaScript value.
|
||||
Value(JsValue),
|
||||
/// A (presumably reactive) function, which will be run inside an effect to toggle the class.
|
||||
/// A (presumably reactive) function, which will be run inside an effect to update the property.
|
||||
Fn(Box<dyn Fn() -> JsValue>),
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@ pub enum Property {
|
||||
pub trait IntoProperty {
|
||||
/// Converts the object into a [`Property`].
|
||||
fn into_property(self) -> Property;
|
||||
|
||||
/// Helper function for dealing with `Box<dyn IntoProperty>`.
|
||||
fn into_property_boxed(self: Box<Self>) -> Property;
|
||||
}
|
||||
|
||||
impl<T, U> IntoProperty for T
|
||||
@@ -36,6 +39,10 @@ where
|
||||
let modified_fn = Box::new(move || self().into());
|
||||
Property::Fn(modified_fn)
|
||||
}
|
||||
|
||||
fn into_property_boxed(self: Box<Self>) -> Property {
|
||||
(*self).into_property()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! prop_type {
|
||||
@@ -45,6 +52,10 @@ macro_rules! prop_type {
|
||||
fn into_property(self) -> Property {
|
||||
Property::Value(self.into())
|
||||
}
|
||||
|
||||
fn into_property_boxed(self: Box<Self>) -> Property {
|
||||
(*self).into_property()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoProperty for Option<$prop_type> {
|
||||
@@ -52,6 +63,10 @@ macro_rules! prop_type {
|
||||
fn into_property(self) -> Property {
|
||||
Property::Value(self.into())
|
||||
}
|
||||
|
||||
fn into_property_boxed(self: Box<Self>) -> Property {
|
||||
(*self).into_property()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -67,6 +82,10 @@ macro_rules! prop_signal_type {
|
||||
let modified_fn = Box::new(move || self.get().into());
|
||||
Property::Fn(modified_fn)
|
||||
}
|
||||
|
||||
fn into_property_boxed(self: Box<Self>) -> Property {
|
||||
(*self).into_property()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -83,6 +102,10 @@ macro_rules! prop_signal_type_optional {
|
||||
let modified_fn = Box::new(move || self.get().into());
|
||||
Property::Fn(modified_fn)
|
||||
}
|
||||
|
||||
fn into_property_boxed(self: Box<Self>) -> Property {
|
||||
(*self).into_property()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -115,13 +138,13 @@ prop_signal_type!(MaybeSignal<T>);
|
||||
prop_signal_type_optional!(MaybeProp<T>);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use std::borrow::Cow;
|
||||
use leptos_reactive::Oco;
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
#[inline(never)]
|
||||
pub(crate) fn property_helper(
|
||||
el: &web_sys::Element,
|
||||
name: Cow<'static, str>,
|
||||
name: Oco<'static, str>,
|
||||
value: Property,
|
||||
) {
|
||||
use leptos_reactive::create_render_effect;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user