mirror of
https://github.com/adoyle0/leptos-use.git
synced 2025-02-02 10:54:15 -05:00
added SSR feature
This commit is contained in:
parent
e2dcc81958
commit
bc94577ec6
58 changed files with 1475 additions and 719 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
|||
- name: Check formatting
|
||||
run: cargo fmt --check
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-features --tests -- -D warnings
|
||||
run: cargo clippy --features storage,docs,math --tests -- -D warnings
|
||||
- name: Run tests
|
||||
run: cargo test --all-features
|
||||
|
||||
|
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
@ -23,6 +23,6 @@ jobs:
|
|||
- name: Check formatting
|
||||
run: cargo fmt --check
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-features --tests -- -D warnings
|
||||
run: cargo clippy --features storage,docs,math --tests -- -D warnings
|
||||
- name: Run tests
|
||||
run: cargo test --all-features
|
||||
|
|
2
.idea/leptos-use.iml
generated
2
.idea/leptos-use.iml
generated
|
@ -43,6 +43,7 @@
|
|||
<sourceFolder url="file://$MODULE_DIR$/examples/use_document_visibility/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/examples/use_window_focus/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/examples/use_window_scroll/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/examples/ssr/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/examples/use_intl_number_format/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/examples/use_websocket/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/testtest/src" isTestSource="false" />
|
||||
|
@ -69,6 +70,7 @@
|
|||
<excludeFolder url="file://$MODULE_DIR$/examples/use_breakpoints/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/examples/use_element_visibility/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/examples/use_intersection_observer/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/examples/ssr/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/testtest/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
|
|
@ -22,6 +22,7 @@ serde = { version = "1", optional = true }
|
|||
serde_json = { version = "1", optional = true }
|
||||
paste = "1"
|
||||
lazy_static = "1"
|
||||
cfg-if = "1"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
|
@ -66,6 +67,7 @@ features = [
|
|||
docs = []
|
||||
math = ["num"]
|
||||
storage = ["serde", "serde_json", "web-sys/StorageEvent"]
|
||||
ssr = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
<p align="center">
|
||||
<a href="https://crates.io/crates/leptos-use"><img src="https://img.shields.io/crates/v/leptos-use.svg?label=&color=%232C1275" alt="Crates.io"/></a>
|
||||
<a href="https://leptos-use.rs/server_side_rendering.html"><img src="https://img.shields.io/badge/-SSR-%236a214b" alt="SSR"></a>
|
||||
<a href="https://leptos-use.rs"><img src="https://img.shields.io/badge/-docs%20%26%20demos-%239A233F" alt="Docs & Demos"></a>
|
||||
<a href="https://leptos-use.rs"><img src="https://img.shields.io/badge/-41%20functions-%23EF3939" alt="41 Functions" /></a>
|
||||
</p>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
[Introduction](introduction.md)
|
||||
[Get Started](get_started.md)
|
||||
[Functions](functions.md)
|
||||
[Server-Side Rendering](server_side_rendering.md)
|
||||
[Changelog](changelog.md)
|
||||
|
||||
# @Storage
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
<p>
|
||||
<a href="https://crates.io/crates/leptos-use"><img src="https://img.shields.io/crates/v/leptos-use.svg?label=&color=%232C1275" alt="Crates.io"/></a>
|
||||
<a href="https://leptos-use.rs/server_side_rendering.html"><img src="https://img.shields.io/badge/-SSR-%236a214b" alt="SSR"></a>
|
||||
<a href="./get_started.html"><img src="https://img.shields.io/badge/-docs%20%26%20demos-%239A233F" alt="Docs & Demos"></a>
|
||||
<a href="./functions.html"><img src="https://img.shields.io/badge/-41%20functions-%23EF3939" alt="41 Functions" /></a>
|
||||
</p>
|
||||
|
|
49
docs/book/src/server_side_rendering.md
Normal file
49
docs/book/src/server_side_rendering.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Server-Side Rendering
|
||||
|
||||
When using together with server-side rendering (SSR) you have to enable the feature `ssr` similar to
|
||||
how you do it for `leptos`.
|
||||
|
||||
In your Cargo.toml file add the following:
|
||||
|
||||
```toml
|
||||
...
|
||||
|
||||
[features]
|
||||
hydrate = [
|
||||
"leptos/hydrate",
|
||||
...
|
||||
]
|
||||
ssr = [
|
||||
...
|
||||
"leptos/ssr",
|
||||
...
|
||||
"leptos-use/ssr" # add this
|
||||
]
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Please see the `ssr` example in the examples folder for a simple working demonstration.
|
||||
|
||||
Many functions work differently on the server and on the client. If that's the case you will
|
||||
find information about these differences in their respective docs under the section "Server-Side Rendering".
|
||||
If you don't find that section, it means that the function works exactly the same on both, the client
|
||||
and the server.
|
||||
|
||||
## Functions with Target Elements
|
||||
|
||||
A lot of functions like `use_event_listener` and `use_element_size` are only useful when a target HTML/SVG element is
|
||||
available. This is not the case on the server. You can simply wrap them in `create_effect` which will cause them to
|
||||
be only called in the browser.
|
||||
|
||||
```rust
|
||||
create_effect(
|
||||
cx,
|
||||
move |_| {
|
||||
// window() doesn't work on the server
|
||||
use_event_listener(cx, window(), "resize", move |_| {
|
||||
// ...
|
||||
})
|
||||
},
|
||||
);
|
||||
```
|
|
@ -1,6 +1,2 @@
|
|||
[build]
|
||||
rustflags = ["--cfg=web_sys_unstable_apis", "--cfg=has_std"]
|
||||
|
||||
[unstable]
|
||||
build-std = ["std", "panic_abort", "core", "alloc"]
|
||||
build-std-features = ["panic_immediate_abort"]
|
|
@ -38,6 +38,10 @@ members = [
|
|||
"watch_throttled",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"ssr"
|
||||
]
|
||||
|
||||
[package]
|
||||
name = "leptos-use-examples"
|
||||
version = "0.3.3"
|
||||
|
|
14
examples/ssr/.gitignore
vendored
Normal file
14
examples/ssr/.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
pkg
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# node e2e test tools and outputs
|
||||
node_modules/
|
||||
test-results/
|
||||
end2end/playwright-report/
|
||||
playwright/.cache/
|
100
examples/ssr/Cargo.toml
Normal file
100
examples/ssr/Cargo.toml
Normal file
|
@ -0,0 +1,100 @@
|
|||
[package]
|
||||
name = "start-axum"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.6.4", optional = true }
|
||||
console_error_panic_hook = "0.1"
|
||||
console_log = "1"
|
||||
cfg-if = "1"
|
||||
leptos = { version = "0.4", features = ["nightly"] }
|
||||
leptos_axum = { version = "0.4", optional = true }
|
||||
leptos_meta = { version = "0.4", features = ["nightly"] }
|
||||
leptos_router = { version = "0.4", features = ["nightly"] }
|
||||
leptos-use = { path = "../..", features = ["storage"] }
|
||||
log = "0.4"
|
||||
simple_logger = "4"
|
||||
tokio = { version = "1.25.0", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.4", features = ["fs"], optional = true }
|
||||
wasm-bindgen = "=0.2.87"
|
||||
thiserror = "1.0.38"
|
||||
tracing = { version = "0.1.37", optional = true }
|
||||
http = "0.2.8"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tokio",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:leptos_axum",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:tracing",
|
||||
"leptos-use/ssr"
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tokio", "tower", "tower-http", "leptos_axum"]
|
||||
skip_feature_sets = [["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
output-name = "start-axum"
|
||||
|
||||
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
|
||||
site-root = "target/site"
|
||||
|
||||
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
|
||||
# Defaults to pkg
|
||||
site-pkg-dir = "pkg"
|
||||
|
||||
# [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/main.scss"
|
||||
# Assets source dir. All files found here will be copied and synchronized to site-root.
|
||||
# The assets-dir cannot have a sub directory with the same name/path as site-pkg-dir.
|
||||
#
|
||||
# Optional. Env: LEPTOS_ASSETS_DIR.
|
||||
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"
|
||||
|
||||
# The port to use for automatic reload monitoring
|
||||
reload-port = 3001
|
||||
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
|
||||
# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
|
||||
watch = false
|
||||
|
||||
# The environment Leptos will run in, usually either "DEV" or "PROD"
|
||||
env = "DEV"
|
||||
|
||||
# The features to use when compiling the bin target
|
||||
#
|
||||
# Optional. Can be over-ridden with the command line parameter --bin-features
|
||||
bin-features = ["ssr"]
|
||||
|
||||
# If the --no-default-features flag should be used when compiling the bin target
|
||||
#
|
||||
# Optional. Defaults to false.
|
||||
bin-default-features = false
|
||||
|
||||
# The features to use when compiling the lib target
|
||||
#
|
||||
# Optional. Can be over-ridden with the command line parameter --lib-features
|
||||
lib-features = ["hydrate"]
|
||||
|
||||
# If the --no-default-features flag should be used when compiling the lib target
|
||||
#
|
||||
# Optional. Defaults to false.
|
||||
lib-default-features = false
|
56
examples/ssr/README.md
Normal file
56
examples/ssr/README.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
# Leptos-Use SSR Example
|
||||
|
||||
## Running the example
|
||||
|
||||
```bash
|
||||
cargo leptos watch
|
||||
```
|
||||
|
||||
## Installing Additional Tools
|
||||
|
||||
By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools.
|
||||
|
||||
1. `rustup toolchain install nightly --allow-downgrade` - make sure you have Rust nightly
|
||||
2. `rustup target add wasm32-unknown-unknown` - add the ability to compile Rust to WebAssembly
|
||||
3. `cargo install cargo-generate` - install `cargo-generate` binary (should be installed automatically in future)
|
||||
4. `npm install -g sass` - install `dart-sass` (should be optional in future
|
||||
|
||||
## Compiling for Release
|
||||
```bash
|
||||
cargo leptos build --release
|
||||
```
|
||||
|
||||
Will generate your server binary in target/server/release and your site package in target/site
|
||||
|
||||
## Testing Your Project
|
||||
```bash
|
||||
cargo leptos end-to-end
|
||||
```
|
||||
|
||||
```bash
|
||||
cargo leptos end-to-end --release
|
||||
```
|
||||
|
||||
Cargo-leptos uses Playwright as the end-to-end test tool.
|
||||
Tests are located in end2end/tests directory.
|
||||
|
||||
## Executing a Server on a Remote Machine Without the Toolchain
|
||||
After running a `cargo leptos build --release` the minimum files needed are:
|
||||
|
||||
1. The server binary located in `target/server/release`
|
||||
2. The `site` directory and all files within located in `target/site`
|
||||
|
||||
Copy these files to your remote server. The directory structure should be:
|
||||
```text
|
||||
start-axum
|
||||
site/
|
||||
```
|
||||
Set the following environment variables (updating for your project as needed):
|
||||
```text
|
||||
LEPTOS_OUTPUT_NAME="start-axum"
|
||||
LEPTOS_SITE_ROOT="site"
|
||||
LEPTOS_SITE_PKG_DIR="pkg"
|
||||
LEPTOS_SITE_ADDR="127.0.0.1:3000"
|
||||
LEPTOS_RELOAD_PORT="3001"
|
||||
```
|
||||
Finally, run the server binary.
|
BIN
examples/ssr/public/favicon.ico
Normal file
BIN
examples/ssr/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
2
examples/ssr/rust-toolchain.toml
Normal file
2
examples/ssr/rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
79
examples/ssr/src/app.rs
Normal file
79
examples/ssr/src/app.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use crate::error_template::{AppError, ErrorTemplate};
|
||||
use leptos::ev::{keypress, KeyboardEvent};
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
use leptos_use::storage::use_local_storage;
|
||||
use leptos_use::{
|
||||
use_debounce_fn, use_event_listener, use_intl_number_format, UseIntlNumberFormatOptions,
|
||||
};
|
||||
|
||||
#[component]
|
||||
pub fn App(cx: Scope) -> impl IntoView {
|
||||
// Provides context that manages stylesheets, titles, meta tags, etc.
|
||||
provide_meta_context(cx);
|
||||
|
||||
view! {
|
||||
cx,
|
||||
|
||||
<Stylesheet id="leptos" href="/pkg/start-axum.css"/>
|
||||
|
||||
<Title text="Leptos-Use SSR Example"/>
|
||||
|
||||
<Router fallback=|cx| {
|
||||
let mut outside_errors = Errors::default();
|
||||
outside_errors.insert_with_default_key(AppError::NotFound);
|
||||
view! { cx,
|
||||
<ErrorTemplate outside_errors/>
|
||||
}
|
||||
.into_view(cx)
|
||||
}>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="" view=|cx| view! { cx, <HomePage/> }/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the home page of your application.
|
||||
#[component]
|
||||
fn HomePage(cx: Scope) -> impl IntoView {
|
||||
// Creates a reactive value to update the button
|
||||
let (count, set_count, _) = use_local_storage(cx, "count-state", 0);
|
||||
let on_click = move |_| set_count.update(|count| *count += 1);
|
||||
|
||||
let nf = use_intl_number_format(
|
||||
UseIntlNumberFormatOptions::default().locale("zh-Hans-CN-u-nu-hanidec"),
|
||||
);
|
||||
|
||||
let zh_count = nf.format::<i32>(cx, count);
|
||||
|
||||
let (key, set_key) = create_signal(cx, "".to_string());
|
||||
|
||||
create_effect(cx, move |_| {
|
||||
// window() doesn't work on the server
|
||||
let _ = use_event_listener(cx, window(), keypress, move |evt: KeyboardEvent| {
|
||||
set_key(evt.key())
|
||||
});
|
||||
});
|
||||
|
||||
let (debounce_value, set_debounce_value) = create_signal(cx, "not called");
|
||||
|
||||
let debounced_fn = use_debounce_fn(
|
||||
move || {
|
||||
set_debounce_value("called");
|
||||
},
|
||||
2000.0,
|
||||
);
|
||||
debounced_fn();
|
||||
|
||||
view! { cx,
|
||||
<h1>"Leptos-Use SSR Example"</h1>
|
||||
<button on:click=on_click>"Click Me: " { count }</button>
|
||||
<p>"Locale \"zh-Hans-CN-u-nu-hanidec\": " { zh_count }</p>
|
||||
<p>"Press any key: " { key }</p>
|
||||
<p>"Debounced called: " { debounce_value }</p>
|
||||
}
|
||||
}
|
76
examples/ssr/src/error_template.rs
Normal file
76
examples/ssr/src/error_template.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use cfg_if::cfg_if;
|
||||
use http::status::StatusCode;
|
||||
use leptos::*;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
use leptos_axum::ResponseOptions;
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
pub enum AppError {
|
||||
#[error("Not Found")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl AppError {
|
||||
pub fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
AppError::NotFound => StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A basic function to display errors served by the error boundaries.
|
||||
// Feel free to do more complicated things here than just displaying the error.
|
||||
#[component]
|
||||
pub fn ErrorTemplate(
|
||||
cx: Scope,
|
||||
#[prop(optional)] outside_errors: Option<Errors>,
|
||||
#[prop(optional)] errors: Option<RwSignal<Errors>>,
|
||||
) -> impl IntoView {
|
||||
let errors = match outside_errors {
|
||||
Some(e) => create_rw_signal(cx, e),
|
||||
None => match errors {
|
||||
Some(e) => e,
|
||||
None => panic!("No Errors found and we expected errors!"),
|
||||
},
|
||||
};
|
||||
// Get Errors from Signal
|
||||
let errors = errors.get();
|
||||
|
||||
// Downcast lets us take a type that implements `std::error::Error`
|
||||
let errors: Vec<AppError> = errors
|
||||
.into_iter()
|
||||
.filter_map(|(_k, v)| v.downcast_ref::<AppError>().cloned())
|
||||
.collect();
|
||||
println!("Errors: {errors:#?}");
|
||||
|
||||
// Only the response code for the first error is actually sent from the server
|
||||
// this may be customized by the specific application
|
||||
cfg_if! { if #[cfg(feature="ssr")] {
|
||||
let response = use_context::<ResponseOptions>(cx);
|
||||
if let Some(response) = response {
|
||||
response.set_status(errors[0].status_code());
|
||||
}
|
||||
}}
|
||||
|
||||
view! {cx,
|
||||
<h1>{if errors.len() > 1 {"Errors"} else {"Error"}}</h1>
|
||||
<For
|
||||
// a function that returns the items we're iterating over; a signal is fine
|
||||
each= move || {errors.clone().into_iter().enumerate()}
|
||||
// a unique key for each item as a reference
|
||||
key=|(index, _error)| *index
|
||||
// renders each item to a view
|
||||
view= move |cx, error| {
|
||||
let error_string = error.1.to_string();
|
||||
let error_code= error.1.status_code();
|
||||
view! {
|
||||
cx,
|
||||
<h2>{error_code.to_string()}</h2>
|
||||
<p>"Error: " {error_string}</p>
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
40
examples/ssr/src/fileserv.rs
Normal file
40
examples/ssr/src/fileserv.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
body::{boxed, Body, BoxBody},
|
||||
extract::State,
|
||||
response::IntoResponse,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
};
|
||||
use axum::response::Response as AxumResponse;
|
||||
use tower::ServiceExt;
|
||||
use tower_http::services::ServeDir;
|
||||
use leptos::*;
|
||||
use crate::app::App;
|
||||
|
||||
pub async fn file_and_error_handler(uri: Uri, State(options): State<LeptosOptions>, req: Request<Body>) -> AxumResponse {
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await.unwrap();
|
||||
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
} else {
|
||||
let handler = leptos_axum::render_app_to_stream(options.to_owned(), move |cx| view!{cx, <App/>});
|
||||
handler(req).await.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_static_file(uri: Uri, root: &str) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||
let req = Request::builder().uri(uri.clone()).body(Body::empty()).unwrap();
|
||||
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
|
||||
// This path is relative to the cargo root
|
||||
match ServeDir::new(root).oneshot(req).await {
|
||||
Ok(res) => Ok(res.map(boxed)),
|
||||
Err(err) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {err}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}}
|
21
examples/ssr/src/lib.rs
Normal file
21
examples/ssr/src/lib.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use cfg_if::cfg_if;
|
||||
pub mod app;
|
||||
pub mod error_template;
|
||||
pub mod fileserv;
|
||||
|
||||
cfg_if! { if #[cfg(feature = "hydrate")] {
|
||||
use leptos::*;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use crate::app::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
// initializes logging using the `log` crate
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
leptos::mount_to_body(move |cx| {
|
||||
view! { cx, <App/> }
|
||||
});
|
||||
}
|
||||
}}
|
43
examples/ssr/src/main.rs
Normal file
43
examples/ssr/src/main.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::{routing::post, Router};
|
||||
use leptos::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use start_axum::app::*;
|
||||
use start_axum::fileserv::file_and_error_handler;
|
||||
|
||||
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
|
||||
|
||||
// Setting get_configuration(None) means we'll be using cargo-leptos's env values
|
||||
// For deployment these variables are:
|
||||
// <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain>
|
||||
// Alternately a file can be specified such as Some("Cargo.toml")
|
||||
// The file would need to be included with the executable when moved to deployment
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
let leptos_options = conf.leptos_options;
|
||||
let addr = leptos_options.site_addr;
|
||||
let routes = generate_route_list(|cx| view! { cx, <App/> }).await;
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
|
||||
.leptos_routes(&leptos_options, routes, |cx| view! { cx, <App/> })
|
||||
.fallback(file_and_error_handler)
|
||||
.with_state(leptos_options);
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
log!("listening on http://{}", &addr);
|
||||
axum::Server::bind(&addr)
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub fn main() {
|
||||
// no client-side main function
|
||||
// unless we want this to work with e.g., Trunk for a purely client-side app
|
||||
// see lib.rs for hydration function instead
|
||||
}
|
4
examples/ssr/style/main.scss
Normal file
4
examples/ssr/style/main.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
body {
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
16
src/lib.rs
16
src/lib.rs
|
@ -1,6 +1,8 @@
|
|||
// #![feature(doc_cfg)]
|
||||
//! Collection of essential Leptos utilities inspired by SolidJS USE / VueUse
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
pub mod core;
|
||||
#[cfg(feature = "docs")]
|
||||
pub mod docs;
|
||||
|
@ -10,15 +12,13 @@ pub mod math;
|
|||
pub mod storage;
|
||||
pub mod utils;
|
||||
|
||||
#[cfg(web_sys_unstable_apis)]
|
||||
mod use_element_size;
|
||||
#[cfg(web_sys_unstable_apis)]
|
||||
mod use_resize_observer;
|
||||
cfg_if! { if #[cfg(web_sys_unstable_apis)] {
|
||||
mod use_element_size;
|
||||
mod use_resize_observer;
|
||||
|
||||
#[cfg(web_sys_unstable_apis)]
|
||||
pub use use_element_size::*;
|
||||
#[cfg(web_sys_unstable_apis)]
|
||||
pub use use_resize_observer::*;
|
||||
pub use use_element_size::*;
|
||||
pub use use_resize_observer::*;
|
||||
}}
|
||||
|
||||
mod on_click_outside;
|
||||
mod use_active_element;
|
||||
|
|
|
@ -45,6 +45,10 @@ static IOS_WORKAROUND: RwLock<bool> = RwLock::new(false);
|
|||
/// which is **not** supported by IE 11, Edge 18 and below.
|
||||
/// If you are targeting these browsers, we recommend you to include
|
||||
/// [this code snippet](https://gist.github.com/sibbng/13e83b1dd1b733317ce0130ef07d4efd) on your project.
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
|
||||
pub fn on_click_outside<El, T, F>(cx: Scope, target: El, handler: F) -> impl FnOnce() + Clone
|
||||
where
|
||||
El: Clone,
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
|
||||
|
||||
use crate::utils::{CloneableFn, CloneableFnWithArg, FilterOptions};
|
||||
use crate::{
|
||||
filter_builder_methods, use_event_listener, watch_pausable_with_options, DebounceOptions,
|
||||
ThrottleOptions, WatchOptions, WatchPausableReturn,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use default_struct_builder::DefaultBuilder;
|
||||
use js_sys::Reflect;
|
||||
use leptos::*;
|
||||
|
@ -144,6 +147,10 @@ const CUSTOM_STORAGE_EVENT_NAME: &str = "leptos-use-storage";
|
|||
///
|
||||
/// You can specify `debounce` or `throttle` options for limiting writes to storage.
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this falls back to a `create_signal(cx, default)` and an empty remove function.
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// * [`use_local_storage`]
|
||||
|
@ -188,6 +195,9 @@ where
|
|||
|
||||
let (data, set_data) = create_signal(cx, defaults.get_untracked());
|
||||
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
let remove: Box<dyn CloneableFn> = Box::new(|| {});
|
||||
} else {
|
||||
let storage = storage_type.into_storage();
|
||||
|
||||
let remove: Box<dyn CloneableFn> = match storage {
|
||||
|
@ -374,6 +384,7 @@ where
|
|||
Box::new(move || {})
|
||||
}
|
||||
};
|
||||
}}
|
||||
|
||||
(data, set_data, move || remove.clone()())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
|
||||
|
||||
use crate::use_event_listener_with_options;
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::ev::{blur, focus};
|
||||
use leptos::html::{AnyElement, ToHtmlElement};
|
||||
use leptos::*;
|
||||
|
@ -27,16 +30,24 @@ use web_sys::AddEventListenerOptions;
|
|||
/// # view! { cx, }
|
||||
/// # }
|
||||
/// ```
|
||||
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this returns a `Signal` that always contains the value `None`.
|
||||
pub fn use_active_element(cx: Scope) -> Signal<Option<HtmlElement<AnyElement>>> {
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
let get_active_element = || { None };
|
||||
} else {
|
||||
let get_active_element = move || {
|
||||
document()
|
||||
.active_element()
|
||||
.map(|el| el.to_leptos_element(cx))
|
||||
};
|
||||
}}
|
||||
|
||||
let (active_element, set_active_element) = create_signal(cx, get_active_element());
|
||||
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
let mut listener_options = AddEventListenerOptions::new();
|
||||
listener_options.capture(true);
|
||||
|
||||
|
@ -63,6 +74,7 @@ pub fn use_active_element(cx: Scope) -> Signal<Option<HtmlElement<AnyElement>>>
|
|||
},
|
||||
listener_options,
|
||||
);
|
||||
}}
|
||||
|
||||
active_element.into()
|
||||
}
|
||||
|
|
|
@ -103,6 +103,11 @@ use std::hash::Hash;
|
|||
/// # view! { cx, }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Since internally this uses [`use_media_query`], which returns always `false` on the server,
|
||||
/// the returned methods also will return `false`.
|
||||
pub fn use_breakpoints<K: Eq + Hash + Debug + Clone>(
|
||||
cx: Scope,
|
||||
breakpoints: HashMap<K, u32>,
|
||||
|
|
|
@ -8,6 +8,7 @@ use std::fmt::{Display, Formatter};
|
|||
use crate::core::StorageType;
|
||||
use crate::use_preferred_dark;
|
||||
use crate::utils::CloneableFnWithArg;
|
||||
use cfg_if::cfg_if;
|
||||
use default_struct_builder::DefaultBuilder;
|
||||
use leptos::*;
|
||||
use std::marker::PhantomData;
|
||||
|
@ -88,6 +89,10 @@ use wasm_bindgen::JsCast;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this will by default return `ColorMode::Light`. Persistence is disabled, of course.
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// * [`use_dark`]
|
||||
|
@ -247,9 +252,9 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "storage"))]
|
||||
/// Color modes
|
||||
#[derive(Clone, Default, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Default, PartialEq, Eq, Hash, Debug)]
|
||||
#[cfg_attr(feature = "storage", derive(Serialize, Deserialize))]
|
||||
pub enum ColorMode {
|
||||
#[default]
|
||||
Auto,
|
||||
|
@ -258,36 +263,8 @@ pub enum ColorMode {
|
|||
Custom(String),
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "storage"))]
|
||||
fn get_store_signal(
|
||||
cx: Scope,
|
||||
initial_value: MaybeSignal<ColorMode>,
|
||||
storage_signal: Option<RwSignal<ColorMode>>,
|
||||
_storage_key: &String,
|
||||
_storage_enabled: bool,
|
||||
_storage: StorageType,
|
||||
_listen_to_storage_changes: bool,
|
||||
) -> (ReadSignal<ColorMode>, WriteSignal<ColorMode>) {
|
||||
if let Some(storage_signal) = storage_signal {
|
||||
storage_signal.split()
|
||||
} else {
|
||||
create_signal(cx, initial_value.get_untracked())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "storage")]
|
||||
/// Color modes
|
||||
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum ColorMode {
|
||||
#[default]
|
||||
Auto,
|
||||
Light,
|
||||
Dark,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
#[cfg(feature = "storage")]
|
||||
fn get_store_signal(
|
||||
cfg_if! { if #[cfg(feature = "storage")] {
|
||||
fn get_store_signal(
|
||||
cx: Scope,
|
||||
initial_value: MaybeSignal<ColorMode>,
|
||||
storage_signal: Option<RwSignal<ColorMode>>,
|
||||
|
@ -295,7 +272,7 @@ fn get_store_signal(
|
|||
storage_enabled: bool,
|
||||
storage: StorageType,
|
||||
listen_to_storage_changes: bool,
|
||||
) -> (ReadSignal<ColorMode>, WriteSignal<ColorMode>) {
|
||||
) -> (ReadSignal<ColorMode>, WriteSignal<ColorMode>) {
|
||||
if let Some(storage_signal) = storage_signal {
|
||||
storage_signal.split()
|
||||
} else if storage_enabled {
|
||||
|
@ -312,7 +289,24 @@ fn get_store_signal(
|
|||
} else {
|
||||
create_signal(cx, initial_value.get_untracked())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fn get_store_signal(
|
||||
cx: Scope,
|
||||
initial_value: MaybeSignal<ColorMode>,
|
||||
storage_signal: Option<RwSignal<ColorMode>>,
|
||||
_storage_key: &String,
|
||||
_storage_enabled: bool,
|
||||
_storage: StorageType,
|
||||
_listen_to_storage_changes: bool,
|
||||
) -> (ReadSignal<ColorMode>, WriteSignal<ColorMode>) {
|
||||
if let Some(storage_signal) = storage_signal {
|
||||
storage_signal.split()
|
||||
} else {
|
||||
create_signal(cx, initial_value.get_untracked())
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
impl Display for ColorMode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
|
||||
|
||||
use crate::core::ElementMaybeSignal;
|
||||
use crate::{use_mutation_observer_with_options, watch, watch_with_options, WatchOptions};
|
||||
use cfg_if::cfg_if;
|
||||
use default_struct_builder::DefaultBuilder;
|
||||
use leptos::*;
|
||||
use std::marker::PhantomData;
|
||||
|
@ -69,7 +72,10 @@ use wasm_bindgen::{JsCast, JsValue};
|
|||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this simply returns `create_signal(cx, options.initial_value)`.
|
||||
pub fn use_css_var(
|
||||
cx: Scope,
|
||||
prop: impl Into<MaybeSignal<String>>,
|
||||
|
@ -98,6 +104,7 @@ where
|
|||
|
||||
let (variable, set_variable) = create_signal(cx, initial_value.clone());
|
||||
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
let el_signal = (cx, target).into();
|
||||
let prop = prop.into();
|
||||
|
||||
|
@ -164,6 +171,7 @@ where
|
|||
}
|
||||
},
|
||||
);
|
||||
}}
|
||||
|
||||
(variable, set_variable)
|
||||
}
|
||||
|
@ -192,7 +200,19 @@ where
|
|||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl Default for UseCssVarOptions<web_sys::Element, web_sys::Element> {
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
impl Default for UseCssVarOptions<Option<web_sys::Element>, web_sys::Element> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
target: None,
|
||||
initial_value: "".into(),
|
||||
observe: false,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
impl Default for UseCssVarOptions<web_sys::Element, web_sys::Element> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
target: document().document_element().expect("No document element"),
|
||||
|
@ -201,4 +221,5 @@ impl Default for UseCssVarOptions<web_sys::Element, web_sys::Element> {
|
|||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -31,7 +31,6 @@ use leptos::*;
|
|||
/// # view! { cx, }
|
||||
/// # }
|
||||
/// ```
|
||||
|
||||
pub fn use_cycle_list<T, L>(
|
||||
cx: Scope,
|
||||
list: L,
|
||||
|
|
|
@ -67,6 +67,11 @@ use std::rc::Rc;
|
|||
///
|
||||
/// - [**Debounce vs Throttle**: Definitive Visual Guide](https://redd.one/blog/debounce-vs-throttle)
|
||||
/// - [Debouncing and Throttling Explained Through Examples](https://css-tricks.com/debouncing-throttling-explained-examples/)
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Internally this uses `setTimeout` which is not supported on the server. So usually calling
|
||||
/// a debounced function on the server will simply be ignored.
|
||||
pub fn use_debounce_fn<F, R>(
|
||||
func: F,
|
||||
ms: impl Into<MaybeSignal<f64>> + 'static,
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
|
||||
|
||||
use crate::use_event_listener;
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::ev::visibilitychange;
|
||||
use leptos::*;
|
||||
|
||||
|
@ -21,12 +24,24 @@ use leptos::*;
|
|||
/// # view! { cx, }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this returns a `Signal` that always contains the value `web_sys::VisibilityState::Hidden`.
|
||||
pub fn use_document_visibility(cx: Scope) -> Signal<web_sys::VisibilityState> {
|
||||
let (visibility, set_visibility) = create_signal(cx, document().visibility_state());
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
let inital_visibility = web_sys::VisibilityState::Hidden;
|
||||
} else {
|
||||
let inital_visibility = document().visibility_state();
|
||||
}}
|
||||
|
||||
let (visibility, set_visibility) = create_signal(cx, inital_visibility);
|
||||
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
let _ = use_event_listener(cx, document(), visibilitychange, move |_| {
|
||||
set_visibility.set(document().visibility_state());
|
||||
});
|
||||
}}
|
||||
|
||||
visibility.into()
|
||||
}
|
||||
|
|
|
@ -30,7 +30,10 @@ use web_sys::AddEventListenerOptions;
|
|||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
|
||||
pub fn use_element_hover<El, T>(cx: Scope, el: El) -> Signal<bool>
|
||||
where
|
||||
El: Clone,
|
||||
|
|
|
@ -39,6 +39,10 @@ use wasm_bindgen::JsCast;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// - [`use_resize_observer`]
|
||||
|
|
|
@ -31,6 +31,10 @@ use std::marker::PhantomData;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// * [`use_intersection_observer`]
|
||||
|
|
|
@ -76,11 +76,9 @@ use wasm_bindgen::JsCast;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Note if your components also run in SSR (Server Side Rendering), you might get errors
|
||||
/// because DOM APIs like document and window are not available outside of the browser.
|
||||
/// To avoid that you can put the logic inside a
|
||||
/// [`create_effect`](https://docs.rs/leptos/latest/leptos/fn.create_effect.html) hook
|
||||
/// which only runs client side.
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
|
||||
pub fn use_event_listener<Ev, El, T, F>(
|
||||
cx: Scope,
|
||||
target: El,
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
|
||||
|
||||
use crate::watch;
|
||||
use cfg_if::cfg_if;
|
||||
use default_struct_builder::DefaultBuilder;
|
||||
use leptos::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
@ -53,6 +56,9 @@ use wasm_bindgen::JsCast;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server only the signals work but no favicon will be changed obviously.
|
||||
pub fn use_favicon(cx: Scope) -> (ReadSignal<Option<String>>, WriteSignal<Option<String>>) {
|
||||
use_favicon_with_options(cx, UseFaviconOptions::default())
|
||||
}
|
||||
|
@ -68,14 +74,15 @@ pub fn use_favicon_with_options(
|
|||
rel,
|
||||
} = options;
|
||||
|
||||
let link_selector = format!("link[rel*=\"{rel}\"]");
|
||||
|
||||
let (favicon, set_favicon) = create_signal(cx, new_icon.get_untracked());
|
||||
|
||||
if matches!(&new_icon, MaybeSignal::Dynamic(_)) {
|
||||
create_effect(cx, move |_| set_favicon.set(new_icon.get()));
|
||||
}
|
||||
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
let link_selector = format!("link[rel*=\"{rel}\"]");
|
||||
|
||||
let apply_icon = move |icon: &String| {
|
||||
if let Some(head) = document().head() {
|
||||
if let Ok(links) = head.query_selector_all(&link_selector) {
|
||||
|
@ -101,6 +108,7 @@ pub fn use_favicon_with_options(
|
|||
}
|
||||
},
|
||||
);
|
||||
}}
|
||||
|
||||
(favicon, set_favicon)
|
||||
}
|
||||
|
|
|
@ -43,6 +43,10 @@ use wasm_bindgen::prelude::*;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// * [`use_element_visibility`]
|
||||
|
|
|
@ -28,6 +28,10 @@ use leptos::*;
|
|||
/// # view! { cx, }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this function will simply be ignored.
|
||||
pub fn use_interval<N>(
|
||||
cx: Scope,
|
||||
interval: N,
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
|
||||
|
||||
use crate::utils::Pausable;
|
||||
use crate::watch;
|
||||
use cfg_if::cfg_if;
|
||||
use default_struct_builder::DefaultBuilder;
|
||||
use leptos::leptos_dom::helpers::IntervalHandle;
|
||||
use leptos::*;
|
||||
|
@ -32,6 +35,10 @@ use std::time::Duration;
|
|||
/// # view! { cx, }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this function will simply be ignored.
|
||||
pub fn use_interval_fn<CbFn, N>(
|
||||
cx: Scope,
|
||||
callback: CbFn,
|
||||
|
@ -86,6 +93,7 @@ where
|
|||
let interval = interval.into();
|
||||
|
||||
let resume = move || {
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
let interval_value = interval.get();
|
||||
if interval_value == 0 {
|
||||
return;
|
||||
|
@ -101,6 +109,7 @@ where
|
|||
timer.set(
|
||||
set_interval_with_handle(callback.clone(), Duration::from_millis(interval_value)).ok(),
|
||||
);
|
||||
}}
|
||||
};
|
||||
|
||||
if immediate {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
|
||||
|
||||
use crate::utils::js_value_from_to_string;
|
||||
use cfg_if::cfg_if;
|
||||
use default_struct_builder::DefaultBuilder;
|
||||
use js_sys::Reflect;
|
||||
use leptos::*;
|
||||
|
@ -31,7 +34,7 @@ use wasm_bindgen::{JsCast, JsValue};
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ### Using locales
|
||||
/// ## Using locales
|
||||
///
|
||||
/// This example shows some of the variations in localized number formats. In order to get the format
|
||||
/// of the language used in the user interface of your application, make sure to specify that language
|
||||
|
@ -81,7 +84,7 @@ use wasm_bindgen::{JsCast, JsValue};
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ### Using options
|
||||
/// ## Using options
|
||||
///
|
||||
/// The results can be customized in multiple ways.
|
||||
///
|
||||
|
@ -143,12 +146,20 @@ use wasm_bindgen::{JsCast, JsValue};
|
|||
///
|
||||
/// For an exhaustive list of options see [`UseIntlNumberFormatOptions`](https://docs.rs/leptos_use/latest/leptos_use/struct.UseIntlNumberFormatOptions.html).
|
||||
///
|
||||
/// ### Formatting ranges
|
||||
/// ## Formatting ranges
|
||||
///
|
||||
/// Apart from the `format` method, the `format_range` method can be used to format a range of numbers.
|
||||
/// Please see [`UseIntlNumberFormatReturn::format_range`](https://docs.rs/leptos_use/latest/leptos_use/struct.UseIntlNumberFormatReturn.html#method.format_range)
|
||||
/// for details.
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Since `Intl.NumberFormat` is a JavaScript API it is not available on the server. That's why
|
||||
/// it falls back to a simple call to `format!()` on the server.
|
||||
pub fn use_intl_number_format(options: UseIntlNumberFormatOptions) -> UseIntlNumberFormatReturn {
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
UseIntlNumberFormatReturn
|
||||
} else {
|
||||
let number_format = js_sys::Intl::NumberFormat::new(
|
||||
&js_sys::Array::from_iter(options.locales.iter().map(JsValue::from)),
|
||||
&js_sys::Object::from(options),
|
||||
|
@ -157,6 +168,7 @@ pub fn use_intl_number_format(options: UseIntlNumberFormatOptions) -> UseIntlNum
|
|||
UseIntlNumberFormatReturn {
|
||||
js_intl_number_format: number_format,
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
|
@ -788,21 +800,32 @@ impl From<UseIntlNumberFormatOptions> for js_sys::Object {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return type of [`use_intl_number_format`].
|
||||
pub struct UseIntlNumberFormatReturn {
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
/// Return type of [`use_intl_number_format`].
|
||||
pub struct UseIntlNumberFormatReturn;
|
||||
} else {
|
||||
/// Return type of [`use_intl_number_format`].
|
||||
pub struct UseIntlNumberFormatReturn {
|
||||
/// The instance of [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).
|
||||
pub js_intl_number_format: js_sys::Intl::NumberFormat,
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
impl UseIntlNumberFormatReturn {
|
||||
/// Formats a number according to the [locale and formatting options](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#parameters) of this `Intl.NumberFormat` object.
|
||||
/// See [`use_intl_number_format`] for more information.
|
||||
pub fn format<N>(&self, cx: Scope, number: impl Into<MaybeSignal<N>>) -> Signal<String>
|
||||
where
|
||||
N: Clone + 'static,
|
||||
N: Clone + Display + 'static,
|
||||
js_sys::Number: From<N>,
|
||||
{
|
||||
let number = number.into();
|
||||
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
Signal::derive(cx, move || {
|
||||
format!("{}", number.get())
|
||||
})
|
||||
} else {
|
||||
let number_format = self.js_intl_number_format.clone();
|
||||
|
||||
Signal::derive(cx, move || {
|
||||
|
@ -815,6 +838,7 @@ impl UseIntlNumberFormatReturn {
|
|||
"".to_string()
|
||||
}
|
||||
})
|
||||
}}
|
||||
}
|
||||
|
||||
/// Formats a range of numbers according to the locale and formatting options of this `Intl.NumberFormat` object.
|
||||
|
@ -870,13 +894,19 @@ impl UseIntlNumberFormatReturn {
|
|||
end: impl Into<MaybeSignal<NEnd>>,
|
||||
) -> Signal<String>
|
||||
where
|
||||
NStart: Clone + 'static,
|
||||
NEnd: Clone + 'static,
|
||||
NStart: Clone + Display + 'static,
|
||||
NEnd: Clone + Display + 'static,
|
||||
js_sys::Number: From<NStart>,
|
||||
js_sys::Number: From<NEnd>,
|
||||
{
|
||||
let start = start.into();
|
||||
let end = end.into();
|
||||
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
Signal::derive(cx, move || {
|
||||
format!("{} - {}", start.get(), end.get())
|
||||
})
|
||||
} else {
|
||||
let number_format = self.js_intl_number_format.clone();
|
||||
|
||||
Signal::derive(cx, move || {
|
||||
|
@ -894,6 +924,7 @@ impl UseIntlNumberFormatReturn {
|
|||
|
||||
"".to_string()
|
||||
})
|
||||
}}
|
||||
}
|
||||
|
||||
// TODO : Allows locale-aware formatting of strings produced by this `Intl.NumberFormat` object.
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
|
||||
|
||||
use crate::use_event_listener;
|
||||
use crate::utils::CloneableFnMutWithArg;
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::ev::change;
|
||||
use leptos::*;
|
||||
use std::cell::RefCell;
|
||||
|
@ -28,6 +31,10 @@ use std::rc::Rc;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this functions returns a Signal that is always `false`.
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// * [`use_preferred_dark`]
|
||||
|
@ -37,6 +44,7 @@ pub fn use_media_query(cx: Scope, query: impl Into<MaybeSignal<String>>) -> Sign
|
|||
|
||||
let (matches, set_matches) = create_signal(cx, false);
|
||||
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
let media_query: Rc<RefCell<Option<web_sys::MediaQueryList>>> = Rc::new(RefCell::new(None));
|
||||
let remove_listener: RemoveListener = Rc::new(RefCell::new(None));
|
||||
|
||||
|
@ -87,6 +95,7 @@ pub fn use_media_query(cx: Scope, query: impl Into<MaybeSignal<String>>) -> Sign
|
|||
create_effect(cx, move |_| update());
|
||||
|
||||
on_cleanup(cx, cleanup);
|
||||
}}
|
||||
|
||||
matches.into()
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
|
||||
|
||||
use crate::core::{ElementMaybeSignal, Position};
|
||||
use crate::use_event_listener_with_options;
|
||||
use cfg_if::cfg_if;
|
||||
use default_struct_builder::DefaultBuilder;
|
||||
use leptos::ev::{dragover, mousemove, touchend, touchmove, touchstart};
|
||||
use leptos::*;
|
||||
|
@ -83,6 +86,10 @@ use web_sys::AddEventListenerOptions;
|
|||
/// view! { cx, <div node_ref=element></div> }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this returns simple `Signal`s with the `initial_value`s.
|
||||
pub fn use_mouse(cx: Scope) -> UseMouseReturn {
|
||||
use_mouse_with_options(cx, Default::default())
|
||||
}
|
||||
|
@ -154,6 +161,7 @@ where
|
|||
|
||||
// TODO : event filters?
|
||||
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
let target = options.target;
|
||||
let mut event_listener_options = AddEventListenerOptions::new();
|
||||
event_listener_options.passive(true);
|
||||
|
@ -197,6 +205,7 @@ where
|
|||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
UseMouseReturn {
|
||||
x,
|
||||
|
@ -235,7 +244,21 @@ where
|
|||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl Default for UseMouseOptions<web_sys::Window, web_sys::Window, UseMouseEventExtractorDefault> {
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
impl Default for UseMouseOptions<Option<web_sys::Window>, web_sys::Window, UseMouseEventExtractorDefault> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
coord_type: UseMouseCoordType::<UseMouseEventExtractorDefault>::default(),
|
||||
target: None,
|
||||
touch: true,
|
||||
reset_on_touch_ends: false,
|
||||
initial_value: Position { x: 0.0, y: 0.0 },
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
impl Default for UseMouseOptions<web_sys::Window, web_sys::Window, UseMouseEventExtractorDefault> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
coord_type: UseMouseCoordType::<UseMouseEventExtractorDefault>::default(),
|
||||
|
@ -246,7 +269,8 @@ impl Default for UseMouseOptions<web_sys::Window, web_sys::Window, UseMouseEvent
|
|||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
/// Defines how to get the coordinates from the event.
|
||||
#[derive(Clone)]
|
||||
|
|
|
@ -45,6 +45,10 @@ use web_sys::MutationObserverInit;
|
|||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
|
||||
pub fn use_mutation_observer<El, T, F>(
|
||||
cx: Scope,
|
||||
target: El,
|
||||
|
|
|
@ -19,6 +19,10 @@ use std::fmt::Display;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this returns a `Signal` that always contains the value `PreferredContrast::NoPreference`.
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// * [`use_media_query`]
|
||||
|
|
|
@ -18,6 +18,10 @@ use leptos::*;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this functions returns a Signal that is always `false`.
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// * [`use_media_query`]
|
||||
|
|
|
@ -43,6 +43,11 @@ use wasm_bindgen::prelude::*;
|
|||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// - [`use_element_size`]
|
||||
|
|
|
@ -164,6 +164,10 @@ use wasm_bindgen::JsCast;
|
|||
/// # }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
|
||||
pub fn use_scroll<El, T>(cx: Scope, element: El) -> UseScrollReturn
|
||||
where
|
||||
El: Clone,
|
||||
|
|
|
@ -63,6 +63,11 @@ pub use crate::utils::ThrottleOptions;
|
|||
///
|
||||
/// - [**Debounce vs Throttle**: Definitive Visual Guide](https://redd.one/blog/debounce-vs-throttle)
|
||||
/// - [Debouncing and Throttling Explained Through Examples](https://css-tricks.com/debouncing-throttling-explained-examples/)
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// Internally this uses `setTimeout` which is not supported on the server. So usually calling
|
||||
/// a throttled function on the server will simply be ignored.
|
||||
pub fn use_throttle_fn<F, R>(
|
||||
func: F,
|
||||
ms: impl Into<MaybeSignal<f64>> + 'static,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use leptos::{leptos_dom::helpers::TimeoutHandle, *};
|
||||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use core::fmt;
|
||||
use leptos::{leptos_dom::helpers::TimeoutHandle, *};
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -21,7 +23,7 @@ use crate::utils::CloneableFnWithArg;
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// # use leptos_use::websocket::*;
|
||||
/// # use leptos_use::{use_websocket, UseWebSocketReadyState, UseWebsocketReturn};
|
||||
/// #
|
||||
/// # #[component]
|
||||
/// # fn Demo(cx: Scope) -> impl IntoView {
|
||||
|
@ -73,7 +75,10 @@ use crate::utils::CloneableFnWithArg;
|
|||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
// #[doc(cfg(feature = "websocket"))]
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server the returned functions amount to noops.
|
||||
pub fn use_websocket(
|
||||
cx: Scope,
|
||||
url: String,
|
||||
|
@ -87,7 +92,6 @@ pub fn use_websocket(
|
|||
}
|
||||
|
||||
/// Version of [`use_websocket`] that takes `UseWebSocketOptions`. See [`use_websocket`] for how to use.
|
||||
// #[doc(cfg(feature = "websocket"))]
|
||||
pub fn use_websocket_with_options(
|
||||
cx: Scope,
|
||||
url: String,
|
||||
|
@ -103,24 +107,26 @@ pub fn use_websocket_with_options(
|
|||
let (message_bytes, set_message_bytes) = create_signal(cx, None);
|
||||
let ws_ref: StoredValue<Option<WebSocket>> = store_value(cx, None);
|
||||
|
||||
let reconnect_limit = options.reconnect_limit.unwrap_or(3);
|
||||
|
||||
let reconnect_timer_ref: StoredValue<Option<TimeoutHandle>> = store_value(cx, None);
|
||||
let manual = options.manual;
|
||||
|
||||
let reconnect_times_ref: StoredValue<u64> = store_value(cx, 0);
|
||||
let unmounted_ref = store_value(cx, false);
|
||||
|
||||
let connect_ref: StoredValue<Option<Rc<dyn Fn()>>> = store_value(cx, None);
|
||||
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
let on_open_ref = store_value(cx, options.on_open);
|
||||
let on_message_ref = store_value(cx, options.on_message);
|
||||
let on_message_bytes_ref = store_value(cx, options.on_message_bytes);
|
||||
let on_error_ref = store_value(cx, options.on_error);
|
||||
let on_close_ref = store_value(cx, options.on_close);
|
||||
|
||||
let reconnect_limit = options.reconnect_limit.unwrap_or(3);
|
||||
let reconnect_interval = options.reconnect_interval.unwrap_or(3 * 1000);
|
||||
|
||||
let reconnect_timer_ref: StoredValue<Option<TimeoutHandle>> = store_value(cx, None);
|
||||
let manual = options.manual;
|
||||
let protocols = options.protocols;
|
||||
|
||||
let reconnect_times_ref: StoredValue<u64> = store_value(cx, 0);
|
||||
let unmounted_ref = store_value(cx, false);
|
||||
|
||||
let connect_ref: StoredValue<Option<Rc<dyn Fn()>>> = store_value(cx, None);
|
||||
|
||||
let reconnect_ref: StoredValue<Option<Rc<dyn Fn()>>> = store_value(cx, None);
|
||||
reconnect_ref.set_value({
|
||||
let ws = ws_ref.get_value();
|
||||
|
@ -270,6 +276,7 @@ pub fn use_websocket_with_options(
|
|||
ws_ref.set_value(Some(web_socket));
|
||||
}))
|
||||
});
|
||||
}}
|
||||
|
||||
// Send text (String)
|
||||
let send = {
|
||||
|
@ -283,29 +290,26 @@ pub fn use_websocket_with_options(
|
|||
};
|
||||
|
||||
// Send bytes
|
||||
let send_bytes = {
|
||||
move |data: Vec<u8>| {
|
||||
let send_bytes = move |data: Vec<u8>| {
|
||||
if ready_state.get() == UseWebSocketReadyState::Open {
|
||||
if let Some(web_socket) = ws_ref.get_value() {
|
||||
let _ = web_socket.send_with_u8_array(&data);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Open connection
|
||||
let open = {
|
||||
move || {
|
||||
let open = move || {
|
||||
reconnect_times_ref.set_value(0);
|
||||
if let Some(connect) = connect_ref.get_value() {
|
||||
connect();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Close connection
|
||||
let close = {
|
||||
reconnect_timer_ref.set_value(None);
|
||||
|
||||
move || {
|
||||
reconnect_times_ref.set_value(reconnect_limit);
|
||||
if let Some(web_socket) = ws_ref.get_value() {
|
||||
|
@ -319,8 +323,6 @@ pub fn use_websocket_with_options(
|
|||
if !manual {
|
||||
open();
|
||||
}
|
||||
|
||||
|| ()
|
||||
});
|
||||
|
||||
// clean up (unmount)
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
|
||||
|
||||
use crate::use_event_listener;
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::ev::{blur, focus};
|
||||
use leptos::*;
|
||||
|
||||
|
@ -22,11 +25,23 @@ use leptos::*;
|
|||
/// # view! { cx, }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this returns a `Signal` that is always `true`.
|
||||
pub fn use_window_focus(cx: Scope) -> Signal<bool> {
|
||||
let (focused, set_focused) = create_signal(cx, document().has_focus().unwrap_or_default());
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
let initial_focus = true;
|
||||
} else {
|
||||
let initial_focus = document().has_focus().unwrap_or_default();
|
||||
}}
|
||||
|
||||
let (focused, set_focused) = create_signal(cx, initial_focus);
|
||||
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
let _ = use_event_listener(cx, window(), blur, move |_| set_focused.set(false));
|
||||
let _ = use_event_listener(cx, window(), focus, move |_| set_focused.set(true));
|
||||
}}
|
||||
|
||||
focused.into()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
|
||||
|
||||
use crate::use_event_listener_with_options;
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::ev::scroll;
|
||||
use leptos::*;
|
||||
use web_sys::AddEventListenerOptions;
|
||||
|
@ -22,10 +25,22 @@ use web_sys::AddEventListenerOptions;
|
|||
/// # view! { cx, }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this returns `Signal`s that are always `0.0`.
|
||||
pub fn use_window_scroll(cx: Scope) -> (Signal<f64>, Signal<f64>) {
|
||||
let (x, set_x) = create_signal(cx, window().scroll_x().unwrap_or_default());
|
||||
let (y, set_y) = create_signal(cx, window().scroll_y().unwrap_or_default());
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
let initial_x = 0.0;
|
||||
let initial_y = 0.0;
|
||||
} else {
|
||||
let initial_x = window().scroll_x().unwrap_or_default();
|
||||
let initial_y = window().scroll_y().unwrap_or_default();
|
||||
}}
|
||||
let (x, set_x) = create_signal(cx, initial_x);
|
||||
let (y, set_y) = create_signal(cx, initial_y);
|
||||
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
let mut options = AddEventListenerOptions::new();
|
||||
options.capture(false);
|
||||
options.passive(true);
|
||||
|
@ -40,6 +55,7 @@ pub fn use_window_scroll(cx: Scope) -> (Signal<f64>, Signal<f64>) {
|
|||
},
|
||||
options,
|
||||
);
|
||||
}}
|
||||
|
||||
(x.into(), y.into())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
|
||||
|
||||
use crate::utils::CloneableFnWithReturn;
|
||||
use cfg_if::cfg_if;
|
||||
use default_struct_builder::DefaultBuilder;
|
||||
use leptos::leptos_dom::helpers::TimeoutHandle;
|
||||
use leptos::{set_timeout_with_handle, MaybeSignal, SignalGetUntracked};
|
||||
|
@ -56,6 +59,7 @@ where
|
|||
return Rc::clone(&last_return_value);
|
||||
}
|
||||
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
// Create the max_timer. Clears the regular timer on invoke
|
||||
if let Some(max_duration) = max_duration {
|
||||
if max_timer.get().is_none() {
|
||||
|
@ -87,6 +91,7 @@ where
|
|||
)
|
||||
.ok(),
|
||||
);
|
||||
}}
|
||||
|
||||
Rc::clone(&last_return_value)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
|
||||
|
||||
use crate::utils::CloneableFnWithReturn;
|
||||
use cfg_if::cfg_if;
|
||||
use default_struct_builder::DefaultBuilder;
|
||||
use js_sys::Date;
|
||||
use leptos::leptos_dom::helpers::TimeoutHandle;
|
||||
|
@ -72,6 +75,7 @@ where
|
|||
last_exec.set(Date::now());
|
||||
invoke();
|
||||
} else if options.trailing {
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
let last_exec = Rc::clone(&last_exec);
|
||||
let is_leading = Rc::clone(&is_leading);
|
||||
timer.set(
|
||||
|
@ -86,8 +90,10 @@ where
|
|||
)
|
||||
.ok(),
|
||||
);
|
||||
}}
|
||||
}
|
||||
|
||||
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
||||
if !options.leading && timer.get().is_none() {
|
||||
let is_leading = Rc::clone(&is_leading);
|
||||
timer.set(
|
||||
|
@ -100,6 +106,7 @@ where
|
|||
.ok(),
|
||||
);
|
||||
}
|
||||
}}
|
||||
|
||||
is_leading.set(false);
|
||||
|
||||
|
|
|
@ -108,6 +108,12 @@ use std::rc::Rc;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this works just fine except if you throttle or debounce in which case the callback
|
||||
/// will never be called except if you set `immediate` to `true` in which case the callback will be
|
||||
/// called exactly once.
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// * [`watch_throttled`]
|
||||
|
|
|
@ -60,6 +60,12 @@ use leptos::*;
|
|||
/// - [**Debounce vs Throttle**: Definitive Visual Guide](https://redd.one/blog/debounce-vs-throttle)
|
||||
/// - [Debouncing and Throttling Explained Through Examples](https://css-tricks.com/debouncing-throttling-explained-examples/)
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server the callback
|
||||
/// will never be called except if you set `immediate` to `true` in which case the callback will be
|
||||
/// called exactly once.
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// * [`watch`]
|
||||
|
|
|
@ -44,6 +44,12 @@ use leptos::*;
|
|||
///
|
||||
/// There's also [`watch_pausable_with_options`] which takes the same options as [`watch`].
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this works just fine except if you throttle or debounce in which case the callback
|
||||
/// will never be called except if you set `immediate` to `true` in which case the callback will be
|
||||
/// called exactly once.
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// * [`watch`]
|
||||
|
|
|
@ -60,6 +60,12 @@ use leptos::*;
|
|||
/// - [**Debounce vs Throttle**: Definitive Visual Guide](https://redd.one/blog/debounce-vs-throttle)
|
||||
/// - [Debouncing and Throttling Explained Through Examples](https://css-tricks.com/debouncing-throttling-explained-examples/)
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server the callback
|
||||
/// will never be called except if you set `immediate` to `true` in which case the callback will be
|
||||
/// called exactly once.
|
||||
///
|
||||
/// ## See also
|
||||
///
|
||||
/// * [`watch`]
|
||||
|
|
|
@ -76,6 +76,12 @@ use leptos::*;
|
|||
/// # view! { cx, }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Server-Side Rendering
|
||||
///
|
||||
/// On the server this works just fine except if you throttle or debounce in which case the callback
|
||||
/// will never be called except if you set `immediate` to `true` in which case the callback will be
|
||||
/// called exactly once.
|
||||
pub fn whenever<T, DFn, CFn>(cx: Scope, source: DFn, callback: CFn) -> impl Fn() + Clone
|
||||
where
|
||||
DFn: Fn() -> bool + 'static,
|
||||
|
|
Loading…
Add table
Reference in a new issue