mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 06:42:35 -05:00
Compare commits
31 Commits
0.5.0-beta
...
arena
| 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 |
2
.github/workflows/run-cargo-make-task.yml
vendored
2
.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
|
||||
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -49,9 +49,9 @@ 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
|
||||
|
||||
@@ -62,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 |
|
||||
@@ -77,56 +76,78 @@ start_path=$(pwd)
|
||||
for path in $makefile_paths; do
|
||||
cd $path
|
||||
|
||||
test_runner=
|
||||
crate_symbols=
|
||||
|
||||
test_count=$(grep -rl -E "#\[test\]" | wc -l)
|
||||
if [ $test_count -gt 0 ]; then
|
||||
test_runner="T"
|
||||
fi
|
||||
pw_count=$(find . -name playwright.config.ts | wc -l)
|
||||
|
||||
while read -r line; do
|
||||
case $line in
|
||||
*"cucumber"*)
|
||||
test_runner=$test_runner"C"
|
||||
crate_symbols=$crate_symbols"C"
|
||||
;;
|
||||
*"rstest"*)
|
||||
test_runner=$test_runner"R"
|
||||
*"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"
|
||||
|
||||
runners=$(echo ${test_runner} | grep -o . | sort | tr -d "\n")
|
||||
# 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}${runners}${RESET}"
|
||||
elif [ ! -z $runners ]; 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}${runners}${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 = Cucumber, L = Cargo Leptos, P = Playwright, R = RS Test, T = Cargo, W = WASM${RESET}"
|
||||
echo "${ITALIC}Keys:${RESET} $c, $d, $l, $n, $p, $t, $w"
|
||||
echo
|
||||
'''
|
||||
|
||||
# ALIASES
|
||||
|
||||
[tasks.tr]
|
||||
alias = "test-runner-report"
|
||||
|
||||
@@ -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
|
||||
'''
|
||||
|
||||
@@ -12,7 +12,7 @@ args = ["-rf", "target"]
|
||||
|
||||
[tasks.clean-trunk]
|
||||
script = '''
|
||||
find . -type d -name target | xargs rm -rf
|
||||
find . -type d -name dist | xargs rm -rf
|
||||
'''
|
||||
|
||||
[tasks.clean-node_modules]
|
||||
|
||||
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,7 +3,7 @@ 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));
|
||||
|
||||
// the function name is the same as the HTML tag name
|
||||
div()
|
||||
@@ -17,18 +17,14 @@ 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: ", 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"),
|
||||
))
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -22,9 +22,6 @@ 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"]
|
||||
|
||||
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 {
|
||||
@@ -200,20 +204,10 @@ pub use typed_builder;
|
||||
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()
|
||||
|
||||
@@ -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>()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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,7 +63,9 @@ 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 leptos_reactive::Oco;
|
||||
@@ -593,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();
|
||||
@@ -622,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`
|
||||
|
||||
@@ -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"))]
|
||||
@@ -39,7 +42,6 @@ use leptos_reactive::Oco;
|
||||
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"))]
|
||||
@@ -851,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)
|
||||
|
||||
@@ -105,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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ impl std::fmt::Debug for Style {
|
||||
pub trait IntoStyle {
|
||||
/// Converts the object into a [`Style`].
|
||||
fn into_style(self) -> Style;
|
||||
|
||||
/// Helper function for dealing with `Box<dyn IntoStyle>`.
|
||||
fn into_style_boxed(self: Box<Self>) -> Style;
|
||||
}
|
||||
|
||||
impl IntoStyle for &'static str {
|
||||
@@ -48,6 +51,10 @@ impl IntoStyle for &'static str {
|
||||
fn into_style(self) -> Style {
|
||||
Style::Value(Oco::Borrowed(self))
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for String {
|
||||
@@ -55,6 +62,10 @@ impl IntoStyle for String {
|
||||
fn into_style(self) -> Style {
|
||||
Style::Value(Oco::Owned(self))
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Rc<str> {
|
||||
@@ -62,6 +73,10 @@ impl IntoStyle for Rc<str> {
|
||||
fn into_style(self) -> Style {
|
||||
Style::Value(Oco::Counted(self))
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(self).into_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Cow<'static, str> {
|
||||
@@ -69,6 +84,10 @@ impl IntoStyle for Cow<'static, str> {
|
||||
fn into_style(self) -> Style {
|
||||
Style::Value(self.into())
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Oco<'static, str> {
|
||||
@@ -76,6 +95,10 @@ impl IntoStyle for Oco<'static, str> {
|
||||
fn into_style(self) -> Style {
|
||||
Style::Value(self)
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Option<&'static str> {
|
||||
@@ -83,6 +106,10 @@ impl IntoStyle for Option<&'static str> {
|
||||
fn into_style(self) -> Style {
|
||||
Style::Option(self.map(Oco::Borrowed))
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Option<String> {
|
||||
@@ -90,6 +117,10 @@ impl IntoStyle for Option<String> {
|
||||
fn into_style(self) -> Style {
|
||||
Style::Option(self.map(Oco::Owned))
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(self).into_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Option<Rc<str>> {
|
||||
@@ -97,6 +128,10 @@ impl IntoStyle for Option<Rc<str>> {
|
||||
fn into_style(self) -> Style {
|
||||
Style::Option(self.map(Oco::Counted))
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Option<Cow<'static, str>> {
|
||||
@@ -104,6 +139,10 @@ impl IntoStyle for Option<Cow<'static, str>> {
|
||||
fn into_style(self) -> Style {
|
||||
Style::Option(self.map(Oco::from))
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Option<Oco<'static, str>> {
|
||||
@@ -111,6 +150,10 @@ impl IntoStyle for Option<Oco<'static, str>> {
|
||||
fn into_style(self) -> Style {
|
||||
Style::Option(self)
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> IntoStyle for T
|
||||
@@ -123,6 +166,10 @@ where
|
||||
let modified_fn = Rc::new(move || (self)().into_style());
|
||||
Style::Fn(modified_fn)
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl Style {
|
||||
@@ -221,12 +268,20 @@ macro_rules! style_type {
|
||||
fn into_style(self) -> Style {
|
||||
Style::Value(self.to_string().into())
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStyle for Option<$style_type> {
|
||||
fn into_style(self) -> Style {
|
||||
Style::Option(self.map(|n| n.to_string().into()))
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -242,6 +297,10 @@ macro_rules! style_signal_type {
|
||||
let modified_fn = Rc::new(move || self.get().into_style());
|
||||
Style::Fn(modified_fn)
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -258,6 +317,10 @@ macro_rules! style_signal_type_optional {
|
||||
let modified_fn = Rc::new(move || self.get().into_style());
|
||||
Style::Fn(modified_fn)
|
||||
}
|
||||
|
||||
fn into_style_boxed(self: Box<Self>) -> Style {
|
||||
(*self).into_style()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::cell::Cell;
|
||||
/// macro to create your UI.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// # use leptos::{*, logging::log};
|
||||
///
|
||||
/// use leptos::html::Input;
|
||||
///
|
||||
@@ -43,7 +43,7 @@ pub struct NodeRef<T: ElementDescriptor + 'static>(
|
||||
/// macro to create your UI.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// # use leptos::{*, logging::log};
|
||||
///
|
||||
/// use leptos::html::Input;
|
||||
///
|
||||
|
||||
@@ -365,7 +365,7 @@ impl View {
|
||||
)]
|
||||
pub fn render_to_string(self) -> Oco<'static, str> {
|
||||
#[cfg(all(feature = "web", feature = "ssr"))]
|
||||
crate::console_error(
|
||||
crate::logging::console_error(
|
||||
"\n[DANGER] You have both `csr` and `ssr` or `hydrate` and `ssr` \
|
||||
enabled as features, which may cause issues like <Suspense/>` \
|
||||
failing to work silently.\n",
|
||||
|
||||
@@ -62,7 +62,7 @@ pub fn render_to_stream_in_order_with_prefix(
|
||||
prefix: impl FnOnce() -> Oco<'static, str> + 'static,
|
||||
) -> impl Stream<Item = String> {
|
||||
#[cfg(all(feature = "web", feature = "ssr"))]
|
||||
crate::console_error(
|
||||
crate::logging::console_error(
|
||||
"\n[DANGER] You have both `csr` and `ssr` or `hydrate` and `ssr` \
|
||||
enabled as features, which may cause issues like <Suspense/>` \
|
||||
failing to work silently.\n",
|
||||
|
||||
@@ -217,6 +217,27 @@ pub(crate) fn element_to_tokens(
|
||||
None
|
||||
}
|
||||
});
|
||||
let spread_attrs = node.attributes().iter().filter_map(|node| {
|
||||
use rstml::node::NodeBlock;
|
||||
use syn::{Expr, ExprRange, RangeLimits, Stmt};
|
||||
|
||||
if let NodeAttribute::Block(NodeBlock::ValidBlock(block)) = node {
|
||||
match block.stmts.first()? {
|
||||
Stmt::Expr(
|
||||
Expr::Range(ExprRange {
|
||||
start: None,
|
||||
limits: RangeLimits::HalfOpen(_),
|
||||
end: Some(end),
|
||||
..
|
||||
}),
|
||||
_,
|
||||
) => Some(quote! { .attrs(#[allow(unused_brace)] {#end}) }),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let class_attrs = node.attributes().iter().filter_map(|node| {
|
||||
if let NodeAttribute::Attribute(node) = node {
|
||||
let name = node.key.to_string();
|
||||
@@ -332,6 +353,7 @@ pub(crate) fn element_to_tokens(
|
||||
#(#ide_helper_close_tag)*
|
||||
#name
|
||||
#(#attrs)*
|
||||
#(#spread_attrs)*
|
||||
#(#class_attrs)*
|
||||
#(#style_attrs)*
|
||||
#global_class_expr
|
||||
|
||||
@@ -124,7 +124,7 @@ pub(crate) fn component_to_tokens(
|
||||
.children({
|
||||
#(#clonables)*
|
||||
|
||||
Box::new(move || #children #view_marker)
|
||||
::leptos::ToChildren::to_children(move || #children #view_marker)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -147,11 +147,18 @@ pub(crate) fn component_to_tokens(
|
||||
}
|
||||
});
|
||||
|
||||
let generics = &node.open_tag.generics;
|
||||
let generics = if generics.lt_token.is_some() {
|
||||
quote! { ::#generics }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
#[allow(unused_mut)] // used in debug
|
||||
let mut component = quote! {
|
||||
::leptos::component_view(
|
||||
&#name,
|
||||
::leptos::component_props_builder(&#name)
|
||||
::leptos::component_props_builder(&#name #generics)
|
||||
#(#props)*
|
||||
#(#slots)*
|
||||
#children
|
||||
|
||||
@@ -522,17 +522,7 @@ pub(crate) fn event_from_attribute_node(
|
||||
|
||||
let handler = attribute_value(attr);
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let (name, name_undelegated) = parse_event(&event_name);
|
||||
|
||||
let event_type = TYPED_EVENTS
|
||||
.binary_search(&name)
|
||||
.map(|_| (name))
|
||||
.unwrap_or(CUSTOM_EVENT);
|
||||
|
||||
let Ok(event_type) = event_type.parse::<TokenStream>() else {
|
||||
abort!(attr.key, "couldn't parse event name");
|
||||
};
|
||||
let (event_type, _, name_undelegated) = parse_event_name(&event_name);
|
||||
|
||||
let event_type = if force_undelegated || name_undelegated {
|
||||
quote! { ::leptos::leptos_dom::ev::undelegated(::leptos::leptos_dom::ev::#event_type) }
|
||||
|
||||
@@ -265,6 +265,31 @@ fn element_to_tokens_ssr(
|
||||
);
|
||||
}
|
||||
}
|
||||
for attr in node.attributes() {
|
||||
use syn::{Expr, ExprRange, RangeLimits, Stmt};
|
||||
|
||||
if let NodeAttribute::Block(NodeBlock::ValidBlock(block)) = attr {
|
||||
if let Some(Stmt::Expr(
|
||||
Expr::Range(ExprRange {
|
||||
start: None,
|
||||
limits: RangeLimits::HalfOpen(_),
|
||||
end: Some(end),
|
||||
..
|
||||
}),
|
||||
_,
|
||||
)) = block.stmts.first()
|
||||
{
|
||||
// should basically be the resolved attributes, joined on spaces, placed into
|
||||
// the template
|
||||
template.push_str(" {}");
|
||||
holes.push(quote! {
|
||||
{#end}.into_iter().filter_map(|(name, attr)| {
|
||||
Some(format!("{}={}", name, ::leptos::leptos_dom::ssr::escape_attr(&attr.as_nameless_value_string()?)))
|
||||
}).collect::<Vec<_>>().join(" ")
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// insert hydration ID
|
||||
let hydration_id = if is_root {
|
||||
|
||||
@@ -130,7 +130,7 @@ pub(crate) fn slot_to_tokens(
|
||||
.children({
|
||||
#(#clonables)*
|
||||
|
||||
Box::new(move || #children #view_marker)
|
||||
::leptos::ToChildren::to_children(move || #children #view_marker)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: pretty(result)
|
||||
---
|
||||
fn view() {
|
||||
::leptos::component_view(
|
||||
&ExternalComponent,
|
||||
::leptos::component_props_builder(&ExternalComponent).build(),
|
||||
)
|
||||
.into_view()
|
||||
.on(
|
||||
::leptos::leptos_dom::ev::undelegated(
|
||||
::leptos::leptos_dom::ev::Custom::new("custom.event.clear"),
|
||||
),
|
||||
move |_: Event| set_value(0),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: result
|
||||
---
|
||||
TokenStream [
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: ExternalComponent,
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: component_props_builder,
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: ExternalComponent,
|
||||
span: bytes(11..28),
|
||||
},
|
||||
],
|
||||
},
|
||||
Punct {
|
||||
char: '.',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: build,
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [],
|
||||
},
|
||||
],
|
||||
},
|
||||
Punct {
|
||||
char: '.',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: into_view,
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [],
|
||||
},
|
||||
Punct {
|
||||
char: '.',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: on,
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: leptos_dom,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: ev,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: undelegated,
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: leptos_dom,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: ev,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: Custom,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: new,
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Literal {
|
||||
lit: "custom.event.clear",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
},
|
||||
Ident {
|
||||
sym: move,
|
||||
span: bytes(51..55),
|
||||
},
|
||||
Punct {
|
||||
char: '|',
|
||||
spacing: Alone,
|
||||
span: bytes(56..57),
|
||||
},
|
||||
Ident {
|
||||
sym: _,
|
||||
span: bytes(57..58),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(58..59),
|
||||
},
|
||||
Ident {
|
||||
sym: Event,
|
||||
span: bytes(60..65),
|
||||
},
|
||||
Punct {
|
||||
char: '|',
|
||||
spacing: Alone,
|
||||
span: bytes(65..66),
|
||||
},
|
||||
Ident {
|
||||
sym: set_value,
|
||||
span: bytes(67..76),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Literal {
|
||||
lit: 0,
|
||||
span: bytes(77..78),
|
||||
},
|
||||
],
|
||||
span: bytes(76..79),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: pretty(result)
|
||||
---
|
||||
fn view() {
|
||||
::leptos::component_view(
|
||||
&ExternalComponent,
|
||||
::leptos::component_props_builder(&ExternalComponent).build(),
|
||||
)
|
||||
.into_view()
|
||||
.on(
|
||||
::leptos::leptos_dom::ev::undelegated(
|
||||
::leptos::leptos_dom::ev::Custom::new("custom.event.clear"),
|
||||
),
|
||||
move |_: Event| set_value(0),
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user