Compare commits

..

23 commits

Author SHA1 Message Date
Maccesch
21b7389eb5 release 0.14.0-gamma1 2024-10-10 03:08:34 +02:00
Maccesch
c8236dfc37 ported use_web_lock and use_window_size examples 2024-10-10 03:00:46 +02:00
Maccesch
0d0d4616f9 chore: clippy 2024-10-09 23:11:51 +02:00
Maccesch
e35a09148f made use_drop_zone usable again 2024-10-09 21:35:25 +02:00
Maccesch
39f3be4015 finished porting use_active_element 2024-10-09 21:15:17 +02:00
Maccesch
0bdc8e16c9 fixed use_color_mode example 2024-10-09 14:15:30 +02:00
Marc-Stefan Cassola
31181c38d9
Merge pull request #174 from BakerNet/update/leptos-0.7-gamma
Fix tests & formatting
2024-10-09 11:25:02 +01:00
BakerNet
171bc334ff Fix tests 2024-10-08 22:23:15 -07:00
Marc-Stefan Cassola
c6d22e19cb
Merge pull request #173 from nikessel/leptos-0.7 2024-10-08 10:51:16 +01:00
Niklas
3f81160091 Fix import issues with leptos version 0.7.0-gamma2 2024-10-08 07:33:12 +02:00
Maccesch
c4152ca358 added total downloads badge 2024-09-24 01:24:30 +02:00
Maccesch
49f89bcb36 ported use_textarea_autosize 2024-09-15 22:30:38 +02:00
Maccesch
406b7b2e16 Merge branch 'main' into leptos-0.7
# Conflicts:
#	CHANGELOG.md
#	Cargo.toml
#	src/use_user_media.rs
#	src/use_websocket.rs
2024-09-15 20:53:59 +02:00
Maccesch
10b1a8a0e8 added use_textarea_autosize 2024-09-15 20:41:51 +02:00
Maccesch
68fd2d7567 added crates.io wasm category 2024-09-13 16:57:14 +01:00
Maccesch
ad186d101c chore: rustfmt 2024-09-11 18:21:01 +01:00
Maccesch
cd88876389 element maybe signal refactoring 2024-09-11 18:19:01 +01:00
Maccesch
874854857f release 0.14.0-beta2 2024-09-09 20:42:56 +01:00
Maccesch
bf96858d6d Merge remote-tracking branch 'origin/leptos-0.7' into leptos-0.7 2024-09-09 16:10:39 +01:00
Marc-Stefan Cassola
e28c480046
Merge pull request #171 from BakerNet/fix/notify-from-trigger
fix: notify from trigger (0.7 beta change)
2024-09-09 16:05:24 +01:00
Maccesch
82e7655d14 use_websocket now returns a signal for the websocket instance 2024-09-05 00:54:08 +01:00
Maccesch
96c907aaaa Merge branch 'main' into leptos-0.7
# Conflicts:
#	CHANGELOG.md
#	Cargo.toml
2024-09-02 23:40:32 +01:00
Maccesch
1a189cf7a4 fixed use_color_mode with cookie enabled.
fixes #170
2024-09-02 18:21:21 +01:00
101 changed files with 1848 additions and 1613 deletions

View file

@ -46,30 +46,30 @@ jobs:
run: cargo test --features math,docs,ssr,actix --doc use_locale run: cargo test --features math,docs,ssr,actix --doc use_locale
#### mdbook #### mdbook
# - name: Install mdbook I - name: Install mdbook I
# uses: taiki-e/install-action@v2 uses: taiki-e/install-action@v2
# with: with:
# tool: cargo-binstall,mdbook tool: cargo-binstall,mdbook
# - name: Install mdbook II - name: Install mdbook II
# run: | run: |
# cargo binstall -y mdbook-cmdrun cargo binstall -y mdbook-cmdrun
# cargo binstall -y trunk@0.17.5 cargo binstall -y trunk@0.17.5
# rustup target add wasm32-unknown-unknown rustup target add wasm32-unknown-unknown
# - name: Setup Pages - name: Setup Pages
# id: pages id: pages
# uses: actions/configure-pages@v3 uses: actions/configure-pages@v3
# - name: Build mdbook # TODO : run mdbook tests - name: Build mdbook # TODO : run mdbook tests
# run: | run: |
# cd docs/book cd docs/book
# mdbook build mdbook build
# python3 post_build.py python3 post_build.py
# - name: Upload artifact - name: Upload artifact
# uses: actions/upload-pages-artifact@v1 uses: actions/upload-pages-artifact@v1
# with: with:
# path: ./docs/book/book path: ./docs/book/book
# - name: Deploy book to github pages - name: Deploy book to github pages
# id: deployment id: deployment
# uses: actions/deploy-pages@v2 uses: actions/deploy-pages@v2
##### mdbook end ##### mdbook end
- name: Publish crate leptos-use - name: Publish crate leptos-use

1
.idea/leptos-use.iml generated
View file

@ -85,6 +85,7 @@
<sourceFolder url="file://$MODULE_DIR$/examples/use_web_lock/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/examples/use_web_lock/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples/use_window_size/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/examples/use_window_size/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples/aaaaa/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/examples/aaaaa/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples/use_textarea_autosize/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/examples/use_event_listener/target" /> <excludeFolder url="file://$MODULE_DIR$/examples/use_event_listener/target" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
<excludeFolder url="file://$MODULE_DIR$/docs/book/book" /> <excludeFolder url="file://$MODULE_DIR$/docs/book/book" />

View file

@ -3,11 +3,56 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.14.0-gamma1] - 2024-10-10
- Adapted to the latest changes in Leptos (thanks to @BakerNet and @nikessel)
- Fixed all the examples
- `use_active_element` ported
- `use_drop_zone` now returns `Signal<Vec<SendSignal<web_sys::File>>>` instead of `Signal<Vec<web_sys::File>, LocalStorage>`
to make it easier to use with `<For>`
## [0.14.0-beta4] - 2024-09-15
- Latest changes from version 0.13.4 and 0.13.5 ported
## [0.14.0-beta3] - 2024-09-02
### Breaking Changes 🛠
- Refactored `ElementMaybeSignal` and `ElementsMaybeSignal` to have a simpler implementation. For the vast majority
of cases this should continue to work as before.
## [0.14.0-beta2] - 2024-09-09
### Change 🔥
- Latest Leptos 0.7 beta changed the trigger trait method (thanks to @BakerNet)
- Latest changes from version 0.13.3 ported
## [0.14.0-beta1] - 2024-09-02 ## [0.14.0-beta1] - 2024-09-02
Ported everything to Leptos 0.7 Ported everything to Leptos 0.7
Some example don't run yet. Some example don't run yet.
## [0.13.5] - 2024-09-15
### New Function 🚀
- `use_textarea_autosize`
## [0.13.4] - 2024-09-05
### Fix 🍕
- `use_websocket` now returns a signal for the websocket instance so the user can actually use it. Before it always
returned `None`.
## [0.13.3] - 2024-09-02
### Fix 🍕
- Fixed `use_color_mode` with cookies enabled
## [0.13.2] - 2024-09-02 ## [0.13.2] - 2024-09-02
### Fix 🍕 ### Fix 🍕

View file

@ -1,9 +1,9 @@
[package] [package]
name = "leptos-use" name = "leptos-use"
version = "0.14.0-beta1" version = "0.14.0-gamma1"
edition = "2021" edition = "2021"
authors = ["Marc-Stefan Cassola"] authors = ["Marc-Stefan Cassola"]
categories = ["gui", "web-programming"] categories = ["gui", "web-programming", "wasm"]
description = "Collection of essential Leptos utilities inspired by React-Use / VueUse / SolidJS-USE" description = "Collection of essential Leptos utilities inspired by React-Use / VueUse / SolidJS-USE"
exclude = ["examples/", "tests/"] exclude = ["examples/", "tests/"]
keywords = ["leptos", "utilities"] keywords = ["leptos", "utilities"]
@ -26,9 +26,9 @@ http1 = { version = "1", optional = true, package = "http" }
http0_2 = { version = "0.2", optional = true, package = "http" } http0_2 = { version = "0.2", optional = true, package = "http" }
js-sys = "0.3" js-sys = "0.3"
lazy_static = "1" lazy_static = "1"
leptos = "0.7.0-beta" leptos = "0.7.0-gamma2"
leptos_axum = { optional = true, version = "0.7.0-beta" } leptos_axum = { version = "0.7.0-gamma2", optional = true }
leptos_actix = { optional = true, version = "0.7.0-beta" } leptos_actix = { version = "0.7.0-gamma2", optional = true }
leptos-spin = { version = "0.2", optional = true } leptos-spin = { version = "0.2", optional = true }
num = { version = "0.4", optional = true } num = { version = "0.4", optional = true }
paste = "1" paste = "1"
@ -47,13 +47,14 @@ codee = { version = "0.2", features = [
"prost", "prost",
] } ] }
getrandom = { version = "0.2", features = ["js"] } getrandom = { version = "0.2", features = ["js"] }
leptos_meta = { version = "0.7.0-beta" } leptos_meta = { version = "0.7.0-gamma2" }
rand = "0.8" rand = "0.8"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
unic-langid = { version = "0.9", features = ["macros"] } unic-langid = { version = "0.9", features = ["macros"] }
[features] [features]
default = [ default = [
"use_textarea_autosize",
"use_web_lock", "use_web_lock",
"use_window_size", "use_window_size",
"is_err", "is_err",
@ -128,6 +129,12 @@ default = [
"watch_with_options", "watch_with_options",
"whenever" "whenever"
] ]
use_textarea_autosize = [
"use_resize_observer",
"web-sys/CssStyleDeclaration",
"web-sys/HtmlElement",
"web-sys/HtmlTextAreaElement",
]
use_web_lock = [ use_web_lock = [
"web-sys/AbortSignal", "web-sys/AbortSignal",
"web-sys/Lock", "web-sys/Lock",

View file

@ -13,7 +13,7 @@
<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://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/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/-docs%20%26%20demos-%239A233F" alt="Docs & Demos"></a>
<a href="https://leptos-use.rs"><img src="https://img.shields.io/badge/-86%20functions-%23EF3939" alt="86 Functions" /></a> <a href="https://leptos-use.rs"><img src="https://img.shields.io/badge/-87%20functions-%23EF3939" alt="87 Functions" /></a>
</p> </p>
<br/> <br/>
@ -22,6 +22,7 @@
## Usage ## Usage
![Crates.io Total Downloads](https://img.shields.io/crates/d/leptos-use)
[![Docs](https://docs.rs/leptos-use/badge.svg)](https://docs.rs/leptos-use/) [![Docs](https://docs.rs/leptos-use/badge.svg)](https://docs.rs/leptos-use/)
[![MIT/Apache 2.0](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/synphonyte/leptos-use#license) [![MIT/Apache 2.0](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/synphonyte/leptos-use#license)
[![Build Status](https://github.com/synphonyte/leptos-use/actions/workflows/cd.yml/badge.svg)](https://github.com/synphonyte/leptos-use/actions/workflows/cd.yml) [![Build Status](https://github.com/synphonyte/leptos-use/actions/workflows/cd.yml/badge.svg)](https://github.com/synphonyte/leptos-use/actions/workflows/cd.yml)

View file

@ -51,6 +51,7 @@
- [use_preferred_dark](browser/use_preferred_dark.md) - [use_preferred_dark](browser/use_preferred_dark.md)
- [use_prefers_reduced_motion](browser/use_prefers_reduced_motion.md) - [use_prefers_reduced_motion](browser/use_prefers_reduced_motion.md)
- [use_service_worker](browser/use_service_worker.md) - [use_service_worker](browser/use_service_worker.md)
- [use_textarea_autosize](browser/use_textarea_autosize.md)
- [use_user_media](browser/use_user_media.md) - [use_user_media](browser/use_user_media.md)
- [use_web_lock](browser/use_web_lock.md) - [use_web_lock](browser/use_web_lock.md)
- [use_web_notification](browser/use_web_notification.md) - [use_web_notification](browser/use_web_notification.md)

View file

@ -0,0 +1,3 @@
# use_textarea_autosize
<!-- cmdrun python3 ../extract_doc_comment.py use_textarea_autosize use_textarea_autosize -->

View file

@ -79,7 +79,8 @@
.demo-container input[type="text"], .demo-container input[type="text"],
.demo-container input[type="search"], .demo-container input[type="search"],
.demo-container input[type="number"] { .demo-container input[type="number"],
.demo-container textarea {
display: block; display: block;
font-size: 90%; font-size: 90%;
padding: .5em 1em .4em; padding: .5em 1em .4em;
@ -96,6 +97,7 @@
.demo-container textarea { .demo-container textarea {
background: var(--bg); background: var(--bg);
border: 1px solid var(--theme-popup-border); border: 1px solid var(--theme-popup-border);
line-height: 1.5;
} }
.demo-container input[type=range], .demo-container input[type=range],

View file

@ -12,6 +12,6 @@
<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://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/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="./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/-86%20functions-%23EF3939" alt="86 Functions" /></a> <a href="./functions.html"><img src="https://img.shields.io/badge/-87%20functions-%23EF3939" alt="87 Functions" /></a>
</p> </p>
</div> </div>

View file

@ -7,11 +7,13 @@ members = [
"signal_throttled", "signal_throttled",
"sync_signal", "sync_signal",
"use_abs", "use_abs",
"use_active_element",
"use_and", "use_and",
"use_breakpoints", "use_breakpoints",
"use_broadcast_channel", "use_broadcast_channel",
"use_ceil", "use_ceil",
"use_clipboard", "use_clipboard",
"use_color_mode",
"use_cookie", "use_cookie",
"use_css_var", "use_css_var",
"use_cycle_list", "use_cycle_list",
@ -21,6 +23,7 @@ members = [
"use_display_media", "use_display_media",
"use_document_visibility", "use_document_visibility",
"use_draggable", "use_draggable",
"use_drop_zone",
"use_element_bounding", "use_element_bounding",
"use_element_hover", "use_element_hover",
"use_element_size", "use_element_size",
@ -52,6 +55,7 @@ members = [
"use_service_worker", "use_service_worker",
"use_sorted", "use_sorted",
"use_storage", "use_storage",
"use_textarea_autosize",
"use_throttle_fn", "use_throttle_fn",
"use_timeout_fn", "use_timeout_fn",
"use_timestamp", "use_timestamp",
@ -66,16 +70,13 @@ members = [
"watch_debounced", "watch_debounced",
"watch_pausable", "watch_pausable",
"watch_throttled", "watch_throttled",
# "use_active_element",
# "use_color_mode",
# "use_drop_zone",
# "use_webtransport", # "use_webtransport",
] ]
exclude = ["ssr", "use_webtransport_with_server"] exclude = ["ssr", "use_webtransport_with_server"]
[workspace.dependencies] [workspace.dependencies]
leptos = "0.7.0-beta" leptos = "0.7.0-gamma2"
codee = "0.2" codee = "0.2"
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1" console_log = "1"

View file

@ -8,16 +8,14 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
axum = { version = "0.7", optional = true } axum = { version = "0.7", optional = true }
codee.workspace = true codee = "0.2"
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1" console_log = "1"
cfg-if = "1" cfg-if = "1"
leptos = { workspace = true, features = ["nightly"] } leptos = { version = "0.7.0-beta", features = ["nightly"] }
leptos_axum = { git = "https://github.com/leptos-rs/leptos", optional = true } leptos_axum = { version = "0.7.0-beta", optional = true }
leptos_meta = { git = "https://github.com/leptos-rs/leptos", features = [ leptos_meta = "0.7.0-beta"
"nightly", leptos_router = { version = "0.7.0-beta", features = [
] }
leptos_router = { git = "https://github.com/leptos-rs/leptos", features = [
"nightly", "nightly",
] } ] }
log = "0.4" log = "0.4"
@ -46,7 +44,7 @@ features = [
] ]
[features] [features]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] hydrate = ["leptos/hydrate"]
ssr = [ ssr = [
"dep:axum", "dep:axum",
"dep:tokio", "dep:tokio",

View file

@ -1,9 +1,9 @@
use crate::error_template::{AppError, ErrorTemplate};
use codee::string::FromToStringCodec; use codee::string::FromToStringCodec;
use leptos::ev::{keypress, KeyboardEvent}; use leptos::ev::{keypress, KeyboardEvent};
use leptos::prelude::*; use leptos::prelude::*;
use leptos_meta::*; use leptos_meta::*;
use leptos_router::*; use leptos_router::components::*;
use leptos_router::path;
use leptos_use::storage::{use_local_storage, use_local_storage_with_options, UseStorageOptions}; use leptos_use::storage::{use_local_storage, use_local_storage_with_options, UseStorageOptions};
use leptos_use::{ use leptos_use::{
use_color_mode_with_options, use_cookie_with_options, use_debounce_fn, use_event_listener, use_color_mode_with_options, use_cookie_with_options, use_debounce_fn, use_event_listener,
@ -12,6 +12,25 @@ use leptos_use::{
UseIntervalReturn, UseIntlNumberFormatOptions, UseIntervalReturn, UseIntlNumberFormatOptions,
}; };
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<MetaTags/>
<AutoReload options=options.clone()/>
<HydrationScripts options/>
<link rel="stylesheet" id="leptos" href="/pkg/leptos_use_ssr.css"/>
</head>
<body>
<App/>
</body>
</html>
}
}
#[component] #[component]
pub fn App() -> impl IntoView { pub fn App() -> impl IntoView {
// Provides context that manages stylesheets, titles, meta tags, etc. // Provides context that manages stylesheets, titles, meta tags, etc.
@ -22,14 +41,10 @@ pub fn App() -> impl IntoView {
<Title text="Leptos-Use SSR Example"/> <Title text="Leptos-Use SSR Example"/>
<Router fallback=|| { <Router>
let mut outside_errors = Errors::default();
outside_errors.insert_with_default_key(AppError::NotFound);
view! { <ErrorTemplate outside_errors/> }.into_view()
}>
<main> <main>
<Routes> <Routes fallback=|| "This page could not be found.">
<Route path="" view=|| view! { <HomePage/> }/> <Route path=path!("") view=|| view! { <HomePage/> }/>
</Routes> </Routes>
</main> </main>
</Router> </Router>
@ -86,7 +101,7 @@ fn HomePage() -> impl IntoView {
let locales = use_locales(); let locales = use_locales();
view! { view! {
<Html class=move || mode.get().to_string()/> <Html {..} class=move || mode.get().to_string()/>
<h1>Leptos-Use SSR Example</h1> <h1>Leptos-Use SSR Example</h1>
<button on:click=on_click>Click Me: {count}</button> <button on:click=on_click>Click Me: {count}</button>
@ -103,7 +118,7 @@ fn HomePage() -> impl IntoView {
<pre>{move || format!("Locales:\n {}", locales().join("\n "))}</pre> <pre>{move || format!("Locales:\n {}", locales().join("\n "))}</pre>
<p>Locale zh-Hans-CN-u-nu-hanidec: {zh_count}</p> <p>Locale zh-Hans-CN-u-nu-hanidec: {zh_count}</p>
<Show when={move || count() > 0 }> <Show when={move || count() > 0 }>
<div>Greater than 0 </div> <div>Greater than 0 </div>
</Show> </Show>

View file

@ -1,74 +0,0 @@
use cfg_if::cfg_if;
use http::status::StatusCode;
use leptos::prelude::*;
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(
#[prop(optional)] outside_errors: Option<Errors>,
#[prop(optional)] errors: Option<RwSignal<Errors>>,
) -> impl IntoView {
let errors = match outside_errors {
Some(e) => create_rw_signal(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>();
if let Some(response) = response {
response.set_status(errors[0].status_code());
}
}}
view! {
<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
children=move |error| {
let error_string = error.1.to_string();
let error_code = error.1.status_code();
view! {
<h2>{error_code.to_string()}</h2>
<p>"Error: " {error_string}</p>
}
}
/>
}
}

View file

@ -1,40 +0,0 @@
use cfg_if::cfg_if;
cfg_if! { if #[cfg(feature = "ssr")] {
use axum::{
body::Body,
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::prelude::*;
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 || view!{<App/>});
handler(req).await.into_response()
}
}
async fn get_static_file(uri: Uri, root: &str) -> Result<Response<Body>, (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.into_response()),
Err(err) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {err}"),
)),
}
}
}}

View file

@ -1,21 +1,10 @@
use cfg_if::cfg_if;
pub mod app; pub mod app;
pub mod error_template;
pub mod fileserv;
cfg_if! { if #[cfg(feature = "hydrate")] { #[cfg(feature = "hydrate")]
use leptos::prelude::*; #[wasm_bindgen::prelude::wasm_bindgen]
use wasm_bindgen::prelude::wasm_bindgen; pub fn hydrate() {
use crate::app::*; use crate::app::App;
#[wasm_bindgen] console_error_panic_hook::set_once();
pub fn hydrate() { leptos::mount::hydrate_body(App);
// 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 || {
view! { <App/> }
});
}
}}

View file

@ -1,51 +1,34 @@
#[cfg(feature = "ssr")] use crate::app::*;
use axum::Router;
use leptos::{config::get_configuration, logging};
use leptos_axum::{generate_route_list, LeptosRoutes};
use leptos_use_ssr::*;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
use axum::{routing::post, Router}; simple_logger::init_with_level(log::Level::Error)
use http::{HeaderMap, HeaderName, HeaderValue}; .expect("couldn't initialize logging");
use leptos::logging::log;
use leptos::prelude::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use leptos_use_ssr::app::*;
use leptos_use_ssr::fileserv::file_and_error_handler;
use tower_default_headers::DefaultHeadersLayer;
simple_logger::init_with_level(log::Level::Info).expect("couldn't initialize logging"); // Setting this to None means we'll be using cargo-leptos and its env vars
let conf = get_configuration(None).unwrap();
// 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 leptos_options = conf.leptos_options;
let addr = leptos_options.site_addr; let addr = leptos_options.site_addr;
let routes = generate_route_list(|| view! { <App/> }); let routes = generate_route_list(App);
let mut default_headers = HeaderMap::new();
let color_header = HeaderValue::from_static("Sec-CH-Prefers-Color-Scheme");
default_headers.insert(HeaderName::from_static("accept-ch"), color_header.clone());
default_headers.insert(HeaderName::from_static("vary"), color_header.clone());
default_headers.insert(HeaderName::from_static("critical-ch"), color_header);
// build our application with a route // build our application with a route
let app = Router::new() let app = Router::new()
.route("/api/*fn_name", post(leptos_axum::handle_server_fns)) .leptos_routes(&leptos_options, routes, {
.leptos_routes(&leptos_options, routes, || view! { <App/> }) let leptos_options = leptos_options.clone();
.fallback(file_and_error_handler) move || shell(leptos_options.clone())
.with_state(leptos_options) })
.layer(DefaultHeadersLayer::new(default_headers)); .fallback(leptos_axum::file_and_error_handler(shell))
.with_state(leptos_options);
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
log!("listening on http://{}", &addr); logging::log!("listening on http://{}", &addr);
axum::serve(listener, app.into_make_service()) axum::serve(listener, app.into_make_service())
.await .await
.unwrap(); .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
}

View file

@ -1,4 +1,5 @@
use leptos::prelude::*; use leptos::prelude::*;
use leptos::wasm_bindgen::JsCast;
use leptos_use::docs::{demo_or_body, Note}; use leptos_use::docs::{demo_or_body, Note};
use leptos_use::use_active_element; use leptos_use::use_active_element;
@ -10,7 +11,10 @@ fn Demo() -> impl IntoView {
"{:?}", "{:?}",
active_element active_element
.get() .get()
.map(|el| el.dataset().get("id")) .map(|el| el
.unchecked_ref::<web_sys::HtmlElement>()
.dataset()
.get("id"))
.unwrap_or_default() .unwrap_or_default()
) )
}; };

View file

@ -1,4 +1,3 @@
use leptos::html::html;
use leptos::prelude::*; use leptos::prelude::*;
use leptos_use::docs::{demo_or_body, Note}; use leptos_use::docs::{demo_or_body, Note};
use leptos_use::{ use leptos_use::{
@ -16,7 +15,13 @@ fn Demo() -> impl IntoView {
"navy".into(), "navy".into(),
"ayu".into(), "ayu".into(),
]) ])
.initial_value(ColorMode::from(html().class_name())), .initial_value(ColorMode::from(
document()
.query_selector("html")
.unwrap()
.unwrap()
.class_name(),
)),
); );
let UseCycleListReturn { state, next, .. } = use_cycle_list_with_options( let UseCycleListReturn { state, next, .. } = use_cycle_list_with_options(

View file

@ -0,0 +1,16 @@
[package]
name = "use_textarea_autosize"
version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { workspace = true, features = ["nightly", "csr"] }
console_error_panic_hook = "0.1"
console_log = "1"
log = "0.4"
leptos-use = { path = "../..", features = ["use_textarea_autosize", "docs"] }
web-sys = "0.3"
[dev-dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.0"

View file

@ -0,0 +1,23 @@
A simple example for `use_textarea_autosize`.
If you don't have it installed already, install [Trunk](https://trunkrs.dev/) and [Tailwind](https://tailwindcss.com/docs/installation)
as well as the nightly toolchain for Rust and the wasm32-unknown-unknown target:
```bash
cargo install trunk
npm install -D tailwindcss @tailwindcss/forms
rustup toolchain install nightly
rustup target add wasm32-unknown-unknown
```
Then, open two terminals. In the first one, run:
```
npx tailwindcss -i ./input.css -o ./style/output.css --watch
```
In the second one, run:
```bash
trunk serve --open
```

View file

@ -0,0 +1,2 @@
[build]
public_url = "/demo/"

View file

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<link data-trunk rel="css" href="style/output.css">
</head>
<body></body>
</html>

View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

View file

@ -0,0 +1,36 @@
use leptos::prelude::*;
use leptos_use::docs::demo_or_body;
use leptos_use::{use_textarea_autosize, UseTextareaAutosizeReturn};
#[component]
fn Demo() -> impl IntoView {
let textarea = NodeRef::new();
let UseTextareaAutosizeReturn {
content,
set_content,
..
} = use_textarea_autosize(textarea);
view! {
<div class="mb-4">Type, the textarea will grow:</div>
<textarea
prop:value=content
on:input=move |evt| set_content.set(event_target_value(&evt))
node_ref=textarea
class="resize-none box-border"
placeholder="What's on your mind?"
/>
}
}
fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
let unmount_handle = leptos::mount::mount_to(demo_or_body(), || {
view! { <Demo/> }
});
unmount_handle.forget();
}

View file

@ -0,0 +1,342 @@
[type='text'],input:where(:not([type])),[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: #fff;
border-color: #6b7280;
border-width: 1px;
border-radius: 0px;
padding-top: 0.5rem;
padding-right: 0.75rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
font-size: 1rem;
line-height: 1.5rem;
--tw-shadow: 0 0 #0000;
}
[type='text']:focus, input:where(:not([type])):focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus {
outline: 2px solid transparent;
outline-offset: 2px;
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: #2563eb;
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
border-color: #2563eb;
}
input::-moz-placeholder, textarea::-moz-placeholder {
color: #6b7280;
opacity: 1;
}
input::placeholder,textarea::placeholder {
color: #6b7280;
opacity: 1;
}
::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
::-webkit-date-and-time-value {
min-height: 1.5em;
text-align: inherit;
}
::-webkit-datetime-edit {
display: inline-flex;
}
::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field {
padding-top: 0;
padding-bottom: 0;
}
select {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
[multiple],[size]:where(select:not([size="1"])) {
background-image: initial;
background-position: initial;
background-repeat: unset;
background-size: initial;
padding-right: 0.75rem;
-webkit-print-color-adjust: unset;
print-color-adjust: unset;
}
[type='checkbox'],[type='radio'] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
padding: 0;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
display: inline-block;
vertical-align: middle;
background-origin: border-box;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
flex-shrink: 0;
height: 1rem;
width: 1rem;
color: #2563eb;
background-color: #fff;
border-color: #6b7280;
border-width: 1px;
--tw-shadow: 0 0 #0000;
}
[type='checkbox'] {
border-radius: 0px;
}
[type='radio'] {
border-radius: 100%;
}
[type='checkbox']:focus,[type='radio']:focus {
outline: 2px solid transparent;
outline-offset: 2px;
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
--tw-ring-offset-width: 2px;
--tw-ring-offset-color: #fff;
--tw-ring-color: #2563eb;
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
[type='checkbox']:checked,[type='radio']:checked {
border-color: transparent;
background-color: currentColor;
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
[type='checkbox']:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
}
@media (forced-colors: active) {
[type='checkbox']:checked {
-webkit-appearance: auto;
-moz-appearance: auto;
appearance: auto;
}
}
[type='radio']:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
}
@media (forced-colors: active) {
[type='radio']:checked {
-webkit-appearance: auto;
-moz-appearance: auto;
appearance: auto;
}
}
[type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus {
border-color: transparent;
background-color: currentColor;
}
[type='checkbox']:indeterminate {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");
border-color: transparent;
background-color: currentColor;
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
@media (forced-colors: active) {
[type='checkbox']:indeterminate {
-webkit-appearance: auto;
-moz-appearance: auto;
appearance: auto;
}
}
[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus {
border-color: transparent;
background-color: currentColor;
}
[type='file'] {
background: unset;
border-color: inherit;
border-width: 0;
border-radius: 0;
padding: 0;
font-size: unset;
line-height: inherit;
}
[type='file']:focus {
outline: 1px solid ButtonText;
outline: 1px auto -webkit-focus-ring-color;
}
*, ::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: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
::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: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
.static {
position: static;
}
.mb-4 {
margin-bottom: 1rem;
}
.box-border {
box-sizing: border-box;
}
.grow {
flex-grow: 1;
}
.resize-none {
resize: none;
}
.text-\[--brand-color\] {
color: var(--brand-color);
}
.text-green-600 {
--tw-text-opacity: 1;
color: rgb(22 163 74 / var(--tw-text-opacity));
}
.opacity-75 {
opacity: 0.75;
}
@media (prefers-color-scheme: dark) {
.dark\:text-green-500 {
--tw-text-opacity: 1;
color: rgb(34 197 94 / var(--tw-text-opacity));
}
}

View file

@ -0,0 +1,15 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: {
files: ["*.html", "./src/**/*.rs", "../../src/docs/**/*.rs"],
},
theme: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [
require('@tailwindcss/forms'),
],
}

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
leptos = { version = "0.6", features = ["nightly", "csr"] } leptos = { workspace = true, features = ["nightly", "csr"] }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1" console_log = "1"
log = "0.4" log = "0.4"

View file

@ -1,10 +1,14 @@
use leptos::*; use leptos::prelude::*;
use leptos_use::docs::{demo_or_body, BooleanDisplay}; use leptos_use::docs::{demo_or_body, BooleanDisplay};
use leptos_use::{use_toggle, UseToggleReturn}; use leptos_use::{use_toggle, UseToggleReturn};
#[component] #[component]
fn Demo() -> impl IntoView { fn Demo() -> impl IntoView {
let UseToggleReturn { toggle, value, set_value } = use_toggle(true); let UseToggleReturn {
toggle,
value,
set_value,
} = use_toggle(true);
view! { view! {
<p>Value: <BooleanDisplay value=value /></p> <p>Value: <BooleanDisplay value=value /></p>
@ -18,7 +22,9 @@ fn main() {
_ = console_log::init_with_level(log::Level::Debug); _ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
mount_to(demo_or_body(), || { let unmount_handle = mount_to(demo_or_body(), || {
view! { <Demo /> } view! { <Demo /> }
}) });
unmount_handle.forget();
} }

View file

@ -5,7 +5,7 @@ edition = "2021"
[dependencies] [dependencies]
gloo-timers = { version = "0.3", features = ["futures"] } gloo-timers = { version = "0.3", features = ["futures"] }
leptos = { version = "0.6", features = ["nightly", "csr"] } leptos = { workspace = true, features = ["nightly", "csr"] }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1" console_log = "1"
log = "0.4" log = "0.4"

View file

@ -1,5 +1,6 @@
use gloo_timers::future::sleep; use gloo_timers::future::sleep;
use leptos::*; use leptos::prelude::*;
use leptos::task::spawn_local;
use leptos_use::docs::demo_or_body; use leptos_use::docs::demo_or_body;
use leptos_use::use_web_lock; use leptos_use::use_web_lock;
use std::time::Duration; use std::time::Duration;
@ -12,7 +13,7 @@ async fn my_process(_lock: web_sys::Lock) -> i32 {
#[component] #[component]
fn Demo() -> impl IntoView { fn Demo() -> impl IntoView {
let (res, set_res) = create_signal("Not started yet".to_string()); let (res, set_res) = signal("Not started yet".to_string());
let on_click = move |_| { let on_click = move |_| {
set_res.set("Running...".to_string()); set_res.set("Running...".to_string());
@ -41,7 +42,9 @@ fn main() {
_ = console_log::init_with_level(log::Level::Debug); _ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
mount_to(demo_or_body(), || { let unmount_handle = leptos::mount::mount_to(demo_or_body(), || {
view! { <Demo/> } view! { <Demo/> }
}) });
unmount_handle.forget();
} }

View file

@ -64,7 +64,7 @@ fn Demo() -> impl IntoView {
move |_| { move |_| {
let transport = transport.clone(); let transport = transport.clone();
leptos::spawn::spawn_local(async move { leptos::task::spawn_local(async move {
match transport.open_bidir_stream().await { match transport.open_bidir_stream().await {
Ok(bidir_stream) => { Ok(bidir_stream) => {
let i = id.get_value(); let i = id.get_value();

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
leptos = { version = "0.6", features = ["nightly", "csr"] } leptos = { workspace = true, features = ["nightly", "csr"] }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1" console_log = "1"
log = "0.4" log = "0.4"

View file

@ -1,4 +1,4 @@
use leptos::*; use leptos::prelude::*;
use leptos_use::docs::demo_or_body; use leptos_use::docs::demo_or_body;
use leptos_use::{use_window_size, UseWindowSizeReturn}; use leptos_use::{use_window_size, UseWindowSizeReturn};
@ -7,7 +7,7 @@ fn Demo() -> impl IntoView {
let UseWindowSizeReturn { width, height } = use_window_size(); let UseWindowSizeReturn { width, height } = use_window_size();
view! { view! {
<p>{{ width }} x {{ height }}</p> <p>{ width } x { height }</p>
} }
} }
@ -15,7 +15,9 @@ fn main() {
_ = console_log::init_with_level(log::Level::Debug); _ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
mount_to(demo_or_body(), || { let unmount_handle = leptos::mount::mount_to(demo_or_body(), || {
view! { <Demo/> } view! { <Demo/> }
}) });
unmount_handle.forget();
} }

View file

@ -1,11 +1,6 @@
use crate::{UseDocument, UseWindow};
use cfg_if::cfg_if;
use leptos::html::{CreateElement, ElementType};
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use send_wrapper::SendWrapper; use std::ops::Deref;
use std::marker::PhantomData;
use wasm_bindgen::JsCast;
/// Used as an argument type to make it easily possible to pass either /// Used as an argument type to make it easily possible to pass either
/// ///
@ -16,333 +11,197 @@ use wasm_bindgen::JsCast;
/// * a `NodeRef` /// * a `NodeRef`
/// ///
/// into a function. Used for example in [`fn@crate::use_event_listener`]. /// into a function. Used for example in [`fn@crate::use_event_listener`].
pub enum ElementMaybeSignal<T, E> #[derive(Copy, Clone)]
where #[cfg_attr(not(debug_assertions), repr(transparent))]
T: Into<E> + Clone + 'static, pub struct ElementMaybeSignal<T: 'static> {
{ #[cfg(debug_assertions)]
Static(SendWrapper<Option<T>>), defined_at: &'static std::panic::Location<'static>,
inner: ElementMaybeSignalType<T>,
}
#[derive(Clone, Copy)]
pub enum ElementMaybeSignalType<T: 'static> {
Static(StoredValue<Option<T>, LocalStorage>),
Dynamic(Signal<Option<T>, LocalStorage>), Dynamic(Signal<Option<T>, LocalStorage>),
_Phantom(PhantomData<fn() -> E>),
} }
impl<T, E> Default for ElementMaybeSignal<T, E> impl<T: 'static> Default for ElementMaybeSignalType<T> {
where
T: Into<E> + Clone + 'static,
{
fn default() -> Self { fn default() -> Self {
Self::Static(SendWrapper::new(None)) Self::Static(StoredValue::new_local(None))
} }
} }
impl<T, E> Clone for ElementMaybeSignal<T, E> impl<T> Default for ElementMaybeSignal<T> {
where fn default() -> Self {
T: Into<E> + Clone + 'static, Self {
{ inner: ElementMaybeSignalType::default(),
fn clone(&self) -> Self { #[cfg(debug_assertions)]
match self { defined_at: std::panic::Location::caller(),
Self::Static(t) => Self::Static(t.clone()),
Self::Dynamic(s) => Self::Dynamic(*s),
_ => unreachable!(),
} }
} }
} }
impl<T, E> DefinedAt for ElementMaybeSignal<T, E> impl<T> DefinedAt for ElementMaybeSignal<T> {
where
T: Into<E> + Clone + 'static,
{
fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> { fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> {
None #[cfg(debug_assertions)]
{
Some(self.defined_at)
}
#[cfg(not(debug_assertions))]
{
None
}
} }
} }
impl<T, E> With for ElementMaybeSignal<T, E> impl<T> With for ElementMaybeSignal<T> {
where
T: Into<E> + Clone + 'static,
{
type Value = Option<T>; type Value = Option<T>;
fn with<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> O {
match self {
Self::Static(t) => f(t),
Self::Dynamic(s) => {
let value = s.get();
f(&value)
}
_ => unreachable!(),
}
}
fn try_with<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> Option<O> { fn try_with<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> Option<O> {
match self { match &self.inner {
Self::Static(t) => Some(f(t)), ElementMaybeSignalType::Static(v) => v.try_with_value(f),
Self::Dynamic(s) => s.try_with(f), ElementMaybeSignalType::Dynamic(s) => s.try_with(f),
_ => unreachable!(),
} }
} }
} }
impl<T, E> WithUntracked for ElementMaybeSignal<T, E> impl<T> WithUntracked for ElementMaybeSignal<T> {
where
T: Into<E> + Clone + 'static,
{
type Value = Option<T>; type Value = Option<T>;
fn with_untracked<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> O {
match self {
Self::Static(t) => f(t),
Self::Dynamic(s) => s.with_untracked(f),
_ => unreachable!(),
}
}
fn try_with_untracked<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> Option<O> { fn try_with_untracked<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> Option<O> {
match self { match &self.inner {
Self::Static(t) => Some(f(t)), ElementMaybeSignalType::Static(t) => t.try_with_value(f),
Self::Dynamic(s) => s.try_with_untracked(f), ElementMaybeSignalType::Dynamic(s) => s.try_with_untracked(f),
_ => unreachable!(),
} }
} }
} }
pub trait IntoElementMaybeSignal<T, Marker: ?Sized> {
fn into_element_maybe_signal(self) -> ElementMaybeSignal<T>;
}
impl<El, T, Marker: ?Sized> IntoElementMaybeSignal<T, Marker> for El
where
El: IntoElementMaybeSignalType<T, Marker>,
{
fn into_element_maybe_signal(self) -> ElementMaybeSignal<T> {
ElementMaybeSignal {
inner: self.into_element_maybe_signal_type(),
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
}
pub trait IntoElementMaybeSignalType<T, Marker: ?Sized> {
fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T>;
}
// From static element ////////////////////////////////////////////////////////////// // From static element //////////////////////////////////////////////////////////////
impl<T, E> From<T> for ElementMaybeSignal<T, E> /// Handles `window()` or `document()`
impl<T, Js> IntoElementMaybeSignalType<T, web_sys::Element> for Js
where where
T: Into<E> + Clone + 'static, T: From<Js> + Clone,
{ {
fn from(value: T) -> Self { fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
ElementMaybeSignal::Static(SendWrapper::new(Some(value))) ElementMaybeSignalType::Static(StoredValue::new_local(Some(T::from(self).clone())))
} }
} }
impl<T, E> From<Option<T>> for ElementMaybeSignal<T, E> /// Handles `window().body()`
impl<T, Js> IntoElementMaybeSignalType<T, Option<web_sys::Element>> for Option<Js>
where where
T: Into<E> + Clone + 'static, T: From<Js> + Clone,
{ {
fn from(target: Option<T>) -> Self { fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
ElementMaybeSignal::Static(SendWrapper::new(target)) ElementMaybeSignalType::Static(StoredValue::new_local(self.map(|el| T::from(el).clone())))
} }
} }
macro_rules! impl_from_deref_option { /// Handles `use_window()` or `use_document()`
($ty:ty, $ty2:ty) => { impl<T, E, Js> IntoElementMaybeSignalType<T, Option<Option<web_sys::Element>>> for Js
impl<E> From<$ty> for ElementMaybeSignal<$ty2, E> where
where Js: Deref<Target = Option<E>>,
E: From<$ty2> + 'static, E: Clone,
{ T: From<E> + Clone,
fn from(value: $ty) -> Self { {
Self::Static(SendWrapper::new((*value).clone())) fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
} ElementMaybeSignalType::Static(StoredValue::new_local(
} self.as_ref().map(|e| T::from(e.clone())),
}; ))
}
} }
impl_from_deref_option!(UseWindow, web_sys::Window);
impl_from_deref_option!(UseDocument, web_sys::Document);
// From string (selector) /////////////////////////////////////////////////////////////// // From string (selector) ///////////////////////////////////////////////////////////////
impl<'a, E> From<&'a str> for ElementMaybeSignal<web_sys::Element, E> /// Handles `"body"` or `"#app"`
impl<T, V> IntoElementMaybeSignalType<T, str> for V
where where
E: From<web_sys::Element> + 'static, V: AsRef<str>,
T: From<web_sys::Element> + Clone,
{ {
fn from(target: &'a str) -> Self { fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
cfg_if! { if #[cfg(feature = "ssr")] { if cfg!(feature = "ssr") {
let _ = target; ElementMaybeSignalType::Static(StoredValue::new_local(None))
Self::Static(SendWrapper::new(None))
} else { } else {
Self::Static(SendWrapper::new(document().query_selector(target).unwrap_or_default())) ElementMaybeSignalType::Static(StoredValue::new_local(
}} document()
} .query_selector(self.as_ref())
} .unwrap_or_default()
.map(|el| T::from(el).clone()),
impl<E> From<String> for ElementMaybeSignal<web_sys::Element, E> ))
where
E: From<web_sys::Element> + 'static,
{
fn from(target: String) -> Self {
Self::from(target.as_str())
}
}
macro_rules! impl_from_signal_string {
($ty:ty) => {
impl<E> From<$ty> for ElementMaybeSignal<web_sys::Element, E>
where
E: From<web_sys::Element> + 'static,
{
fn from(signal: $ty) -> Self {
cfg_if! { if #[cfg(feature = "ssr")] {
let _ = signal;
Self::Dynamic(Signal::derive_local(|| None))
} else {
Self::Dynamic(
Signal::derive_local(move || document().query_selector(&signal.get()).unwrap_or_default()),
)
}}
}
} }
}; }
} }
impl_from_signal_string!(Signal<String>); pub struct SignalStrMarker;
impl_from_signal_string!(ReadSignal<String>);
impl_from_signal_string!(RwSignal<String>);
impl_from_signal_string!(Memo<String>);
impl_from_signal_string!(Signal<&'static str>); /// Handles `Signal<&str>`
impl_from_signal_string!(ReadSignal<&'static str>); impl<T, V, I> IntoElementMaybeSignalType<T, SignalStrMarker> for V
impl_from_signal_string!(RwSignal<&'static str>); where
impl_from_signal_string!(Memo<&'static str>); V: Get<Value = I> + 'static,
I: AsRef<str>,
T: From<web_sys::Element> + Clone,
{
fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
if cfg!(feature = "ssr") {
ElementMaybeSignalType::Static(StoredValue::new_local(None))
} else {
ElementMaybeSignalType::Dynamic(Signal::derive_local(move || {
document()
.query_selector(self.get().as_ref())
.unwrap_or_default()
.map(|el| T::from(el).clone())
}))
}
}
}
// From signal /////////////////////////////////////////////////////////////// // From signal ///////////////////////////////////////////////////////////////
macro_rules! impl_from_signal_option { pub struct SignalMarker;
($ty:ty) => {
impl<T, E> From<$ty> for ElementMaybeSignal<T, E> /// Handles `Signal<web_sys::*>`
where impl<T, V, E> IntoElementMaybeSignalType<T, SignalMarker> for V
T: Into<E> + Clone + 'static, where
{ V: Get<Value = E> + 'static,
fn from(target: $ty) -> Self { T: From<E> + Clone,
Self::Dynamic(target.into()) {
} fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
} ElementMaybeSignalType::Dynamic(Signal::derive_local(move || Some(T::from(self.get()))))
}; }
} }
impl_from_signal_option!(Signal<Option<T>, LocalStorage>); pub struct OptionSignalMarker;
impl_from_signal_option!(ReadSignal<Option<T>, LocalStorage>);
impl_from_signal_option!(RwSignal<Option<T>, LocalStorage>);
impl_from_signal_option!(Memo<Option<T>, LocalStorage>);
macro_rules! impl_from_signal { /// Handles `Signal<Option<web_sys::*>>` and `NodeRef` and `ElementMaybeSignal`
($ty:ty) => { impl<T, V, E> IntoElementMaybeSignalType<T, OptionSignalMarker> for V
impl<T, E> From<$ty> for ElementMaybeSignal<T, E> where
where V: Get<Value = Option<E>> + 'static,
T: Into<E> + Clone + 'static, T: From<E> + Clone,
{ {
fn from(signal: $ty) -> Self { fn into_element_maybe_signal_type(self) -> ElementMaybeSignalType<T> {
Self::Dynamic(Signal::derive_local(move || Some(signal.get()))) ElementMaybeSignalType::Dynamic(Signal::derive_local(move || self.get().map(T::from)))
} }
}
};
} }
impl_from_signal!(Signal<T, LocalStorage>);
impl_from_signal!(ReadSignal<T, LocalStorage>);
impl_from_signal!(RwSignal<T, LocalStorage>);
impl_from_signal!(Memo<T, LocalStorage>);
// From NodeRef //////////////////////////////////////////////////////////////
macro_rules! impl_from_node_ref {
($ty:ty) => {
impl<R> From<NodeRef<R>> for ElementMaybeSignal<$ty, $ty>
where
R: ElementType + CreateElement<Dom> + Clone + Send + Sync + 'static,
R::Output: JsCast + Into<$ty> + Clone + 'static,
{
fn from(node_ref: NodeRef<R>) -> Self {
Self::Dynamic(Signal::derive_local(move || {
node_ref.get().map(move |el| {
let el: $ty = el.clone().into();
el
})
}))
}
}
};
}
impl_from_node_ref!(web_sys::EventTarget);
impl_from_node_ref!(web_sys::Element);
impl_from_node_ref!(web_sys::HtmlElement);
// // From leptos::html::HTMLElement ///////////////////////////////////////////////
//
// macro_rules! impl_from_html_element {
// ($ty:ty) => {
// impl<HtmlEl> From<HtmlElement<HtmlEl>> for ElementMaybeSignal<$ty, $ty>
// where
// HtmlEl: ElementDescriptor + std::ops::Deref<Target = $ty>,
// {
// fn from(value: HtmlElement<HtmlEl>) -> Self {
// let el: &$ty = value.deref();
// Self::Static(Some(el.clone()))
// }
// }
// };
// }
//
// impl_from_html_element!(web_sys::EventTarget);
// impl_from_html_element!(web_sys::Element);
// impl_from_html_element!(web_sys::HtmlElement);
//
// // From Signal<leptos::html::HTMLElement> /////////////////////////////////////////
//
// macro_rules! impl_from_signal_html_element {
// ($signal:ty, $ty:ty) => {
// impl<HtmlEl> From<$signal> for ElementMaybeSignal<$ty, $ty>
// where
// HtmlEl: ElementDescriptor + std::ops::Deref<Target = $ty> + Clone,
// {
// fn from(value: $signal) -> Self {
// Self::Dynamic(Signal::derive(move || {
// let value = value.get();
// let el: &$ty = value.deref();
// Some(el.clone())
// }))
// }
// }
// };
// }
//
// impl_from_signal_html_element!(Signal<HtmlElement<HtmlEl>>, web_sys::EventTarget);
// impl_from_signal_html_element!(ReadSignal<HtmlElement<HtmlEl>>, web_sys::EventTarget);
// impl_from_signal_html_element!(RwSignal<HtmlElement<HtmlEl>>, web_sys::EventTarget);
// impl_from_signal_html_element!(Memo<HtmlElement<HtmlEl>>, web_sys::EventTarget);
//
// impl_from_signal_html_element!(Signal<HtmlElement<HtmlEl>>, web_sys::Element);
// impl_from_signal_html_element!(ReadSignal<HtmlElement<HtmlEl>>, web_sys::Element);
// impl_from_signal_html_element!(RwSignal<HtmlElement<HtmlEl>>, web_sys::Element);
// impl_from_signal_html_element!(Memo<HtmlElement<HtmlEl>>, web_sys::Element);
//
// // From Signal<Option<leptos::html::HTMLElement>> /////////////////////////////////////////
//
// macro_rules! impl_from_signal_html_element {
// ($signal:ty, $ty:ty) => {
// impl<HtmlEl> From<$signal> for ElementMaybeSignal<$ty, $ty>
// where
// HtmlEl: ElementDescriptor + std::ops::Deref<Target = $ty> + Clone,
// {
// fn from(value: $signal) -> Self {
// Self::Dynamic(Signal::derive(move || {
// let el: Option<$ty> = value.get().map(|el| el.deref().clone());
// el
// }))
// }
// }
// };
// }
//
// impl_from_signal_html_element!(Signal<Option<HtmlElement<HtmlEl>>>, web_sys::EventTarget);
// impl_from_signal_html_element!(
// ReadSignal<Option<HtmlElement<HtmlEl>>>,
// web_sys::EventTarget
// );
// impl_from_signal_html_element!(RwSignal<Option<HtmlElement<HtmlEl>>>, web_sys::EventTarget);
// impl_from_signal_html_element!(Memo<Option<HtmlElement<HtmlEl>>>, web_sys::EventTarget);
//
// impl_from_signal_html_element!(Signal<Option<HtmlElement<HtmlEl>>>, web_sys::Element);
// impl_from_signal_html_element!(ReadSignal<Option<HtmlElement<HtmlEl>>>, web_sys::Element);
// impl_from_signal_html_element!(RwSignal<Option<HtmlElement<HtmlEl>>>, web_sys::Element);
// impl_from_signal_html_element!(Memo<Option<HtmlElement<HtmlEl>>>, web_sys::Element);
//
// impl_from_signal_html_element!(Signal<Option<HtmlElement<HtmlEl>>>, web_sys::HtmlElement);
// impl_from_signal_html_element!(
// ReadSignal<Option<HtmlElement<HtmlEl>>>,
// web_sys::HtmlElement
// );
// impl_from_signal_html_element!(RwSignal<Option<HtmlElement<HtmlEl>>>, web_sys::HtmlElement);
// impl_from_signal_html_element!(Memo<Option<HtmlElement<HtmlEl>>>, web_sys::HtmlElement);

View file

@ -1,12 +1,7 @@
use crate::core::ElementMaybeSignal; use crate::core::{SignalMarker, SignalStrMarker};
use crate::{UseDocument, UseWindow};
use cfg_if::cfg_if;
use leptos::html::ElementType;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use send_wrapper::SendWrapper; use std::ops::Deref;
use std::marker::PhantomData;
use wasm_bindgen::JsCast;
/// Used as an argument type to make it easily possible to pass either /// Used as an argument type to make it easily possible to pass either
/// ///
@ -17,656 +12,345 @@ use wasm_bindgen::JsCast;
/// * a `NodeRef` /// * a `NodeRef`
/// ///
/// into a function. Used for example in [`fn@crate::use_event_listener`]. /// into a function. Used for example in [`fn@crate::use_event_listener`].
pub enum ElementsMaybeSignal<T, E> #[derive(Copy, Clone)]
where #[cfg_attr(not(debug_assertions), repr(transparent))]
T: Into<E> + Clone + 'static, pub struct ElementsMaybeSignal<T: 'static> {
{ #[cfg(debug_assertions)]
Static(SendWrapper<Vec<Option<T>>>), defined_at: &'static std::panic::Location<'static>,
inner: ElementsMaybeSignalType<T>,
}
#[derive(Clone, Copy)]
pub enum ElementsMaybeSignalType<T: 'static> {
Static(StoredValue<Vec<Option<T>>, LocalStorage>),
Dynamic(Signal<Vec<Option<T>>, LocalStorage>), Dynamic(Signal<Vec<Option<T>>, LocalStorage>),
_Phantom(PhantomData<fn() -> E>),
} }
impl<T, E> Default for ElementsMaybeSignal<T, E> impl<T: 'static> Default for ElementsMaybeSignalType<T> {
where
T: Into<E> + Clone + 'static,
{
fn default() -> Self { fn default() -> Self {
Self::Static(SendWrapper::new(vec![])) Self::Static(StoredValue::new_local(vec![]))
} }
} }
impl<T, E> Clone for ElementsMaybeSignal<T, E> impl<T> Default for ElementsMaybeSignal<T> {
where fn default() -> Self {
T: Into<E> + Clone + 'static, Self {
{ inner: ElementsMaybeSignalType::default(),
fn clone(&self) -> Self { #[cfg(debug_assertions)]
match self { defined_at: std::panic::Location::caller(),
Self::Static(v) => Self::Static(v.clone()),
Self::Dynamic(s) => Self::Dynamic(*s),
Self::_Phantom(_) => unreachable!(),
} }
} }
} }
impl<T, E> DefinedAt for ElementsMaybeSignal<T, E> impl<T> DefinedAt for ElementsMaybeSignal<T> {
where
T: Into<E> + Clone + 'static,
{
fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> { fn defined_at(&self) -> Option<&'static std::panic::Location<'static>> {
None #[cfg(debug_assertions)]
{
Some(self.defined_at)
}
#[cfg(not(debug_assertions))]
{
None
}
} }
} }
impl<T, E> With for ElementsMaybeSignal<T, E> impl<T> With for ElementsMaybeSignal<T> {
where
T: Into<E> + Clone + 'static,
{
type Value = Vec<Option<T>>; type Value = Vec<Option<T>>;
fn with<O>(&self, f: impl FnOnce(&Vec<Option<T>>) -> O) -> O {
match self {
Self::Static(v) => f(v),
Self::Dynamic(s) => s.with(f),
Self::_Phantom(_) => unreachable!(),
}
}
fn try_with<O>(&self, f: impl FnOnce(&Vec<Option<T>>) -> O) -> Option<O> { fn try_with<O>(&self, f: impl FnOnce(&Vec<Option<T>>) -> O) -> Option<O> {
match self { match &self.inner {
Self::Static(v) => Some(f(v)), ElementsMaybeSignalType::Static(v) => v.try_with_value(f),
Self::Dynamic(s) => s.try_with(f), ElementsMaybeSignalType::Dynamic(s) => s.try_with(f),
Self::_Phantom(_) => unreachable!(),
} }
} }
} }
impl<T, E> WithUntracked for ElementsMaybeSignal<T, E> impl<T> WithUntracked for ElementsMaybeSignal<T> {
where
T: Into<E> + Clone + 'static,
{
type Value = Vec<Option<T>>; type Value = Vec<Option<T>>;
fn with_untracked<O>(&self, f: impl FnOnce(&Vec<Option<T>>) -> O) -> O {
match self {
Self::Static(t) => f(t),
Self::Dynamic(s) => s.with_untracked(f),
Self::_Phantom(_) => unreachable!(),
}
}
fn try_with_untracked<O>(&self, f: impl FnOnce(&Vec<Option<T>>) -> O) -> Option<O> { fn try_with_untracked<O>(&self, f: impl FnOnce(&Vec<Option<T>>) -> O) -> Option<O> {
match self { match self.inner {
Self::Static(t) => Some(f(t)), ElementsMaybeSignalType::Static(t) => t.try_with_value(f),
Self::Dynamic(s) => s.try_with_untracked(f), ElementsMaybeSignalType::Dynamic(s) => s.try_with_untracked(f),
Self::_Phantom(_) => unreachable!(),
} }
} }
} }
pub trait IntoElementsMaybeSignal<T, Marker: ?Sized> {
fn into_elements_maybe_signal(self) -> ElementsMaybeSignal<T>;
}
impl<El, T, Marker: ?Sized> IntoElementsMaybeSignal<T, Marker> for El
where
El: IntoElementsMaybeSignalType<T, Marker>,
{
fn into_elements_maybe_signal(self) -> ElementsMaybeSignal<T> {
ElementsMaybeSignal {
inner: self.into_elements_maybe_signal_type(),
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
}
pub trait IntoElementsMaybeSignalType<T, Marker: ?Sized> {
fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T>;
}
// From single static element ////////////////////////////////////////////////////////////// // From single static element //////////////////////////////////////////////////////////////
impl<T, E> From<T> for ElementsMaybeSignal<T, E> /// Handles `window()` or `document()`
impl<T, Js> IntoElementsMaybeSignalType<T, web_sys::Element> for Js
where where
T: Into<E> + Clone + 'static, T: From<Js> + Clone,
{ {
fn from(value: T) -> Self { fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
ElementsMaybeSignal::Static(SendWrapper::new(vec![Some(value)])) ElementsMaybeSignalType::Static(StoredValue::new_local(vec![Some(T::from(self).clone())]))
} }
} }
impl<T, E> From<Option<T>> for ElementsMaybeSignal<T, E> /// Handles `window().body()`
impl<T, Js> IntoElementsMaybeSignalType<T, Option<web_sys::Element>> for Option<Js>
where where
T: Into<E> + Clone + 'static, T: From<Js> + Clone,
{ {
fn from(target: Option<T>) -> Self { fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
ElementsMaybeSignal::Static(SendWrapper::new(vec![target])) ElementsMaybeSignalType::Static(StoredValue::new_local(vec![
self.map(|el| T::from(el).clone())
]))
} }
} }
macro_rules! impl_from_deref_option { /// Handles `use_window()` or `use_document()`
($ty:ty, $ty2:ty) => { impl<T, E, Js> IntoElementsMaybeSignalType<T, Option<Option<web_sys::Element>>> for Js
impl<E> From<$ty> for ElementsMaybeSignal<$ty2, E> where
where Js: Deref<Target = Option<E>>,
E: From<$ty2> + 'static, E: Clone,
{ T: From<E> + Clone,
fn from(value: $ty) -> Self { {
Self::Static(SendWrapper::new(vec![(*value).clone()])) fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
} ElementsMaybeSignalType::Static(StoredValue::new_local(vec![self
} .as_ref()
}; .map(|e| T::from(e.clone()))]))
}
} }
impl_from_deref_option!(UseWindow, web_sys::Window);
impl_from_deref_option!(UseDocument, web_sys::Document);
// From string (selector) /////////////////////////////////////////////////////////////// // From string (selector) ///////////////////////////////////////////////////////////////
impl<'a, E> From<&'a str> for ElementsMaybeSignal<web_sys::Node, E> /// Handles `"body"` or `"#app"`
impl<T, V> IntoElementsMaybeSignalType<T, str> for V
where where
E: From<web_sys::Node> + 'static, V: AsRef<str>,
T: From<web_sys::Element> + Clone,
{ {
fn from(target: &'a str) -> Self { fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
cfg_if! { if #[cfg(feature = "ssr")] { if cfg!(feature = "ssr") {
let _ = target; ElementsMaybeSignalType::Static(StoredValue::new_local(vec![]))
Self::Static(SendWrapper::new(vec![]))
} else { } else {
if let Ok(node_list) = document().query_selector_all(target) { ElementsMaybeSignalType::Static(StoredValue::new_local(vec![document()
let mut list = Vec::with_capacity(node_list.length() as usize); .query_selector(self.as_ref())
for i in 0..node_list.length() { .unwrap_or_default()
let node = node_list.get(i).expect("checked the range"); .map(|el| T::from(el).clone())]))
list.push(Some(node));
}
Self::Static(SendWrapper::new(list))
} else {
Self::Static(SendWrapper::new(vec![]))
}
}}
}
}
impl<E> From<String> for ElementsMaybeSignal<web_sys::Node, E>
where
E: From<web_sys::Node> + 'static,
{
fn from(target: String) -> Self {
Self::from(target.as_str())
}
}
macro_rules! impl_from_signal_string {
($ty:ty) => {
impl<E> From<$ty> for ElementsMaybeSignal<web_sys::Node, E>
where
E: From<web_sys::Node> + 'static,
{
fn from(signal: $ty) -> Self {
cfg_if! { if #[cfg(feature = "ssr")] {
Self::Dynamic(
Signal::derive_local(move || {
if let Ok(node_list) = document().query_selector_all(&signal.get()) {
let mut list = Vec::with_capacity(node_list.length() as usize);
for i in 0..node_list.length() {
let node = node_list.get(i).expect("checked the range");
list.push(Some(node));
}
list
} else {
vec![]
}
}),
)
} else {
let _ = signal;
Self::Dynamic(Signal::derive_local(Vec::new))
}}
}
} }
}; }
} }
impl_from_signal_string!(Signal<String>); /// Handles `Signal<&str>`
impl_from_signal_string!(ReadSignal<String>); impl<T, V, I> IntoElementsMaybeSignalType<T, SignalStrMarker> for V
impl_from_signal_string!(RwSignal<String>); where
impl_from_signal_string!(Memo<String>); V: Get<Value = I> + 'static,
I: AsRef<str>,
impl_from_signal_string!(Signal<&'static str>); T: From<web_sys::Element> + Clone,
impl_from_signal_string!(ReadSignal<&'static str>); {
impl_from_signal_string!(RwSignal<&'static str>); fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
impl_from_signal_string!(Memo<&'static str>); if cfg!(feature = "ssr") {
ElementsMaybeSignalType::Static(StoredValue::new_local(vec![]))
} else {
ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
vec![document()
.query_selector(self.get().as_ref())
.unwrap_or_default()
.map(|el| T::from(el).clone())]
}))
}
}
}
// From single signal /////////////////////////////////////////////////////////////// // From single signal ///////////////////////////////////////////////////////////////
macro_rules! impl_from_signal_option { /// Handles `Signal<web_sys::*>`
($ty:ty) => { impl<T, V, E> IntoElementsMaybeSignalType<T, SignalMarker> for V
impl<T, E> From<$ty> for ElementsMaybeSignal<T, E> where
where V: Get<Value = E> + 'static,
T: Into<E> + Clone + 'static, T: From<E> + Clone,
{ {
fn from(signal: $ty) -> Self { fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
Self::Dynamic(Signal::derive_local(move || vec![signal.get()])) ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
} vec![Some(T::from(self.get()))]
} }))
}; }
} }
impl_from_signal_option!(Signal<Option<T>, LocalStorage>);
impl_from_signal_option!(ReadSignal<Option<T>, LocalStorage>);
impl_from_signal_option!(RwSignal<Option<T>, LocalStorage>);
impl_from_signal_option!(Memo<Option<T>, LocalStorage>);
macro_rules! impl_from_signal {
($ty:ty) => {
impl<T, E> From<$ty> for ElementsMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
fn from(signal: $ty) -> Self {
Self::Dynamic(Signal::derive_local(move || vec![Some(signal.get())]))
}
}
};
}
impl_from_signal!(Signal<T, LocalStorage>);
impl_from_signal!(ReadSignal<T, LocalStorage>);
impl_from_signal!(RwSignal<T, LocalStorage>);
impl_from_signal!(Memo<T, LocalStorage>);
// From single NodeRef //////////////////////////////////////////////////////////////
macro_rules! impl_from_node_ref {
($ty:ty) => {
impl<R> From<NodeRef<R>> for ElementsMaybeSignal<$ty, $ty>
where
R: ElementType + Clone + 'static,
R::Output: JsCast + Into<$ty> + Clone + 'static,
{
fn from(node_ref: NodeRef<R>) -> Self {
Self::Dynamic(Signal::derive_local(move || {
vec![node_ref.get().map(move |el| {
let el: $ty = el.clone().into();
el
})]
}))
}
}
};
}
impl_from_node_ref!(web_sys::EventTarget);
impl_from_node_ref!(web_sys::Element);
// From single leptos::html::HTMLElement ///////////////////////////////////////////
// macro_rules! impl_from_html_element {
// ($ty:ty) => {
// impl<E, At, Ch, Rndr> From<HtmlElement<E, At, Ch, Rndr>> for ElementsMaybeSignal<$ty, $ty>
// where
// E: ElementType,
// E::Output: std::ops::Deref<Target = $ty>,
// {
// fn from(value: HtmlElement<E, At, Ch, Rndr>) -> Self {
// let el: &$ty = value.deref();
// Self::Static(vec![Some(el.clone())])
// }
// }
// };
// }
//
// impl_from_html_element!(web_sys::EventTarget);
// impl_from_html_element!(web_sys::Element);
// From multiple static elements ////////////////////////////////////////////////////// // From multiple static elements //////////////////////////////////////////////////////
impl<T, E> From<&[T]> for ElementsMaybeSignal<T, E> /// Handles `&[web_sys::*]`
impl<'a, T, Js, C> IntoElementsMaybeSignalType<T, &'a [web_sys::Element]> for C
where where
T: Into<E> + Clone + 'static, Js: Clone + 'a,
T: From<Js>,
C: IntoIterator<Item = &'a Js>,
{ {
fn from(target: &[T]) -> Self { fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
Self::Static(SendWrapper::new( ElementsMaybeSignalType::Static(StoredValue::new_local(
target.iter().map(|t| Some(t.clone())).collect(), self.into_iter().map(|t| Some(T::from(t.clone()))).collect(),
)) ))
} }
} }
impl<T, E> From<&[Option<T>]> for ElementsMaybeSignal<T, E> /// Handles `&[Option<web_sys::*>]`
impl<'a, T, Js, C> IntoElementsMaybeSignalType<T, &'a [Option<web_sys::Element>]> for C
where where
T: Into<E> + Clone + 'static, Js: Clone + 'a,
T: From<Js>,
C: IntoIterator<Item = &'a Option<Js>>,
{ {
fn from(target: &[Option<T>]) -> Self { fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
Self::Static(SendWrapper::new(target.to_vec())) ElementsMaybeSignalType::Static(StoredValue::new_local(
} self.into_iter().map(|t| t.clone().map(T::from)).collect(),
}
impl<T, E> From<Vec<T>> for ElementsMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
fn from(target: Vec<T>) -> Self {
Self::Static(SendWrapper::new(
target.iter().map(|t| Some(t.clone())).collect(),
)) ))
} }
} }
impl<T, E> From<Vec<Option<T>>> for ElementsMaybeSignal<T, E> /// Handles `Vec<web_sys::*>`
impl<T, Js, C> IntoElementsMaybeSignalType<T, Vec<web_sys::Element>> for C
where where
T: Into<E> + Clone + 'static, T: From<Js> + Clone,
C: IntoIterator<Item = Js>,
{ {
fn from(target: Vec<Option<T>>) -> Self { fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
Self::Static(SendWrapper::new(target.into_iter().collect())) ElementsMaybeSignalType::Static(StoredValue::new_local(
} self.into_iter().map(|t| Some(T::from(t))).collect(),
}
impl<T, E, const C: usize> From<[T; C]> for ElementsMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
fn from(target: [T; C]) -> Self {
Self::Static(SendWrapper::new(
target.into_iter().map(|t| Some(t)).collect(),
)) ))
} }
} }
impl<T, E, const C: usize> From<[Option<T>; C]> for ElementsMaybeSignal<T, E> /// Handles `Vec<Option<web_sys::*>>`
impl<T, Js, C> IntoElementsMaybeSignalType<T, Vec<Option<web_sys::Element>>> for C
where where
T: Into<E> + Clone + 'static, T: From<Js> + Clone,
C: IntoIterator<Item = Option<Js>>,
{ {
fn from(target: [Option<T>; C]) -> Self { fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
Self::Static(SendWrapper::new(target.into_iter().collect())) ElementsMaybeSignalType::Static(StoredValue::new_local(
self.into_iter().map(|t| t.map(T::from)).collect(),
))
} }
} }
// From multiple strings ////////////////////////////////////////////////////// // From multiple strings //////////////////////////////////////////////////////
macro_rules! impl_from_strings_inner { /// Handles `["body", "#app"]`
($el_ty:ty, $str_ty:ty, $target:ident) => { impl<T, V, C> IntoElementsMaybeSignalType<T, &[&str]> for C
Self::Static(
SendWrapper::new(
$target
.iter()
.filter_map(|sel: &$str_ty| -> Option<Vec<Option<$el_ty>>> {
cfg_if! { if #[cfg(feature = "ssr")] {
let _ = sel;
None
} else {
use wasm_bindgen::JsCast;
if let Ok(node_list) = document().query_selector_all(sel) {
let mut list = Vec::with_capacity(node_list.length() as usize);
for i in 0..node_list.length() {
let node: $el_ty = node_list.get(i).expect("checked the range").unchecked_into();
list.push(Some(node));
}
Some(list)
} else {
None
}
}}
})
.flatten()
.collect(),
)
)
};
}
macro_rules! impl_from_strings_with_container {
($el_ty:ty, $str_ty:ty, $container_ty:ty) => {
impl From<$container_ty> for ElementsMaybeSignal<$el_ty, $el_ty> {
fn from(target: $container_ty) -> Self {
impl_from_strings_inner!($el_ty, $str_ty, target)
}
}
};
}
macro_rules! impl_from_strings {
($el_ty:ty, $str_ty:ty) => {
impl_from_strings_with_container!($el_ty, $str_ty, Vec<$str_ty>);
impl_from_strings_with_container!($el_ty, $str_ty, &[$str_ty]);
impl<const C: usize> From<[$str_ty; C]> for ElementsMaybeSignal<$el_ty, $el_ty> {
fn from(target: [$str_ty; C]) -> Self {
impl_from_strings_inner!($el_ty, $str_ty, target)
}
}
impl<const C: usize> From<&[$str_ty; C]> for ElementsMaybeSignal<$el_ty, $el_ty> {
fn from(target: &[$str_ty; C]) -> Self {
impl_from_strings_inner!($el_ty, $str_ty, target)
}
}
};
}
impl_from_strings!(web_sys::Element, &str);
impl_from_strings!(web_sys::Element, String);
impl_from_strings!(web_sys::EventTarget, &str);
impl_from_strings!(web_sys::EventTarget, String);
// From signal of vec ////////////////////////////////////////////////////////////////
impl<T, E> From<Signal<Vec<T>, LocalStorage>> for ElementsMaybeSignal<T, E>
where where
T: Into<E> + Clone + 'static, V: AsRef<str>,
T: From<web_sys::Element> + Clone,
C: IntoIterator<Item = V>,
{ {
fn from(signal: Signal<Vec<T>, LocalStorage>) -> Self { fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
Self::Dynamic(Signal::derive_local(move || { if cfg!(feature = "ssr") {
signal.get().into_iter().map(|t| Some(t)).collect() ElementsMaybeSignalType::Static(StoredValue::new_local(vec![]))
} else {
ElementsMaybeSignalType::Static(StoredValue::new_local(
self.into_iter()
.map(|sel| {
document()
.query_selector(sel.as_ref())
.unwrap_or_default()
.map(|el| T::from(el).clone())
})
.collect(),
))
}
}
}
// From signal of multiple ////////////////////////////////////////////////////////////////
pub struct SignalVecMarker;
/// Handles `Signal<Vec<web_sys::*>>` and `Signal<Option<web_sys::*>>` and `NodeRef` and `ElementMaybeSignal`
impl<T, Js, C, G> IntoElementsMaybeSignalType<T, SignalVecMarker> for G
where
T: From<Js> + Clone,
G: Get<Value = C> + 'static,
C: IntoIterator<Item = Js>,
{
fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
self.get().into_iter().map(|t| Some(T::from(t))).collect()
})) }))
} }
} }
impl<T, E> From<Signal<Vec<Option<T>>, LocalStorage>> for ElementsMaybeSignal<T, E> pub struct SignalVecOptionMarker;
/// Handles `Signal<Vec<Option<web_sys::*>>>`
impl<T, Js, C, G> IntoElementsMaybeSignalType<T, SignalVecOptionMarker> for G
where where
T: Into<E> + Clone + 'static, T: From<Js> + Clone,
G: Get<Value = C> + 'static,
C: IntoIterator<Item = Option<Js>>,
{ {
fn from(target: Signal<Vec<Option<T>>, LocalStorage>) -> Self { fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
Self::Dynamic(target) ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
self.get().into_iter().map(|t| t.map(T::from)).collect()
}))
} }
} }
// From multiple signals ////////////////////////////////////////////////////////////// // From multiple signals //////////////////////////////////////////////////////////////
impl<T, E> From<&[Signal<T, LocalStorage>]> for ElementsMaybeSignal<T, E> pub struct VecSignalMarker;
/// Handles `Vec<Signal<web_sys::*>>`
impl<T, Js, C, G> IntoElementsMaybeSignalType<T, VecSignalMarker> for C
where where
T: Into<E> + Clone + 'static, T: From<Js> + Clone,
C: IntoIterator<Item = G> + Clone + 'static,
G: Get<Value = Js>,
{ {
fn from(list: &[Signal<T, LocalStorage>]) -> Self { fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
let list = list.to_vec(); let signals = self.clone();
Self::Dynamic(Signal::derive_local(move || { ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
list.iter().map(|t| Some(t.get())).collect() signals
})) .clone()
} .into_iter()
} .map(|t| Some(T::from(t.get())))
.collect()
impl<T, E> From<&[Signal<Option<T>, LocalStorage>]> for ElementsMaybeSignal<T, E> }))
where }
T: Into<E> + Clone + 'static, }
{
fn from(list: &[Signal<Option<T>, LocalStorage>]) -> Self { pub struct VecSignalOptionMarker;
let list = list.to_vec();
/// Handles `Vec<Signal<Option<web_sys::*>>>`, `Vec<NodeRef>`
Self::Dynamic(Signal::derive_local(move || { impl<T, Js, C, G> IntoElementsMaybeSignalType<T, VecSignalOptionMarker> for C
list.iter().map(|t| t.get()).collect() where
})) T: From<Js> + Clone,
} C: IntoIterator<Item = G> + Clone + 'static,
} G: Get<Value = Option<Js>>,
{
impl<T, E> From<Vec<Signal<T, LocalStorage>>> for ElementsMaybeSignal<T, E> fn into_elements_maybe_signal_type(self) -> ElementsMaybeSignalType<T> {
where let signals = self.clone();
T: Into<E> + Clone + 'static,
{ ElementsMaybeSignalType::Dynamic(Signal::derive_local(move || {
fn from(list: Vec<Signal<T, LocalStorage>>) -> Self { signals
let list = list.clone(); .clone()
.into_iter()
Self::Dynamic(Signal::derive_local(move || { .map(|t| t.get().map(T::from))
list.iter().map(|t| Some(t.get())).collect()
}))
}
}
impl<T, E> From<Vec<Signal<Option<T>, LocalStorage>>> for ElementsMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
fn from(list: Vec<Signal<Option<T>, LocalStorage>>) -> Self {
let list = list.clone();
Self::Dynamic(Signal::derive_local(move || {
list.iter().map(|t| t.get()).collect()
}))
}
}
impl<T, E, const C: usize> From<[Signal<T, LocalStorage>; C]> for ElementsMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
fn from(list: [Signal<T, LocalStorage>; C]) -> Self {
let list = list.to_vec();
Self::Dynamic(Signal::derive_local(move || {
list.iter().map(|t| Some(t.get())).collect()
}))
}
}
impl<T, E, const C: usize> From<[Signal<Option<T>, LocalStorage>; C]> for ElementsMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
fn from(list: [Signal<Option<T>, LocalStorage>; C]) -> Self {
let list = list.to_vec();
Self::Dynamic(Signal::derive_local(move || {
list.iter().map(|t| t.get()).collect()
}))
}
}
// From multiple NodeRefs //////////////////////////////////////////////////////////////
macro_rules! impl_from_multi_node_ref_inner {
($ty:ty, $node_refs:ident) => {
Self::Dynamic(Signal::derive_local(move || {
$node_refs
.iter()
.map(|node_ref| {
node_ref.get().map(move |el| {
let el: $ty = el.clone().into();
el
})
})
.collect() .collect()
})) }))
};
}
macro_rules! impl_from_multi_node_ref {
($ty:ty) => {
impl<R> From<&[NodeRef<R>]> for ElementsMaybeSignal<$ty, $ty>
where
R: ElementType + Clone + 'static,
R::Output: JsCast + Into<$ty> + Clone + 'static,
{
fn from(node_refs: &[NodeRef<R>]) -> Self {
let node_refs = node_refs.to_vec();
impl_from_multi_node_ref_inner!($ty, node_refs)
}
}
impl<R, const C: usize> From<[NodeRef<R>; C]> for ElementsMaybeSignal<$ty, $ty>
where
R: ElementType + Clone + 'static,
R::Output: JsCast + Into<$ty> + Clone + 'static,
{
fn from(node_refs: [NodeRef<R>; C]) -> Self {
let node_refs = node_refs.to_vec();
impl_from_multi_node_ref_inner!($ty, node_refs)
}
}
impl<R> From<Vec<NodeRef<R>>> for ElementsMaybeSignal<$ty, $ty>
where
R: ElementType + Clone + 'static,
R::Output: JsCast + Into<$ty> + Clone + 'static,
{
fn from(node_refs: Vec<NodeRef<R>>) -> Self {
let node_refs = node_refs.clone();
impl_from_multi_node_ref_inner!($ty, node_refs)
}
}
};
}
impl_from_multi_node_ref!(web_sys::EventTarget);
impl_from_multi_node_ref!(web_sys::Element);
// // From multiple leptos::html::HTMLElement /////////////////////////////////////////
//
// macro_rules! impl_from_multi_html_element {
// ($ty:ty) => {
// impl<E, At, Ch, Rndr> From<&[HtmlElement<E, At, Ch, Rndr>]>
// for ElementsMaybeSignal<$ty, $ty>
// where
// E: ElementType,
// E::Output: std::ops::Deref<Target = $ty>,
// {
// fn from(value: &[HtmlElement<E, At, Ch, Rndr>]) -> Self {
// Self::Static(
// value
// .iter()
// .map(|el| {
// let el: &$ty = el.deref();
// Some(el.clone())
// })
// .collect(),
// )
// }
// }
//
// impl<E, At, Ch, Rndr, const C: usize> From<[HtmlElement<E, At, Ch, Rndr>; C]>
// for ElementsMaybeSignal<$ty, $ty>
// where
// E: ElementType,
// E::Output: std::ops::Deref<Target = $ty>,
// {
// fn from(value: [HtmlElement<E, At, Ch, Rndr>; C]) -> Self {
// Self::Static(
// value
// .iter()
// .map(|el| {
// let el: &$ty = el.deref();
// Some(el.clone())
// })
// .collect(),
// )
// }
// }
//
// impl<E, At, Ch, Rndr> From<Vec<HtmlElement<E, At, Ch, Rndr>>>
// for ElementsMaybeSignal<$ty, $ty>
// where
// E: ElementType,
// E::Output: std::ops::Deref<Target = $ty>,
// {
// fn from(value: Vec<HtmlElement<E, At, Ch, Rndr>>) -> Self {
// Self::Static(
// value
// .iter()
// .map(|el| {
// let el: &$ty = el.deref();
// Some(el.clone())
// })
// .collect(),
// )
// }
// }
// };
// }
//
// impl_from_multi_html_element!(web_sys::EventTarget);
// impl_from_multi_html_element!(web_sys::Element);
// From ElementMaybeSignal //////////////////////////////////////////////////////////////
impl<T, E> From<ElementMaybeSignal<T, E>> for ElementsMaybeSignal<T, E>
where
T: Into<E> + Clone + 'static,
{
fn from(signal: ElementMaybeSignal<T, E>) -> Self {
match signal {
ElementMaybeSignal::Dynamic(signal) => {
Self::Dynamic(Signal::derive_local(move || vec![signal.get()]))
}
ElementMaybeSignal::Static(el) => {
Self::Static(SendWrapper::new(vec![SendWrapper::take(el)]))
}
ElementMaybeSignal::_Phantom(_) => unreachable!(),
}
} }
} }

View file

@ -35,7 +35,7 @@ mod signal_throttled;
#[cfg(feature = "sync_signal")] #[cfg(feature = "sync_signal")]
mod sync_signal; mod sync_signal;
#[cfg(feature = "use_active_element")] #[cfg(feature = "use_active_element")]
// mod use_active_element; mod use_active_element;
#[cfg(feature = "use_breakpoints")] #[cfg(feature = "use_breakpoints")]
mod use_breakpoints; mod use_breakpoints;
#[cfg(feature = "use_broadcast_channel")] #[cfg(feature = "use_broadcast_channel")]
@ -126,6 +126,8 @@ mod use_service_worker;
mod use_sorted; mod use_sorted;
#[cfg(feature = "use_supported")] #[cfg(feature = "use_supported")]
mod use_supported; mod use_supported;
#[cfg(feature = "use_textarea_autosize")]
mod use_textarea_autosize;
#[cfg(feature = "use_throttle_fn")] #[cfg(feature = "use_throttle_fn")]
mod use_throttle_fn; mod use_throttle_fn;
#[cfg(feature = "use_timeout_fn")] #[cfg(feature = "use_timeout_fn")]
@ -181,7 +183,7 @@ pub use signal_throttled::*;
#[cfg(feature = "sync_signal")] #[cfg(feature = "sync_signal")]
pub use sync_signal::*; pub use sync_signal::*;
#[cfg(feature = "use_active_element")] #[cfg(feature = "use_active_element")]
// pub use use_active_element::*; pub use use_active_element::*;
#[cfg(feature = "use_breakpoints")] #[cfg(feature = "use_breakpoints")]
pub use use_breakpoints::*; pub use use_breakpoints::*;
#[cfg(feature = "use_broadcast_channel")] #[cfg(feature = "use_broadcast_channel")]
@ -272,6 +274,8 @@ pub use use_service_worker::*;
pub use use_sorted::*; pub use use_sorted::*;
#[cfg(feature = "use_supported")] #[cfg(feature = "use_supported")]
pub use use_supported::*; pub use use_supported::*;
#[cfg(feature = "use_textarea_autosize")]
pub use use_textarea_autosize::*;
#[cfg(feature = "use_throttle_fn")] #[cfg(feature = "use_throttle_fn")]
pub use use_throttle_fn::*; pub use use_throttle_fn::*;
#[cfg(feature = "use_timeout_fn")] #[cfg(feature = "use_timeout_fn")]

View file

@ -1,6 +1,6 @@
use crate::math::shared::use_simple_math; use crate::math::shared::use_simple_math;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use num::Float; use num::Float;
use paste::paste; use paste::paste;

View file

@ -1,6 +1,6 @@
use crate::math::shared::use_binary_logic; use crate::math::shared::use_binary_logic;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use paste::paste; use paste::paste;
use_binary_logic!( use_binary_logic!(

View file

@ -1,6 +1,6 @@
use crate::math::shared::use_simple_math; use crate::math::shared::use_simple_math;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use num::Float; use num::Float;
use paste::paste; use paste::paste;

View file

@ -1,6 +1,6 @@
use crate::math::shared::use_simple_math; use crate::math::shared::use_simple_math;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use num::Float; use num::Float;
use paste::paste; use paste::paste;

View file

@ -1,6 +1,6 @@
use crate::math::shared::use_partial_cmp; use crate::math::shared::use_partial_cmp;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::cmp::Ordering; use std::cmp::Ordering;
use_partial_cmp!( use_partial_cmp!(

View file

@ -1,6 +1,6 @@
use crate::math::shared::use_partial_cmp; use crate::math::shared::use_partial_cmp;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::cmp::Ordering; use std::cmp::Ordering;
use_partial_cmp!( use_partial_cmp!(

View file

@ -1,5 +1,5 @@
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
/// Reactive `NOT` condition. /// Reactive `NOT` condition.
/// ///

View file

@ -1,6 +1,6 @@
use crate::math::shared::use_binary_logic; use crate::math::shared::use_binary_logic;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use paste::paste; use paste::paste;
use_binary_logic!( use_binary_logic!(

View file

@ -1,6 +1,6 @@
use crate::math::shared::use_simple_math; use crate::math::shared::use_simple_math;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use num::Float; use num::Float;
use paste::paste; use paste::paste;

View file

@ -1,4 +1,4 @@
use crate::core::{ElementMaybeSignal, ElementsMaybeSignal}; use crate::core::{ElementsMaybeSignal, IntoElementMaybeSignal, IntoElementsMaybeSignal};
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
@ -79,33 +79,24 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] {
/// ## Server-Side Rendering /// ## Server-Side Rendering
/// ///
/// On the server this amounts to a no-op. /// On the server this amounts to a no-op.
pub fn on_click_outside<El, T, F>(target: El, handler: F) -> impl FnOnce() + Clone pub fn on_click_outside<El, M, F>(target: El, handler: F) -> impl FnOnce() + Clone
where where
El: Clone, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
T: Into<web_sys::EventTarget> + Clone + 'static,
F: FnMut(web_sys::Event) + Clone + 'static, F: FnMut(web_sys::Event) + Clone + 'static,
{ {
on_click_outside_with_options::<_, _, _, web_sys::EventTarget>( on_click_outside_with_options(target, handler, OnClickOutsideOptions::default())
target,
handler,
OnClickOutsideOptions::default(),
)
} }
/// Version of `on_click_outside` that takes an `OnClickOutsideOptions`. See `on_click_outside` for more details. /// Version of `on_click_outside` that takes an `OnClickOutsideOptions`. See `on_click_outside` for more details.
#[cfg_attr(feature = "ssr", allow(unused_variables))] #[cfg_attr(feature = "ssr", allow(unused_variables))]
pub fn on_click_outside_with_options<El, T, F, I>( pub fn on_click_outside_with_options<El, M, F>(
target: El, target: El,
handler: F, handler: F,
options: OnClickOutsideOptions<I>, options: OnClickOutsideOptions,
) -> impl FnOnce() + Clone ) -> impl FnOnce() + Clone
where where
El: Clone, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
T: Into<web_sys::EventTarget> + Clone + 'static,
F: FnMut(web_sys::Event) + Clone + 'static, F: FnMut(web_sys::Event) + Clone + 'static,
I: Into<web_sys::EventTarget> + Clone + 'static,
{ {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
{ {
@ -148,25 +139,21 @@ where
let ignore = ignore.get_untracked(); let ignore = ignore.get_untracked();
ignore.into_iter().flatten().any(|element| { ignore.into_iter().flatten().any(|element| {
let element: web_sys::EventTarget = element.into();
event_target::<web_sys::EventTarget>(event) == element event_target::<web_sys::EventTarget>(event) == element
|| event.composed_path().includes(element.as_ref(), 0) || event.composed_path().includes(element.as_ref(), 0)
}) })
}; };
let target = target.into(); let target = target.into_element_maybe_signal();
let listener = { let listener = {
let should_listen = Rc::clone(&should_listen); let should_listen = Rc::clone(&should_listen);
let mut handler = handler.clone(); let mut handler = handler.clone();
let target = target.clone();
let should_ignore = should_ignore.clone(); let should_ignore = should_ignore.clone();
let target = target.clone();
move |event: web_sys::UiEvent| { move |event: web_sys::UiEvent| {
if let Some(el) = target.get_untracked() { if let Some(el) = target.get_untracked() {
let el = el.into();
if el == event_target(&event) || event.composed_path().includes(el.as_ref(), 0) if el == event_target(&event) || event.composed_path().includes(el.as_ref(), 0)
{ {
return; return;
@ -182,7 +169,7 @@ where
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
handler(event.into()); handler(event.into());
} }
@ -211,10 +198,8 @@ where
pointerdown, pointerdown,
move |event| { move |event| {
if let Some(el) = target.get_untracked() { if let Some(el) = target.get_untracked() {
should_listen.set( should_listen
!event.composed_path().includes(el.into().as_ref(), 0) .set(!event.composed_path().includes(&el, 0) && !should_ignore(&event));
&& !should_ignore(&event),
);
} }
}, },
UseEventListenerOptions::default().passive(true), UseEventListenerOptions::default().passive(true),
@ -235,7 +220,6 @@ where
if let Some(active_element) = document().active_element() { if let Some(active_element) = document().active_element() {
if active_element.tag_name() == "IFRAME" if active_element.tag_name() == "IFRAME"
&& !el && !el
.into()
.unchecked_into::<web_sys::Node>() .unchecked_into::<web_sys::Node>()
.contains(Some(&active_element.into())) .contains(Some(&active_element.into()))
{ {
@ -265,13 +249,10 @@ where
/// Options for [`on_click_outside_with_options`]. /// Options for [`on_click_outside_with_options`].
#[derive(Clone, DefaultBuilder)] #[derive(Clone, DefaultBuilder)]
#[cfg_attr(feature = "ssr", allow(dead_code))] #[cfg_attr(feature = "ssr", allow(dead_code))]
pub struct OnClickOutsideOptions<T> pub struct OnClickOutsideOptions {
where
T: Into<web_sys::EventTarget> + Clone + 'static,
{
/// List of elementss that should not trigger the callback. Defaults to `[]`. /// List of elementss that should not trigger the callback. Defaults to `[]`.
#[builder(skip)] #[builder(skip)]
ignore: ElementsMaybeSignal<T, web_sys::EventTarget>, ignore: ElementsMaybeSignal<web_sys::EventTarget>,
/// Use capturing phase for internal event listener. Defaults to `true`. /// Use capturing phase for internal event listener. Defaults to `true`.
capture: bool, capture: bool,
@ -280,28 +261,25 @@ where
detect_iframes: bool, detect_iframes: bool,
} }
impl<T> Default for OnClickOutsideOptions<T> impl Default for OnClickOutsideOptions {
where
T: Into<web_sys::EventTarget> + Clone + 'static,
{
fn default() -> Self { fn default() -> Self {
Self { Self {
ignore: Default::default(), ignore: Vec::<web_sys::EventTarget>::new().into_elements_maybe_signal(),
capture: true, capture: true,
detect_iframes: false, detect_iframes: false,
} }
} }
} }
impl<T> OnClickOutsideOptions<T> impl OnClickOutsideOptions {
where /// List of elements that should not trigger the callback. Defaults to `[]`.
T: Into<web_sys::EventTarget> + Clone + 'static,
{
/// List of elementss that should not trigger the callback. Defaults to `[]`.
#[cfg_attr(feature = "ssr", allow(dead_code))] #[cfg_attr(feature = "ssr", allow(dead_code))]
pub fn ignore(self, ignore: impl Into<ElementsMaybeSignal<T, web_sys::EventTarget>>) -> Self { pub fn ignore<M: ?Sized>(
self,
ignore: impl IntoElementsMaybeSignal<web_sys::EventTarget, M>,
) -> Self {
Self { Self {
ignore: ignore.into(), ignore: ignore.into_elements_maybe_signal(),
..self ..self
} }
} }

View file

@ -1,7 +1,7 @@
use crate::utils::signal_filtered; use crate::utils::signal_filtered;
use crate::{use_debounce_fn_with_options, DebounceOptions}; use crate::{use_debounce_fn_with_options, DebounceOptions};
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use paste::paste; use paste::paste;
signal_filtered!( signal_filtered!(

View file

@ -1,7 +1,7 @@
use crate::utils::signal_filtered; use crate::utils::signal_filtered;
use crate::{use_throttle_fn_with_options, ThrottleOptions}; use crate::{use_throttle_fn_with_options, ThrottleOptions};
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use paste::paste; use paste::paste;
signal_filtered!( signal_filtered!(

View file

@ -1,7 +1,7 @@
use super::{use_storage_with_options, StorageType, UseStorageOptions}; use super::{use_storage_with_options, StorageType, UseStorageOptions};
use codee::{Decoder, Encoder}; use codee::{Decoder, Encoder};
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
#[allow(rustdoc::bare_urls)] #[allow(rustdoc::bare_urls)]
/// Reactive [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). /// Reactive [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).

View file

@ -2,7 +2,7 @@ use crate::{core::MaybeRwSignal, storage::StorageType, utils::FilterOptions};
use codee::{CodecError, Decoder, Encoder}; use codee::{CodecError, Decoder, Encoder};
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::sync::Arc; use std::sync::Arc;
use thiserror::Error; use thiserror::Error;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;

View file

@ -226,7 +226,7 @@ where
move |new_value, _, _| { move |new_value, _, _| {
if !is_sync_update.get_value() { if !is_sync_update.get_value() {
is_sync_update.set_value(true); is_sync_update.set_value(true);
right.update(|right| assign_ltr(right, new_value)); right.try_update(|right| assign_ltr(right, new_value));
is_sync_update.set_value(false); is_sync_update.set_value(false);
} }
}, },
@ -240,7 +240,7 @@ where
move |new_value, _, _| { move |new_value, _, _| {
if !is_sync_update.get_value() { if !is_sync_update.get_value() {
is_sync_update.set_value(true); is_sync_update.set_value(true);
left.update(|left| assign_rtl(left, new_value)); left.try_update(|left| assign_rtl(left, new_value));
is_sync_update.set_value(false); is_sync_update.set_value(false);
} }
}, },

View file

@ -2,9 +2,8 @@
use crate::{use_document, use_event_listener_with_options, use_window, UseEventListenerOptions}; use crate::{use_document, use_event_listener_with_options, use_window, UseEventListenerOptions};
use leptos::ev::{blur, focus}; use leptos::ev::{blur, focus};
use leptos::html::{AnyElement, ToHtmlElement};
use leptos::reactive_graph::wrappers::read::Signal;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive::wrappers::read::Signal;
/// Reactive `document.activeElement` /// Reactive `document.activeElement`
/// ///
@ -16,14 +15,13 @@ use leptos::prelude::*;
/// ///
/// ``` /// ```
/// # use leptos::prelude::*; /// # use leptos::prelude::*;
/// # use leptos::logging::log; /// # use leptos_use::use_active_element;
/// use leptos_use::use_active_element;
/// # /// #
/// # #[component] /// # #[component]
/// # fn Demo() -> impl IntoView { /// # fn Demo() -> impl IntoView {
/// let active_element = use_active_element(); /// let active_element = use_active_element();
/// ///
/// create_effect(move |_| { /// Effect::new(move || {
/// log!("focus changed to {:?}", active_element.get()); /// log!("focus changed to {:?}", active_element.get());
/// }); /// });
/// # /// #
@ -34,14 +32,10 @@ use leptos::prelude::*;
/// ## Server-Side Rendering /// ## Server-Side Rendering
/// ///
/// On the server this returns a `Signal` that always contains the value `None`. /// On the server this returns a `Signal` that always contains the value `None`.
pub fn use_active_element() -> Signal<Option<HtmlElement<AnyElement>>> { pub fn use_active_element() -> Signal<Option<web_sys::Element>, LocalStorage> {
let get_active_element = move || { let get_active_element = move || use_document().active_element();
use_document()
.active_element()
.map(|el| el.to_leptos_element())
};
let (active_element, set_active_element) = signal(get_active_element()); let (active_element, set_active_element) = signal_local(get_active_element());
let listener_options = UseEventListenerOptions::default().capture(true); let listener_options = UseEventListenerOptions::default().capture(true);

View file

@ -1,7 +1,7 @@
use crate::{use_media_query, use_window}; use crate::{use_media_query, use_window};
use leptos::logging::error; use leptos::logging::error;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use paste::paste; use paste::paste;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;

View file

@ -72,13 +72,7 @@ use wasm_bindgen::JsValue;
/// ``` /// ```
pub fn use_broadcast_channel<T, C>( pub fn use_broadcast_channel<T, C>(
name: &str, name: &str,
) -> UseBroadcastChannelReturn< ) -> UseBroadcastChannelReturn<T, impl Fn(&T) + Clone, impl Fn() + Clone, C>
T,
impl Fn(&T) + Clone,
impl Fn() + Clone,
<C as Encoder<T>>::Error,
<C as Decoder<T>>::Error,
>
where where
T: Send + Sync, T: Send + Sync,
C: Encoder<T, Encoded = String> + Decoder<T, Encoded = str>, C: Encoder<T, Encoded = String> + Decoder<T, Encoded = str>,
@ -179,13 +173,12 @@ where
} }
/// Return type of [`use_broadcast_channel`]. /// Return type of [`use_broadcast_channel`].
pub struct UseBroadcastChannelReturn<T, PFn, CFn, E, D> pub struct UseBroadcastChannelReturn<T, PFn, CFn, C>
where where
T: Send + Sync + 'static, T: Send + Sync + 'static,
PFn: Fn(&T) + Clone, PFn: Fn(&T) + Clone,
CFn: Fn() + Clone, CFn: Fn() + Clone,
E: Send + Sync + 'static, C: Encoder<T> + Decoder<T>,
D: Send + Sync + 'static,
{ {
/// `true` if this browser supports `BroadcastChannel`s. /// `true` if this browser supports `BroadcastChannel`s.
pub is_supported: Signal<bool>, pub is_supported: Signal<bool>,
@ -203,12 +196,14 @@ where
pub close: CFn, pub close: CFn,
/// Latest error as reported by the `messageerror` event. /// Latest error as reported by the `messageerror` event.
pub error: Signal<Option<UseBroadcastChannelError<E, D>>, LocalStorage>, pub error: Signal<Option<ErrorType<T, C>>, LocalStorage>,
/// Wether the channel is closed /// Wether the channel is closed
pub is_closed: Signal<bool>, pub is_closed: Signal<bool>,
} }
type ErrorType<T, C> = UseBroadcastChannelError<<C as Encoder<T>>::Error, <C as Decoder<T>>::Error>;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum UseBroadcastChannelError<E, D> { pub enum UseBroadcastChannelError<E, D> {
#[error("failed to post message")] #[error("failed to post message")]

View file

@ -2,7 +2,7 @@ use crate::{js, js_fut, use_event_listener, use_supported, UseTimeoutFnReturn};
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::ev::{copy, cut}; use leptos::ev::{copy, cut};
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
/// Reactive [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API). /// Reactive [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API).
/// ///
@ -77,7 +77,7 @@ pub fn use_clipboard_with_options(
let update_text = move |_| { let update_text = move |_| {
if is_supported.get() { if is_supported.get() {
leptos::spawn::spawn_local(async move { leptos::task::spawn_local(async move {
let clipboard = window().navigator().clipboard(); let clipboard = window().navigator().clipboard();
if let Ok(text) = js_fut!(clipboard.read_text()).await { if let Ok(text) = js_fut!(clipboard.read_text()).await {
set_text.set(text.as_string()); set_text.set(text.as_string());
@ -99,7 +99,7 @@ pub fn use_clipboard_with_options(
let start = start.clone(); let start = start.clone();
let value = value.to_owned(); let value = value.to_owned();
leptos::spawn::spawn_local(async move { leptos::task::spawn_local(async move {
let clipboard = window().navigator().clipboard(); let clipboard = window().navigator().clipboard();
if js_fut!(clipboard.write_text(&value)).await.is_ok() { if js_fut!(clipboard.write_text(&value)).await.is_ok() {
set_text.set(Some(value)); set_text.set(Some(value));

View file

@ -1,5 +1,5 @@
use crate::core::url; use crate::core::url;
use crate::core::{ElementMaybeSignal, MaybeRwSignal}; use crate::core::{ElementMaybeSignal, IntoElementMaybeSignal, MaybeRwSignal};
use crate::storage::{use_storage_with_options, StorageType, UseStorageOptions}; use crate::storage::{use_storage_with_options, StorageType, UseStorageOptions};
use crate::utils::get_header; use crate::utils::get_header;
use crate::{ use crate::{
@ -9,7 +9,7 @@ use crate::{
use codee::string::FromToStringCodec; use codee::string::FromToStringCodec;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::str::FromStr; use std::str::FromStr;
@ -147,11 +147,10 @@ pub fn use_color_mode() -> UseColorModeReturn {
} }
/// Version of [`use_color_mode`] that takes a `UseColorModeOptions`. See [`use_color_mode`] for how to use. /// Version of [`use_color_mode`] that takes a `UseColorModeOptions`. See [`use_color_mode`] for how to use.
pub fn use_color_mode_with_options<El, T>(options: UseColorModeOptions<El, T>) -> UseColorModeReturn pub fn use_color_mode_with_options<El, M>(options: UseColorModeOptions<El, M>) -> UseColorModeReturn
where where
El: Clone, El: IntoElementMaybeSignal<web_sys::Element, M>,
El: Into<ElementMaybeSignal<T, web_sys::Element>>, M: ?Sized,
T: Into<web_sys::Element> + Clone + 'static,
{ {
let UseColorModeOptions { let UseColorModeOptions {
target, target,
@ -216,11 +215,15 @@ where
let _ = sync_signal_with_options( let _ = sync_signal_with_options(
(cookie, set_cookie), (cookie, set_cookie),
(store, set_store), (store, set_store),
SyncSignalOptions::with_transforms( SyncSignalOptions::with_assigns(
move |cookie: &Option<ColorMode>| { move |store: &mut ColorMode, cookie: &Option<ColorMode>| {
cookie.clone().unwrap_or_else(|| store.get_untracked()) if let Some(cookie) = cookie {
*store = cookie.clone();
}
},
move |cookie: &mut Option<ColorMode>, store: &ColorMode| {
*cookie = Some(store.clone())
}, },
move |store: &ColorMode| Some(store.clone()),
), ),
); );
} }
@ -243,17 +246,13 @@ where
} }
}); });
let target = target.into(); let target = target.into_element_maybe_signal();
let update_html_attrs = { let update_html_attrs = {
move |target: ElementMaybeSignal<T, web_sys::Element>, move |target: ElementMaybeSignal<web_sys::Element>, attribute: String, value: ColorMode| {
attribute: String,
value: ColorMode| {
let el = target.get_untracked(); let el = target.get_untracked();
if let Some(el) = el { if let Some(el) = el {
let el = el.into();
let mut style: Option<web_sys::HtmlStyleElement> = None; let mut style: Option<web_sys::HtmlStyleElement> = None;
if !transition_enabled { if !transition_enabled {
if let Ok(styl) = document().create_element("style") { if let Ok(styl) = document().create_element("style") {
@ -414,11 +413,10 @@ impl FromStr for ColorMode {
} }
#[derive(DefaultBuilder)] #[derive(DefaultBuilder)]
pub struct UseColorModeOptions<El, T> pub struct UseColorModeOptions<El, M>
where where
El: Clone, El: IntoElementMaybeSignal<web_sys::Element, M>,
El: Into<ElementMaybeSignal<T, web_sys::Element>>, M: ?Sized,
T: Into<web_sys::Element> + Clone + 'static,
{ {
/// Element that the color mode will be applied to. Defaults to `"html"`. /// Element that the color mode will be applied to. Defaults to `"html"`.
target: El, target: El,
@ -502,12 +500,12 @@ where
ssr_color_header_getter: Arc<dyn Fn() -> Option<String> + Send + Sync>, ssr_color_header_getter: Arc<dyn Fn() -> Option<String> + Send + Sync>,
#[builder(skip)] #[builder(skip)]
_marker: PhantomData<T>, _marker: PhantomData<M>,
} }
type OnChangedFn = Arc<dyn Fn(ColorMode, Arc<dyn Fn(ColorMode) + Send + Sync>) + Send + Sync>; type OnChangedFn = Arc<dyn Fn(ColorMode, Arc<dyn Fn(ColorMode) + Send + Sync>) + Send + Sync>;
impl Default for UseColorModeOptions<&'static str, web_sys::Element> { impl Default for UseColorModeOptions<&'static str, str> {
fn default() -> Self { fn default() -> Self {
Self { Self {
target: "html", target: "html",

View file

@ -126,6 +126,8 @@ use std::sync::Arc;
/// { /// {
/// Some("Somehow get the value of the cookie header as a string".to_owned()) /// Some("Somehow get the value of the cookie header as a string".to_owned())
/// } /// }
/// #[cfg(not(feature = "ssr"))]
/// None
/// }) /// })
/// .ssr_set_cookie(|cookie: &Cookie| { /// .ssr_set_cookie(|cookie: &Cookie| {
/// #[cfg(feature = "ssr")] /// #[cfg(feature = "ssr")]
@ -233,7 +235,7 @@ where
return; return;
} }
let value = cookie.with_untracked(|cookie| { let value = cookie.try_with_untracked(|cookie| {
cookie.as_ref().and_then(|cookie| { cookie.as_ref().and_then(|cookie| {
C::encode(cookie) C::encode(cookie)
.map_err(|err| on_error(CodecError::Encode(err))) .map_err(|err| on_error(CodecError::Encode(err)))
@ -241,29 +243,31 @@ where
}) })
}); });
if value if let Some(value) = value {
== jar.with_value(|jar| jar.get(&cookie_name).map(|c| c.value().to_owned())) if value
{ == jar.with_value(|jar| jar.get(&cookie_name).map(|c| c.value().to_owned()))
return; {
return;
}
jar.update_value(|jar| {
write_client_cookie(
&cookie_name,
&value,
jar,
max_age,
expires,
&domain,
&path,
same_site,
secure,
http_only,
Arc::clone(&ssr_cookies_header_getter),
);
});
post(&value);
} }
jar.update_value(|jar| {
write_client_cookie(
&cookie_name,
&value,
jar,
max_age,
expires,
&domain,
&path,
same_site,
secure,
http_only,
Arc::clone(&ssr_cookies_header_getter),
);
});
post(&value);
} }
}; };
@ -272,7 +276,7 @@ where
resume, resume,
stop, stop,
.. ..
} = watch_pausable(move || cookie.get(), { } = watch_pausable(move || cookie.track(), {
let on_cookie_change = on_cookie_change.clone(); let on_cookie_change = on_cookie_change.clone();
move |_, _, _| { move |_, _, _| {
@ -365,30 +369,29 @@ where
let domain = domain.clone(); let domain = domain.clone();
let path = path.clone(); let path = path.clone();
let value = cookie if let Some(value) = cookie.try_with(|cookie| {
.with(|cookie| { cookie.as_ref().map(|cookie| {
cookie.as_ref().map(|cookie| { C::encode(cookie)
C::encode(cookie) .map_err(|err| on_error(CodecError::Encode(err)))
.map_err(|err| on_error(CodecError::Encode(err))) .ok()
.ok()
})
}) })
.flatten(); }) {
jar.update_value(|jar| { jar.update_value(|jar| {
write_server_cookie( write_server_cookie(
&cookie_name, &cookie_name,
value, value.flatten(),
jar, jar,
max_age, max_age,
expires, expires,
domain, domain,
path, path,
same_site, same_site,
secure, secure,
http_only, http_only,
Arc::clone(&ssr_set_cookie), Arc::clone(&ssr_set_cookie),
) )
}); });
}
} }
}); });
} }

View file

@ -1,11 +1,10 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))] #![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
use crate::core::ElementMaybeSignal; use crate::core::IntoElementMaybeSignal;
use crate::{ use crate::{
use_mutation_observer_with_options, watch_with_options, UseMutationObserverOptions, use_mutation_observer_with_options, watch_with_options, UseMutationObserverOptions,
WatchOptions, WatchOptions,
}; };
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -85,15 +84,14 @@ pub fn use_css_var(
} }
/// Version of [`use_css_var`] that takes a `UseCssVarOptions`. See [`use_css_var`] for how to use. /// Version of [`use_css_var`] that takes a `UseCssVarOptions`. See [`use_css_var`] for how to use.
pub fn use_css_var_with_options<P, El, T>( pub fn use_css_var_with_options<P, El, M>(
prop: P, prop: P,
options: UseCssVarOptions<El, T>, options: UseCssVarOptions<El, M>,
) -> (ReadSignal<String>, WriteSignal<String>) ) -> (ReadSignal<String>, WriteSignal<String>)
where where
P: Into<MaybeSignal<String>>, P: Into<MaybeSignal<String>>,
El: Clone, El: Clone,
El: Into<ElementMaybeSignal<T, web_sys::Element>>, El: IntoElementMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
{ {
let UseCssVarOptions { let UseCssVarOptions {
target, target,
@ -104,8 +102,9 @@ where
let (variable, set_variable) = signal(initial_value.clone()); let (variable, set_variable) = signal(initial_value.clone());
cfg_if! { if #[cfg(not(feature = "ssr"))] { #[cfg(not(feature = "ssr"))]
let el_signal = target.into(); {
let el_signal = target.into_element_maybe_signal();
let prop = prop.into(); let prop = prop.into();
let update_css_var = { let update_css_var = {
@ -116,7 +115,7 @@ where
let key = prop.get_untracked(); let key = prop.get_untracked();
if let Some(el) = el_signal.get_untracked() { if let Some(el) = el_signal.get_untracked() {
if let Ok(Some(style)) = window().get_computed_style(&el.into()) { if let Ok(Some(style)) = window().get_computed_style(&el) {
if let Ok(value) = style.get_property_value(&key) { if let Ok(value) = style.get_property_value(&key) {
set_variable.update(|var| *var = value.trim().to_string()); set_variable.update(|var| *var = value.trim().to_string());
return; return;
@ -133,11 +132,10 @@ where
let update_css_var = update_css_var.clone(); let update_css_var = update_css_var.clone();
let el_signal = el_signal.clone(); let el_signal = el_signal.clone();
use_mutation_observer_with_options::<ElementMaybeSignal<T, web_sys::Element>, T, _>( use_mutation_observer_with_options(
el_signal, el_signal,
move |_, _| update_css_var(), move |_, _| update_css_var(),
UseMutationObserverOptions::default() UseMutationObserverOptions::default().attribute_filter(vec!["style".to_string()]),
.attribute_filter(vec!["style".to_string()]),
); );
} }
@ -149,35 +147,33 @@ where
let prop = prop.clone(); let prop = prop.clone();
let _ = watch_with_options( let _ = watch_with_options(
move || (el_signal.get(), prop.get()), move || (el_signal.get(), prop.get()),
move |_, _, _| update_css_var(), move |_, _, _| update_css_var(),
WatchOptions::default().immediate(true), WatchOptions::default().immediate(true),
); );
} }
Effect::watch( Effect::watch(
move || variable.get(), move || variable.get(),
move |val, _, _| { move |val, _, _| {
if let Some(el) = el_signal.get() { if let Some(el) = el_signal.get() {
let el = el.into().unchecked_into::<web_sys::HtmlElement>(); let el = el.unchecked_into::<web_sys::HtmlElement>();
let style = el.style(); let style = el.style();
let _ = style.set_property(&prop.get_untracked(), val); let _ = style.set_property(&prop.get_untracked(), val);
} }
}, },
false, false,
); );
}} }
(variable, set_variable) (variable, set_variable)
} }
/// Options for [`use_css_var_with_options`]. /// Options for [`use_css_var_with_options`].
#[derive(DefaultBuilder)] #[derive(DefaultBuilder)]
pub struct UseCssVarOptions<El, T> pub struct UseCssVarOptions<El, M>
where where
El: Clone, El: IntoElementMaybeSignal<web_sys::Element, M>,
El: Into<ElementMaybeSignal<T, web_sys::Element>>,
T: Into<web_sys::Element> + Clone + 'static,
{ {
/// The target element to read the variable from and set the variable on. /// The target element to read the variable from and set the variable on.
/// Defaults to the `document.documentElement`. /// Defaults to the `document.documentElement`.
@ -192,29 +188,35 @@ where
observe: bool, observe: bool,
#[builder(skip)] #[builder(skip)]
_marker: PhantomData<T>, _marker: PhantomData<M>,
} }
cfg_if! { if #[cfg(feature = "ssr")] { #[cfg(feature = "ssr")]
impl Default for UseCssVarOptions<Option<web_sys::Element>, web_sys::Element> { impl<M> Default for UseCssVarOptions<Option<web_sys::Element>, M>
fn default() -> Self { where
Self { Option<web_sys::Element>: IntoElementMaybeSignal<web_sys::Element, M>,
target: None, {
initial_value: "".into(), fn default() -> Self {
observe: false, Self {
_marker: PhantomData, target: None,
} initial_value: "".into(),
observe: false,
_marker: PhantomData,
} }
} }
} else { }
impl Default for UseCssVarOptions<web_sys::Element, web_sys::Element> {
fn default() -> Self { #[cfg(not(feature = "ssr"))]
Self { impl<M> Default for UseCssVarOptions<web_sys::Element, M>
target: document().document_element().expect("No document element"), where
initial_value: "".into(), web_sys::Element: IntoElementMaybeSignal<web_sys::Element, M>,
observe: false, {
_marker: PhantomData, fn default() -> Self {
} Self {
target: document().document_element().expect("No document element"),
initial_value: "".into(),
observe: false,
_marker: PhantomData,
} }
} }
}} }

View file

@ -1,5 +1,5 @@
use cfg_if::cfg_if; use cfg_if::cfg_if;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
/// Reactive [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent). /// Reactive [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent).
/// ///

View file

@ -2,7 +2,7 @@ use crate::core::MaybeRwSignal;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen::{JsCast, JsValue};
/// Reactive [`mediaDevices.getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) streaming. /// Reactive [`mediaDevices.getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) streaming.
@ -83,7 +83,7 @@ pub fn use_display_media_with_options(
let start = move || { let start = move || {
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
leptos::spawn::spawn_local(async move { leptos::task::spawn_local(async move {
_start().await; _start().await;
stream.with_untracked(move |stream| { stream.with_untracked(move |stream| {
if let Some(Ok(_)) = stream { if let Some(Ok(_)) = stream {
@ -103,7 +103,7 @@ pub fn use_display_media_with_options(
move || enabled.get(), move || enabled.get(),
move |enabled, _, _| { move |enabled, _, _| {
if *enabled { if *enabled {
leptos::spawn::spawn_local(async move { leptos::task::spawn_local(async move {
_start().await; _start().await;
}); });
} else { } else {

View file

@ -4,7 +4,7 @@ use crate::use_event_listener;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use leptos::ev::visibilitychange; use leptos::ev::visibilitychange;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
/// Reactively track `document.visibilityState` /// Reactively track `document.visibilityState`
/// ///

View file

@ -1,9 +1,9 @@
use crate::core::{ElementMaybeSignal, MaybeRwSignal, PointerType, Position}; use crate::core::{IntoElementMaybeSignal, MaybeRwSignal, PointerType, Position};
use crate::{use_event_listener_with_options, use_window, UseEventListenerOptions, UseWindow}; use crate::{use_event_listener_with_options, use_window, UseEventListenerOptions, UseWindow};
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::ev::{pointerdown, pointermove, pointerup}; use leptos::ev::{pointerdown, pointermove, pointerup};
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
@ -45,34 +45,22 @@ use web_sys::PointerEvent;
/// } /// }
/// # } /// # }
/// ``` /// ```
pub fn use_draggable<El, T>(target: El) -> UseDraggableReturn pub fn use_draggable<El, M>(target: El) -> UseDraggableReturn
where where
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
T: Into<web_sys::EventTarget> + Clone + 'static,
{ {
use_draggable_with_options::< use_draggable_with_options::<El, M, _, _, _, _>(target, UseDraggableOptions::default())
El,
T,
UseWindow,
web_sys::Window,
web_sys::EventTarget,
web_sys::EventTarget,
>(target, UseDraggableOptions::default())
} }
/// Version of [`use_draggable`] that takes a `UseDraggableOptions`. See [`use_draggable`] for how to use. /// Version of [`use_draggable`] that takes a `UseDraggableOptions`. See [`use_draggable`] for how to use.
pub fn use_draggable_with_options<El, T, DragEl, DragT, HandleEl, HandleT>( pub fn use_draggable_with_options<El, M, DragEl, DragM, HandleEl, HandleM>(
target: El, target: El,
options: UseDraggableOptions<DragEl, DragT, HandleEl, HandleT>, options: UseDraggableOptions<DragEl, DragM, HandleEl, HandleM>,
) -> UseDraggableReturn ) -> UseDraggableReturn
where where
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
T: Into<web_sys::EventTarget> + Clone + 'static, DragEl: IntoElementMaybeSignal<web_sys::EventTarget, DragM>,
DragEl: Clone, HandleEl: IntoElementMaybeSignal<web_sys::EventTarget, HandleM>,
DragEl: Into<ElementMaybeSignal<DragT, web_sys::EventTarget>>,
DragT: Into<web_sys::EventTarget> + Clone + 'static,
HandleEl: Into<ElementMaybeSignal<HandleT, web_sys::EventTarget>>,
HandleT: Into<web_sys::EventTarget> + Clone + 'static,
{ {
let UseDraggableOptions { let UseDraggableOptions {
exact, exact,
@ -88,14 +76,12 @@ where
.. ..
} = options; } = options;
let target = target.into(); let target = target.into_element_maybe_signal();
let dragging_handle = if let Some(handle) = handle { let dragging_handle = if let Some(handle) = handle {
let handle: ElementMaybeSignal<_, _> = handle.into(); handle.into_element_maybe_signal()
Signal::derive_local(move || handle.get().map(|handle| handle.into()))
} else { } else {
let target = target.clone(); target.clone()
Signal::derive_local(move || target.get().map(|target| target.into()))
}; };
let (position, set_position) = initial_value.into_signal(); let (position, set_position) = initial_value.into_signal();
@ -124,7 +110,7 @@ where
} }
if let Some(target) = target.get_untracked() { if let Some(target) = target.get_untracked() {
let target: web_sys::Element = target.into().unchecked_into(); let target: web_sys::Element = target.unchecked_into();
if exact.get_untracked() && event_target::<web_sys::Element>(&event) != target { if exact.get_untracked() && event_target::<web_sys::Element>(&event) != target {
return; return;
@ -137,7 +123,7 @@ where
}; };
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let zone = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
if !on_start(UseDraggableCallbackArgs { if !on_start(UseDraggableCallbackArgs {
position, position,
@ -172,7 +158,7 @@ where
set_position.set(position); set_position.set(position);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let zone = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_move(UseDraggableCallbackArgs { on_move(UseDraggableCallbackArgs {
position, position,
@ -197,7 +183,7 @@ where
set_start_position.set(None); set_start_position.set(None);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let zone = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_end(UseDraggableCallbackArgs { on_end(UseDraggableCallbackArgs {
position: position.get_untracked(), position: position.get_untracked(),
@ -210,6 +196,8 @@ where
handle_event(event); handle_event(event);
}; };
let dragging_element = dragging_element.into_element_maybe_signal();
let listener_options = UseEventListenerOptions::default().capture(true); let listener_options = UseEventListenerOptions::default().capture(true);
let _ = use_event_listener_with_options( let _ = use_event_listener_with_options(
@ -246,12 +234,10 @@ where
/// Options for [`use_draggable_with_options`]. /// Options for [`use_draggable_with_options`].
#[derive(DefaultBuilder)] #[derive(DefaultBuilder)]
pub struct UseDraggableOptions<DragEl, DragT, HandleEl, HandleT> pub struct UseDraggableOptions<DragEl, DragM, HandleEl, HandleM>
where where
DragEl: Into<ElementMaybeSignal<DragT, web_sys::EventTarget>>, DragEl: IntoElementMaybeSignal<web_sys::EventTarget, DragM>,
DragT: Into<web_sys::EventTarget> + Clone + 'static, HandleEl: IntoElementMaybeSignal<web_sys::EventTarget, HandleM>,
HandleEl: Into<ElementMaybeSignal<HandleT, web_sys::EventTarget>>,
HandleT: Into<web_sys::EventTarget> + Clone + 'static,
{ {
/// Only start the dragging when click on the element directly. Defaults to `false`. /// Only start the dragging when click on the element directly. Defaults to `false`.
#[builder(into)] #[builder(into)]
@ -288,13 +274,16 @@ where
on_end: Arc<dyn Fn(UseDraggableCallbackArgs) + Send + Sync>, on_end: Arc<dyn Fn(UseDraggableCallbackArgs) + Send + Sync>,
#[builder(skip)] #[builder(skip)]
_marker1: PhantomData<DragT>, _marker1: PhantomData<DragM>,
#[builder(skip)] #[builder(skip)]
_marker2: PhantomData<HandleT>, _marker2: PhantomData<HandleM>,
} }
impl Default impl<DragM, HandleM> Default
for UseDraggableOptions<UseWindow, web_sys::Window, web_sys::EventTarget, web_sys::EventTarget> for UseDraggableOptions<UseWindow, DragM, Option<web_sys::EventTarget>, HandleM>
where
UseWindow: IntoElementMaybeSignal<web_sys::EventTarget, DragM>,
Option<web_sys::EventTarget>: IntoElementMaybeSignal<web_sys::EventTarget, HandleM>,
{ {
fn default() -> Self { fn default() -> Self {
Self { Self {

View file

@ -1,8 +1,9 @@
use crate::core::ElementMaybeSignal; use crate::core::IntoElementMaybeSignal;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use send_wrapper::SendWrapper;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::sync::Arc; use std::sync::Arc;
@ -52,31 +53,29 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] {
/// ///
/// On the server the returned `file` signal always contains an empty `Vec` and /// On the server the returned `file` signal always contains an empty `Vec` and
/// `is_over_drop_zone` contains always `false` /// `is_over_drop_zone` contains always `false`
pub fn use_drop_zone<El, T>(target: El) -> UseDropZoneReturn pub fn use_drop_zone<El, M>(target: El) -> UseDropZoneReturn
where where
El: Clone, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
T: Into<web_sys::EventTarget> + Clone + 'static,
{ {
use_drop_zone_with_options(target, UseDropZoneOptions::default()) use_drop_zone_with_options(target, UseDropZoneOptions::default())
} }
/// Version of [`use_drop_zone`] that takes a `UseDropZoneOptions`. See [`use_drop_zone`] for how to use. /// Version of [`use_drop_zone`] that takes a `UseDropZoneOptions`. See [`use_drop_zone`] for how to use.
#[cfg_attr(feature = "ssr", allow(unused_variables))] #[cfg_attr(feature = "ssr", allow(unused_variables))]
pub fn use_drop_zone_with_options<El, T>( pub fn use_drop_zone_with_options<El, M>(
target: El, target: El,
options: UseDropZoneOptions, options: UseDropZoneOptions,
) -> UseDropZoneReturn ) -> UseDropZoneReturn
where where
El: Clone, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
T: Into<web_sys::EventTarget> + Clone + 'static,
{ {
let (is_over_drop_zone, set_over_drop_zone) = signal(false); let (is_over_drop_zone, set_over_drop_zone) = signal(false);
let (files, set_files) = signal_local(Vec::<web_sys::File>::new()); let (files, set_files) = signal(Vec::<SendWrapper<web_sys::File>>::new());
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
{ {
use std::ops::Deref;
let UseDropZoneOptions { let UseDropZoneOptions {
on_drop, on_drop,
on_enter, on_enter,
@ -94,12 +93,15 @@ where
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(web_sys::File::from) .map(web_sys::File::from)
.map(SendWrapper::new)
.collect(); .collect();
set_files.update(move |f| *f = files); set_files.update(move |f| *f = files);
} }
}; };
let target = target.into_element_maybe_signal();
let _ = use_event_listener(target.clone(), dragenter, move |event| { let _ = use_event_listener(target.clone(), dragenter, move |event| {
event.prevent_default(); event.prevent_default();
counter.update_value(|counter| *counter += 1); counter.update_value(|counter| *counter += 1);
@ -108,10 +110,11 @@ where
update_files(&event); update_files(&event);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_enter(UseDropZoneEvent { on_enter(UseDropZoneEvent {
files: files.get_untracked().into_iter().collect(), files: files
.with_untracked(|files| files.iter().map(|f| f.deref().clone()).collect()),
event, event,
}); });
}); });
@ -121,10 +124,11 @@ where
update_files(&event); update_files(&event);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_over(UseDropZoneEvent { on_over(UseDropZoneEvent {
files: files.get_untracked().into_iter().collect(), files: files
.with_untracked(|files| files.iter().map(|f| f.deref().clone()).collect()),
event, event,
}); });
}); });
@ -139,10 +143,11 @@ where
update_files(&event); update_files(&event);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_leave(UseDropZoneEvent { on_leave(UseDropZoneEvent {
files: files.get_untracked().into_iter().collect(), files: files
.with_untracked(|files| files.iter().map(|f| f.deref().clone()).collect()),
event, event,
}); });
}); });
@ -155,10 +160,11 @@ where
update_files(&event); update_files(&event);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_drop(UseDropZoneEvent { on_drop(UseDropZoneEvent {
files: files.get_untracked().into_iter().collect(), files: files
.with_untracked(|files| files.iter().map(|f| f.deref().clone()).collect()),
event, event,
}); });
}); });
@ -213,7 +219,7 @@ pub struct UseDropZoneEvent {
/// Return type of [`use_drop_zone`]. /// Return type of [`use_drop_zone`].
pub struct UseDropZoneReturn { pub struct UseDropZoneReturn {
/// Files being handled /// Files being handled
pub files: Signal<Vec<web_sys::File>, LocalStorage>, pub files: Signal<Vec<SendWrapper<web_sys::File>>>,
/// Whether the files (dragged by the pointer) are over the drop zone /// Whether the files (dragged by the pointer) are over the drop zone
pub is_over_drop_zone: Signal<bool>, pub is_over_drop_zone: Signal<bool>,
} }

View file

@ -1,8 +1,7 @@
use crate::core::ElementMaybeSignal; use crate::core::IntoElementMaybeSignal;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
/// Reactive [bounding box](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) of an HTML element /// Reactive [bounding box](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) of an HTML element
/// ///
@ -30,22 +29,20 @@ use leptos::reactive_graph::wrappers::read::Signal;
/// ## Server-Side Rendering /// ## Server-Side Rendering
/// ///
/// On the server the returned signals always are `0.0` and `update` is a no-op. /// On the server the returned signals always are `0.0` and `update` is a no-op.
pub fn use_element_bounding<El, T>(target: El) -> UseElementBoundingReturn<impl Fn() + Clone> pub fn use_element_bounding<El, M>(target: El) -> UseElementBoundingReturn<impl Fn() + Clone>
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>> + Clone, El: IntoElementMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
{ {
use_element_bounding_with_options(target, UseElementBoundingOptions::default()) use_element_bounding_with_options(target, UseElementBoundingOptions::default())
} }
/// Version of [`use_element_bounding`] that takes a `UseElementBoundingOptions`. See [`use_element_bounding`] for how to use. /// Version of [`use_element_bounding`] that takes a `UseElementBoundingOptions`. See [`use_element_bounding`] for how to use.
pub fn use_element_bounding_with_options<El, T>( pub fn use_element_bounding_with_options<El, M>(
target: El, target: El,
options: UseElementBoundingOptions, options: UseElementBoundingOptions,
) -> UseElementBoundingReturn<impl Fn() + Clone> ) -> UseElementBoundingReturn<impl Fn() + Clone>
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>> + Clone, El: IntoElementMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
{ {
let (height, set_height) = signal(0.0); let (height, set_height) = signal(0.0);
let (width, set_width) = signal(0.0); let (width, set_width) = signal(0.0);
@ -56,7 +53,10 @@ where
let (x, set_x) = signal(0.0); let (x, set_x) = signal(0.0);
let (y, set_y) = signal(0.0); let (y, set_y) = signal(0.0);
cfg_if! { if #[cfg(feature = "ssr")] { let update;
#[cfg(feature = "ssr")]
{
let _ = target; let _ = target;
let _ = options; let _ = options;
@ -69,8 +69,11 @@ where
let _ = set_x; let _ = set_x;
let _ = set_y; let _ = set_y;
let update = move || (); update = move || ();
} else { }
#[cfg(not(feature = "ssr"))]
{
use crate::{ use crate::{
use_event_listener_with_options, use_resize_observer, use_window, use_event_listener_with_options, use_resize_observer, use_window,
UseEventListenerOptions, UseEventListenerOptions,
@ -84,16 +87,16 @@ where
immediate, immediate,
} = options; } = options;
let target = target.into(); let target = target.into_element_maybe_signal();
let update = { update = {
let target = target.clone(); let target = target.clone();
move || { move || {
let el = target.get_untracked(); let el = target.get_untracked();
if let Some(el) = el { if let Some(el) = el {
let rect = el.into().get_bounding_client_rect(); let rect = el.get_bounding_client_rect();
set_height.set(rect.height()); set_height.set(rect.height());
set_width.set(rect.width()); set_width.set(rect.width());
@ -164,7 +167,7 @@ where
if immediate { if immediate {
update(); update();
} }
}} }
UseElementBoundingReturn { UseElementBoundingReturn {
height: height.into(), height: height.into(),

View file

@ -1,11 +1,11 @@
use crate::core::ElementMaybeSignal; use crate::core::IntoElementMaybeSignal;
use crate::{use_event_listener_with_options, UseEventListenerOptions}; use crate::{use_event_listener_with_options, UseEventListenerOptions};
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::ev::{mouseenter, mouseleave}; use leptos::ev::{mouseenter, mouseleave};
use leptos::leptos_dom::helpers::TimeoutHandle; use leptos::leptos_dom::helpers::TimeoutHandle;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
use std::time::Duration; use std::time::Duration;
@ -38,11 +38,9 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] {
/// ## Server-Side Rendering /// ## Server-Side Rendering
/// ///
/// On the server this returns a `Signal` that always contains the value `false`. /// On the server this returns a `Signal` that always contains the value `false`.
pub fn use_element_hover<El, T>(el: El) -> Signal<bool> pub fn use_element_hover<El, M>(el: El) -> Signal<bool>
where where
El: Clone, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
T: Into<web_sys::EventTarget> + Clone + 'static,
{ {
use_element_hover_with_options(el, UseElementHoverOptions::default()) use_element_hover_with_options(el, UseElementHoverOptions::default())
} }
@ -50,14 +48,12 @@ where
/// Version of [`use_element_hover`] that takes a `UseElementHoverOptions`. See [`use_element_hover`] for how to use. /// Version of [`use_element_hover`] that takes a `UseElementHoverOptions`. See [`use_element_hover`] for how to use.
#[cfg_attr(feature = "ssr", allow(unused_variables, unused_mut))] #[cfg_attr(feature = "ssr", allow(unused_variables, unused_mut))]
pub fn use_element_hover_with_options<El, T>( pub fn use_element_hover_with_options<El, M>(
el: El, el: El,
options: UseElementHoverOptions, options: UseElementHoverOptions,
) -> Signal<bool> ) -> Signal<bool>
where where
El: Clone, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
T: Into<web_sys::EventTarget> + Clone + 'static,
{ {
let UseElementHoverOptions { let UseElementHoverOptions {
delay_enter, delay_enter,
@ -90,6 +86,8 @@ where
let listener_options = UseEventListenerOptions::default().passive(true); let listener_options = UseEventListenerOptions::default().passive(true);
let el = el.into_element_maybe_signal();
let _ = use_event_listener_with_options( let _ = use_event_listener_with_options(
el.clone(), el.clone(),
mouseenter, mouseenter,

View file

@ -1,8 +1,9 @@
use crate::core::{ElementMaybeSignal, Size}; use crate::core::IntoElementMaybeSignal;
use crate::core::Size;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
use crate::{use_resize_observer_with_options, UseResizeObserverOptions}; use crate::{use_resize_observer_with_options, UseResizeObserverOptions};
@ -47,23 +48,21 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] {
/// ## See also /// ## See also
/// ///
/// - [`fn@crate::use_resize_observer`] /// - [`fn@crate::use_resize_observer`]
pub fn use_element_size<El, T>(target: El) -> UseElementSizeReturn pub fn use_element_size<El, M>(target: El) -> UseElementSizeReturn
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>> + Clone, El: IntoElementMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
{ {
use_element_size_with_options(target, UseElementSizeOptions::default()) use_element_size_with_options(target, UseElementSizeOptions::default())
} }
/// Version of [`use_element_size`] that takes a `UseElementSizeOptions`. See [`use_element_size`] for how to use. /// Version of [`use_element_size`] that takes a `UseElementSizeOptions`. See [`use_element_size`] for how to use.
#[cfg_attr(feature = "ssr", allow(unused_variables))] #[cfg_attr(feature = "ssr", allow(unused_variables))]
pub fn use_element_size_with_options<El, T>( pub fn use_element_size_with_options<El, M>(
target: El, target: El,
options: UseElementSizeOptions, options: UseElementSizeOptions,
) -> UseElementSizeReturn ) -> UseElementSizeReturn
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>> + Clone, El: IntoElementMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
{ {
let UseElementSizeOptions { box_, initial_size } = options; let UseElementSizeOptions { box_, initial_size } = options;
@ -74,7 +73,7 @@ where
{ {
let box_ = box_.unwrap_or(web_sys::ResizeObserverBoxOptions::ContentBox); let box_ = box_.unwrap_or(web_sys::ResizeObserverBoxOptions::ContentBox);
let target = target.into(); let target = target.into_element_maybe_signal();
let is_svg = { let is_svg = {
let target = target.clone(); let target = target.clone();
@ -82,7 +81,6 @@ where
move || { move || {
if let Some(target) = target.get_untracked() { if let Some(target) = target.get_untracked() {
target target
.into()
.namespace_uri() .namespace_uri()
.map(|ns| ns.contains("svg")) .map(|ns| ns.contains("svg"))
.unwrap_or(false) .unwrap_or(false)
@ -92,11 +90,11 @@ where
} }
}; };
{ let _ = use_resize_observer_with_options(
let target = target.clone(); target.clone(),
{
let target = target.clone();
let _ = use_resize_observer_with_options::<ElementMaybeSignal<T, web_sys::Element>, _, _>(
target.clone(),
move |entries, _| { move |entries, _| {
let entry = &entries[0]; let entry = &entries[0];
@ -111,7 +109,7 @@ where
if is_svg() { if is_svg() {
if let Some(target) = target.get() { if let Some(target) = target.get() {
if let Ok(Some(styles)) = window().get_computed_style(&target.into()) { if let Ok(Some(styles)) = window().get_computed_style(&target) {
set_height.set( set_height.set(
styles styles
.get_property_value("height") .get_property_value("height")
@ -155,10 +153,10 @@ where
set_width.set(entry.content_rect().width()); set_width.set(entry.content_rect().width());
set_height.set(entry.content_rect().height()) set_height.set(entry.content_rect().height())
} }
}, }
UseResizeObserverOptions::default().box_(box_), },
); UseResizeObserverOptions::default().box_(box_),
} );
let _ = watch_with_options( let _ = watch_with_options(
move || target.get(), move || target.get(),

View file

@ -1,4 +1,4 @@
use crate::core::ElementMaybeSignal; use crate::core::IntoElementMaybeSignal;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
@ -6,7 +6,7 @@ use std::marker::PhantomData;
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
use crate::{use_intersection_observer_with_options, UseIntersectionObserverOptions}; use crate::{use_intersection_observer_with_options, UseIntersectionObserverOptions};
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
/// Tracks the visibility of an element within the viewport. /// Tracks the visibility of an element within the viewport.
/// ///
@ -42,12 +42,11 @@ use leptos::reactive_graph::wrappers::read::Signal;
/// ## See also /// ## See also
/// ///
/// * [`fn@crate::use_intersection_observer`] /// * [`fn@crate::use_intersection_observer`]
pub fn use_element_visibility<El, T>(target: El) -> Signal<bool> pub fn use_element_visibility<El, M>(target: El) -> Signal<bool>
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>>, El: IntoElementMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
{ {
use_element_visibility_with_options::<El, T, web_sys::Element, web_sys::Element>( use_element_visibility_with_options::<El, M, web_sys::Element, _>(
target, target,
UseElementVisibilityOptions::default(), UseElementVisibilityOptions::default(),
) )
@ -55,21 +54,19 @@ where
/// Version of [`use_element_visibility`] with that takes a `UseElementVisibilityOptions`. See [`use_element_visibility`] for how to use. /// Version of [`use_element_visibility`] with that takes a `UseElementVisibilityOptions`. See [`use_element_visibility`] for how to use.
#[cfg_attr(feature = "ssr", allow(unused_variables))] #[cfg_attr(feature = "ssr", allow(unused_variables))]
pub fn use_element_visibility_with_options<El, T, ContainerEl, ContainerT>( pub fn use_element_visibility_with_options<El, M, ContainerEl, ContainerM>(
target: El, target: El,
options: UseElementVisibilityOptions<ContainerEl, ContainerT>, options: UseElementVisibilityOptions<ContainerEl, ContainerM>,
) -> Signal<bool> ) -> Signal<bool>
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>>, El: IntoElementMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static, ContainerEl: IntoElementMaybeSignal<web_sys::Element, ContainerM>,
ContainerEl: Into<ElementMaybeSignal<ContainerT, web_sys::Element>>,
ContainerT: Into<web_sys::Element> + Clone + 'static,
{ {
let (is_visible, set_visible) = signal(false); let (is_visible, set_visible) = signal(false);
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
use_intersection_observer_with_options( use_intersection_observer_with_options(
target.into(), target.into_element_maybe_signal(),
move |entries, _| { move |entries, _| {
// In some circumstances Chrome passes a first (or only) entry which has a zero bounding client rect // In some circumstances Chrome passes a first (or only) entry which has a zero bounding client rect
// and returns `is_intersecting` erroneously as `false`. // and returns `is_intersecting` erroneously as `false`.
@ -89,10 +86,9 @@ where
/// Options for [`use_element_visibility_with_options`]. /// Options for [`use_element_visibility_with_options`].
#[derive(DefaultBuilder)] #[derive(DefaultBuilder)]
pub struct UseElementVisibilityOptions<El, T> pub struct UseElementVisibilityOptions<El, M>
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>>, El: IntoElementMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
{ {
/// A `web_sys::Element` or `web_sys::Document` object which is an ancestor of the intended `target`, /// A `web_sys::Element` or `web_sys::Document` object which is an ancestor of the intended `target`,
/// whose bounding rectangle will be considered the viewport. /// whose bounding rectangle will be considered the viewport.
@ -104,10 +100,13 @@ where
viewport: Option<El>, viewport: Option<El>,
#[builder(skip)] #[builder(skip)]
_marker: PhantomData<T>, _marker: PhantomData<M>,
} }
impl Default for UseElementVisibilityOptions<web_sys::Element, web_sys::Element> { impl<M> Default for UseElementVisibilityOptions<web_sys::Element, M>
where
web_sys::Element: IntoElementMaybeSignal<web_sys::Element, M>,
{
fn default() -> Self { fn default() -> Self {
Self { Self {
viewport: None, viewport: None,

View file

@ -1,4 +1,4 @@
use crate::core::ElementMaybeSignal; use crate::core::IntoElementMaybeSignal;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::ev::EventDescriptor; use leptos::ev::EventDescriptor;
@ -88,11 +88,10 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] {
/// ## Server-Side Rendering /// ## Server-Side Rendering
/// ///
/// On the server this amounts to a noop. /// On the server this amounts to a noop.
pub fn use_event_listener<Ev, El, T, F>(target: El, event: Ev, handler: F) -> impl Fn() + Clone pub fn use_event_listener<Ev, El, M, F>(target: El, event: Ev, handler: F) -> impl Fn() + Clone
where where
Ev: EventDescriptor + 'static, Ev: EventDescriptor + 'static,
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
T: Into<web_sys::EventTarget> + Clone + 'static,
F: FnMut(<Ev as EventDescriptor>::EventType) + 'static, F: FnMut(<Ev as EventDescriptor>::EventType) + 'static,
{ {
use_event_listener_with_options(target, event, handler, UseEventListenerOptions::default()) use_event_listener_with_options(target, event, handler, UseEventListenerOptions::default())
@ -101,7 +100,7 @@ where
/// Version of [`use_event_listener`] that takes `web_sys::AddEventListenerOptions`. See the docs for [`use_event_listener`] for how to use. /// Version of [`use_event_listener`] that takes `web_sys::AddEventListenerOptions`. See the docs for [`use_event_listener`] for how to use.
#[cfg_attr(feature = "ssr", allow(unused_variables))] #[cfg_attr(feature = "ssr", allow(unused_variables))]
#[allow(unused_mut)] #[allow(unused_mut)]
pub fn use_event_listener_with_options<Ev, El, T, F>( pub fn use_event_listener_with_options<Ev, El, M, F>(
target: El, target: El,
event: Ev, event: Ev,
mut handler: F, mut handler: F,
@ -109,8 +108,7 @@ pub fn use_event_listener_with_options<Ev, El, T, F>(
) -> impl Fn() + Clone ) -> impl Fn() + Clone
where where
Ev: EventDescriptor + 'static, Ev: EventDescriptor + 'static,
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
T: Into<web_sys::EventTarget> + Clone + 'static,
F: FnMut(<Ev as EventDescriptor>::EventType) + 'static, F: FnMut(<Ev as EventDescriptor>::EventType) + 'static,
{ {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
@ -124,7 +122,7 @@ where
let event_name = event.name(); let event_name = event.name();
let closure_js = Closure::wrap(Box::new(move |e| { let closure_js = Closure::wrap(Box::new(move |e| {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
handler(e); handler(e);
}) as Box<dyn FnMut(_)>) }) as Box<dyn FnMut(_)>)
@ -145,7 +143,7 @@ where
let event_name = event.name(); let event_name = event.name();
let signal = target.into(); let signal = target.into_element_maybe_signal();
let prev_element = Rc::new(RefCell::new(None::<web_sys::EventTarget>)); let prev_element = Rc::new(RefCell::new(None::<web_sys::EventTarget>));
@ -163,7 +161,7 @@ where
let cleanup_prev_element = cleanup_prev_element.clone(); let cleanup_prev_element = cleanup_prev_element.clone();
watch_with_options( watch_with_options(
move || signal.get().map(|e| e.into()), move || signal.get(),
move |element, _, _| { move |element, _, _| {
cleanup_prev_element(); cleanup_prev_element();
prev_element.replace(element.clone()); prev_element.replace(element.clone());

View file

@ -234,9 +234,7 @@ where
); );
} else { } else {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(
);
on_failed(); on_failed();
} }

View file

@ -4,7 +4,7 @@ use crate::core::MaybeRwSignal;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
/// Reactive favicon. /// Reactive favicon.

View file

@ -1,7 +1,7 @@
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
/// Reactive [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API). /// Reactive [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API).
/// ///

View file

@ -4,7 +4,7 @@ use crate::utils::{DebounceOptions, FilterOptions, ThrottleOptions};
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
/// ///
/// ///

View file

@ -1,4 +1,4 @@
use crate::core::{Direction, Directions, ElementMaybeSignal}; use crate::core::{Direction, Directions, IntoElementMaybeSignal};
use crate::{ use crate::{
use_element_visibility, use_scroll_with_options, ScrollOffset, UseEventListenerOptions, use_element_visibility, use_scroll_with_options, ScrollOffset, UseEventListenerOptions,
UseScrollOptions, UseScrollReturn, UseScrollOptions, UseScrollReturn,
@ -7,7 +7,7 @@ use default_struct_builder::DefaultBuilder;
use futures_util::join; use futures_util::join;
use gloo_timers::future::sleep; use gloo_timers::future::sleep;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::future::Future; use std::future::Future;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@ -50,10 +50,9 @@ use wasm_bindgen::JsCast;
/// ``` /// ```
/// ///
/// The returned signal is `true` while new data is being loaded. /// The returned signal is `true` while new data is being loaded.
pub fn use_infinite_scroll<El, T, LFn, LFut>(el: El, on_load_more: LFn) -> Signal<bool> pub fn use_infinite_scroll<El, M, LFn, LFut>(el: El, on_load_more: LFn) -> Signal<bool>
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>> + Clone + 'static, El: IntoElementMaybeSignal<web_sys::Element, M> + 'static,
T: Into<web_sys::Element> + Clone + 'static,
LFn: Fn(ScrollState) -> LFut + Send + Sync + 'static, LFn: Fn(ScrollState) -> LFut + Send + Sync + 'static,
LFut: Future<Output = ()>, LFut: Future<Output = ()>,
{ {
@ -61,14 +60,13 @@ where
} }
/// Version of [`use_infinite_scroll`] that takes a `UseInfiniteScrollOptions`. See [`use_infinite_scroll`] for how to use. /// Version of [`use_infinite_scroll`] that takes a `UseInfiniteScrollOptions`. See [`use_infinite_scroll`] for how to use.
pub fn use_infinite_scroll_with_options<El, T, LFn, LFut>( pub fn use_infinite_scroll_with_options<El, M, LFn, LFut>(
el: El, el: El,
on_load_more: LFn, on_load_more: LFn,
options: UseInfiniteScrollOptions, options: UseInfiniteScrollOptions,
) -> Signal<bool> ) -> Signal<bool>
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>> + Clone + 'static, El: IntoElementMaybeSignal<web_sys::Element, M> + 'static,
T: Into<web_sys::Element> + Clone + 'static,
LFn: Fn(ScrollState) -> LFut + Send + Sync + 'static, LFn: Fn(ScrollState) -> LFut + Send + Sync + 'static,
LFut: Future<Output = ()>, LFut: Future<Output = ()>,
{ {
@ -82,6 +80,8 @@ where
let on_load_more = StoredValue::new(on_load_more); let on_load_more = StoredValue::new(on_load_more);
let el = el.into_element_maybe_signal();
let UseScrollReturn { let UseScrollReturn {
x, x,
y, y,
@ -108,13 +108,10 @@ where
let (is_loading, set_loading) = signal(false); let (is_loading, set_loading) = signal(false);
let el = el.into();
let observed_element = Signal::derive_local(move || { let observed_element = Signal::derive_local(move || {
let el = el.get(); let el = el.get();
el.map(|el| { el.map(|el| {
let el = el.into();
if el.is_instance_of::<web_sys::Window>() || el.is_instance_of::<web_sys::Document>() { if el.is_instance_of::<web_sys::Window>() || el.is_instance_of::<web_sys::Document>() {
document() document()
.document_element() .document_element()
@ -157,10 +154,9 @@ where
set_loading.set(true); set_loading.set(true);
let measure = measure.clone(); let measure = measure.clone();
leptos::spawn::spawn_local(async move { leptos::task::spawn_local(async move {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let zone = let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
join!( join!(
on_load_more.with_value(|f| f(state)), on_load_more.with_value(|f| f(state)),

View file

@ -1,8 +1,8 @@
use crate::core::{ElementMaybeSignal, ElementsMaybeSignal}; use crate::core::{IntoElementMaybeSignal, IntoElementsMaybeSignal};
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::marker::PhantomData; use std::marker::PhantomData;
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
@ -55,16 +55,16 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] {
/// ## See also /// ## See also
/// ///
/// * [`fn@crate::use_element_visibility`] /// * [`fn@crate::use_element_visibility`]
pub fn use_intersection_observer<El, T, F>( pub fn use_intersection_observer<Els, M, F, RootM>(
target: El, target: Els,
callback: F, callback: F,
) -> UseIntersectionObserverReturn<impl Fn() + Clone, impl Fn() + Clone, impl Fn() + Clone> ) -> UseIntersectionObserverReturn<impl Fn() + Clone, impl Fn() + Clone, impl Fn() + Clone>
where where
El: Into<ElementsMaybeSignal<T, web_sys::Element>>, Els: IntoElementsMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
F: FnMut(Vec<web_sys::IntersectionObserverEntry>, web_sys::IntersectionObserver) + 'static, F: FnMut(Vec<web_sys::IntersectionObserverEntry>, web_sys::IntersectionObserver) + 'static,
web_sys::Element: IntoElementMaybeSignal<web_sys::Element, RootM>,
{ {
use_intersection_observer_with_options::<El, T, web_sys::Element, web_sys::Element, F>( use_intersection_observer_with_options::<Els, M, web_sys::Element, RootM, F>(
target, target,
callback, callback,
UseIntersectionObserverOptions::default(), UseIntersectionObserverOptions::default(),
@ -73,16 +73,14 @@ where
/// Version of [`use_intersection_observer`] that takes a [`UseIntersectionObserverOptions`]. See [`use_intersection_observer`] for how to use. /// Version of [`use_intersection_observer`] that takes a [`UseIntersectionObserverOptions`]. See [`use_intersection_observer`] for how to use.
#[cfg_attr(feature = "ssr", allow(unused_variables, unused_mut))] #[cfg_attr(feature = "ssr", allow(unused_variables, unused_mut))]
pub fn use_intersection_observer_with_options<El, T, RootEl, RootT, F>( pub fn use_intersection_observer_with_options<Els, M, RootEl, RootM, F>(
target: El, target: Els,
mut callback: F, mut callback: F,
options: UseIntersectionObserverOptions<RootEl, RootT>, options: UseIntersectionObserverOptions<RootEl, RootM>,
) -> UseIntersectionObserverReturn<impl Fn() + Clone, impl Fn() + Clone, impl Fn() + Clone> ) -> UseIntersectionObserverReturn<impl Fn() + Clone, impl Fn() + Clone, impl Fn() + Clone>
where where
El: Into<ElementsMaybeSignal<T, web_sys::Element>>, Els: IntoElementsMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static, RootEl: IntoElementMaybeSignal<web_sys::Element, RootM>,
RootEl: Into<ElementMaybeSignal<RootT, web_sys::Element>>,
RootT: Into<web_sys::Element> + Clone + 'static,
F: FnMut(Vec<web_sys::IntersectionObserverEntry>, web_sys::IntersectionObserver) + 'static, F: FnMut(Vec<web_sys::IntersectionObserverEntry>, web_sys::IntersectionObserver) + 'static,
{ {
let UseIntersectionObserverOptions { let UseIntersectionObserverOptions {
@ -105,7 +103,7 @@ where
let closure_js = Closure::<dyn FnMut(js_sys::Array, web_sys::IntersectionObserver)>::new( let closure_js = Closure::<dyn FnMut(js_sys::Array, web_sys::IntersectionObserver)>::new(
move |entries: js_sys::Array, observer| { move |entries: js_sys::Array, observer| {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
callback( callback(
entries entries
@ -132,8 +130,8 @@ where
} }
}; };
let targets = target.into(); let targets = target.into_elements_maybe_signal();
let root = root.map(|root| (root).into()); let root = root.map(|root| root.into_element_maybe_signal());
let stop_watch = { let stop_watch = {
let cleanup = cleanup.clone(); let cleanup = cleanup.clone();
@ -166,7 +164,7 @@ where
); );
if let Some(Some(root)) = root { if let Some(Some(root)) = root {
let root: web_sys::Element = root.clone().into(); let root = root.clone();
options.set_root(Some(&root)); options.set_root(Some(&root));
} }
@ -177,7 +175,7 @@ where
.expect("failed to create IntersectionObserver"); .expect("failed to create IntersectionObserver");
for target in targets.iter().flatten() { for target in targets.iter().flatten() {
let target: web_sys::Element = target.clone().into(); let target = target.clone();
obs.observe(&target); obs.observe(&target);
} }
@ -221,10 +219,9 @@ where
/// Options for [`use_intersection_observer_with_options`]. /// Options for [`use_intersection_observer_with_options`].
#[derive(DefaultBuilder)] #[derive(DefaultBuilder)]
pub struct UseIntersectionObserverOptions<El, T> pub struct UseIntersectionObserverOptions<El, M>
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>>, El: IntoElementMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
{ {
/// If `true`, the `IntersectionObserver` will be attached immediately. Otherwise it /// If `true`, the `IntersectionObserver` will be attached immediately. Otherwise it
/// will only be attached after the returned `resume` closure is called. That is /// will only be attached after the returned `resume` closure is called. That is
@ -257,10 +254,13 @@ where
thresholds: Vec<f64>, thresholds: Vec<f64>,
#[builder(skip)] #[builder(skip)]
_marker: PhantomData<T>, _marker: PhantomData<M>,
} }
impl Default for UseIntersectionObserverOptions<web_sys::Element, web_sys::Element> { impl<M> Default for UseIntersectionObserverOptions<web_sys::Element, M>
where
web_sys::Element: IntoElementMaybeSignal<web_sys::Element, M>,
{
fn default() -> Self { fn default() -> Self {
Self { Self {
immediate: true, immediate: true,

View file

@ -2,7 +2,7 @@ use crate::utils::Pausable;
use crate::{use_interval_fn_with_options, UseIntervalFnOptions}; use crate::{use_interval_fn_with_options, UseIntervalFnOptions};
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::rc::Rc; use std::rc::Rc;
/// Reactive counter increases on every interval. /// Reactive counter increases on every interval.

View file

@ -104,7 +104,7 @@ where
move || { move || {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
callback(); callback();
} }

View file

@ -5,7 +5,7 @@ use crate::utils::js_value_from_to_string;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::fmt::Display; use std::fmt::Display;
use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen::{JsCast, JsValue};

View file

@ -4,7 +4,7 @@ use crate::use_event_listener;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use leptos::ev::change; use leptos::ev::change;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;

View file

@ -1,8 +1,7 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))] #![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
use crate::core::{ElementMaybeSignal, Position}; use crate::core::{IntoElementMaybeSignal, Position};
use crate::{use_event_listener_with_options, use_window, UseEventListenerOptions, UseWindow}; use crate::{use_event_listener_with_options, use_window, UseEventListenerOptions, UseWindow};
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::ev::{dragover, mousemove, touchend, touchmove, touchstart}; use leptos::ev::{dragover, mousemove, touchend, touchmove, touchstart};
use leptos::prelude::*; use leptos::prelude::*;
@ -93,10 +92,9 @@ pub fn use_mouse() -> UseMouseReturn {
} }
/// Variant of [`use_mouse`] that accepts options. Please see [`use_mouse`] for how to use. /// Variant of [`use_mouse`] that accepts options. Please see [`use_mouse`] for how to use.
pub fn use_mouse_with_options<El, T, Ex>(options: UseMouseOptions<El, T, Ex>) -> UseMouseReturn pub fn use_mouse_with_options<El, M, Ex>(options: UseMouseOptions<El, M, Ex>) -> UseMouseReturn
where where
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>> + Clone, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
T: Into<web_sys::EventTarget> + Clone + 'static,
Ex: UseMouseEventExtractor + Clone + 'static, Ex: UseMouseEventExtractor + Clone + 'static,
{ {
let (x, set_x) = signal(options.initial_value.x); let (x, set_x) = signal(options.initial_value.x);
@ -155,45 +153,47 @@ where
// TODO : event filters? // TODO : event filters?
cfg_if! { if #[cfg(not(feature = "ssr"))] { #[cfg(not(feature = "ssr"))]
let target = options.target; {
let target = options.target.into_element_maybe_signal();
let event_listener_options = UseEventListenerOptions::default().passive(true); let event_listener_options = UseEventListenerOptions::default().passive(true);
let _ = use_event_listener_with_options( let _ = use_event_listener_with_options(
target.clone(), target.clone(),
mousemove, mousemove,
mouse_handler, mouse_handler,
event_listener_options, event_listener_options,
); );
let _ = use_event_listener_with_options( let _ = use_event_listener_with_options(
target.clone(), target.clone(),
dragover, dragover,
drag_handler, drag_handler,
event_listener_options, event_listener_options,
); );
if options.touch && !matches!(options.coord_type, UseMouseCoordType::Movement) { if options.touch && !matches!(options.coord_type, UseMouseCoordType::Movement) {
let _ = use_event_listener_with_options( let _ = use_event_listener_with_options(
target.clone(), target.clone(),
touchstart, touchstart,
touch_handler.clone(), touch_handler.clone(),
event_listener_options, event_listener_options,
); );
let _ = use_event_listener_with_options( let _ = use_event_listener_with_options(
target.clone(), target.clone(),
touchmove, touchmove,
touch_handler, touch_handler,
event_listener_options, event_listener_options,
); );
if options.reset_on_touch_ends { if options.reset_on_touch_ends {
let _ = use_event_listener_with_options( let _ = use_event_listener_with_options(
target, target,
touchend, touchend,
move |_| reset(), move |_| reset(),
event_listener_options, event_listener_options,
); );
} }
} }
}} }
UseMouseReturn { UseMouseReturn {
x: x.into(), x: x.into(),
@ -206,10 +206,9 @@ where
#[derive(DefaultBuilder)] #[derive(DefaultBuilder)]
/// Options for [`use_mouse_with_options`]. /// Options for [`use_mouse_with_options`].
pub struct UseMouseOptions<El, T, Ex> pub struct UseMouseOptions<El, M, Ex>
where where
El: Clone + Into<ElementMaybeSignal<T, web_sys::EventTarget>>, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
T: Into<web_sys::EventTarget> + Clone + 'static,
Ex: UseMouseEventExtractor + Clone, Ex: UseMouseEventExtractor + Clone,
{ {
/// How to extract the x, y coordinates from mouse events or touches /// How to extract the x, y coordinates from mouse events or touches
@ -228,10 +227,13 @@ where
initial_value: Position, initial_value: Position,
#[builder(skip)] #[builder(skip)]
_marker: PhantomData<T>, _marker: PhantomData<M>,
} }
impl Default for UseMouseOptions<UseWindow, web_sys::Window, Infallible> { impl<M> Default for UseMouseOptions<UseWindow, M, Infallible>
where
UseWindow: IntoElementMaybeSignal<web_sys::EventTarget, M>,
{
fn default() -> Self { fn default() -> Self {
Self { Self {
coord_type: UseMouseCoordType::default(), coord_type: UseMouseCoordType::default(),

View file

@ -1,9 +1,8 @@
use crate::core::{ElementMaybeSignal, Position}; use crate::core::{IntoElementMaybeSignal, Position};
use crate::{ use crate::{
use_mouse_with_options, use_window, UseMouseCoordType, UseMouseEventExtractor, UseMouseOptions, use_mouse_with_options, use_window, UseMouseCoordType, UseMouseEventExtractor, UseMouseOptions,
UseMouseReturn, UseMouseSourceType, UseWindow, UseMouseReturn, UseMouseSourceType, UseWindow,
}; };
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use std::convert::Infallible; use std::convert::Infallible;
@ -39,24 +38,21 @@ use std::marker::PhantomData;
/// ///
/// On the server this returns simple Signals with the `initial_value` for `x` and `y`, /// On the server this returns simple Signals with the `initial_value` for `x` and `y`,
/// no-op for `stop`, `is_outside = true` and `0.0` for the rest of the signals. /// no-op for `stop`, `is_outside = true` and `0.0` for the rest of the signals.
pub fn use_mouse_in_element<El, T>(target: El) -> UseMouseInElementReturn<impl Fn() + Clone> pub fn use_mouse_in_element<El, M>(target: El) -> UseMouseInElementReturn<impl Fn() + Clone>
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>> + Clone, El: IntoElementMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
{ {
use_mouse_in_element_with_options(target, Default::default()) use_mouse_in_element_with_options(target, Default::default())
} }
/// Version of [`use_mouse_in_element`] that takes a `UseMouseInElementOptions`. See [`use_mouse_in_element`] for how to use. /// Version of [`use_mouse_in_element`] that takes a `UseMouseInElementOptions`. See [`use_mouse_in_element`] for how to use.
pub fn use_mouse_in_element_with_options<El, T, OptEl, OptT, OptEx>( pub fn use_mouse_in_element_with_options<El, M, OptEl, OptM, OptEx>(
target: El, target: El,
options: UseMouseInElementOptions<OptEl, OptT, OptEx>, options: UseMouseInElementOptions<OptEl, OptM, OptEx>,
) -> UseMouseInElementReturn<impl Fn() + Clone> ) -> UseMouseInElementReturn<impl Fn() + Clone>
where where
El: Into<ElementMaybeSignal<T, web_sys::Element>> + Clone, El: IntoElementMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static, OptEl: IntoElementMaybeSignal<web_sys::EventTarget, OptM>,
OptEl: Into<ElementMaybeSignal<OptT, web_sys::EventTarget>> + Clone,
OptT: Into<web_sys::EventTarget> + Clone + 'static,
OptEx: UseMouseEventExtractor + Clone + 'static, OptEx: UseMouseEventExtractor + Clone + 'static,
{ {
let UseMouseInElementOptions { let UseMouseInElementOptions {
@ -88,8 +84,11 @@ where
let (element_height, set_element_height) = signal(0.0); let (element_height, set_element_height) = signal(0.0);
let (is_outside, set_outside) = signal(true); let (is_outside, set_outside) = signal(true);
cfg_if! { if #[cfg(feature = "ssr")] { let stop;
let stop = || ();
#[cfg(feature = "ssr")]
{
stop = || ();
let _ = handle_outside; let _ = handle_outside;
@ -101,18 +100,21 @@ where
let _ = set_element_height; let _ = set_element_height;
let _ = set_outside; let _ = set_outside;
let _ = target; let _ = target;
} else { }
#[cfg(not(feature = "ssr"))]
{
use crate::use_event_listener; use crate::use_event_listener;
use leptos::ev::mouseleave; use leptos::ev::mouseleave;
let target = target.into(); let target = target.into_element_maybe_signal();
let window = window(); let window = window();
let effect = Effect::watch( let effect = Effect::watch(
move || (target.get(), x.get(), y.get()), move || (target.get(), x.get(), y.get()),
move |(el, x, y), _, _| { move |(el, x, y), _, _| {
if let Some(el) = el { if let Some(el) = el {
let el: web_sys::Element = el.clone().into(); let el = el.clone();
let rect = el.get_bounding_client_rect(); let rect = el.get_bounding_client_rect();
let left = rect.left(); let left = rect.left();
let top = rect.top(); let top = rect.top();
@ -146,10 +148,10 @@ where
false, false,
); );
let stop = move || effect.stop(); stop = move || effect.stop();
let _ = use_event_listener(document(), mouseleave, move |_| set_outside.set(true)); let _ = use_event_listener(document(), mouseleave, move |_| set_outside.set(true));
}} }
UseMouseInElementReturn { UseMouseInElementReturn {
x, x,
@ -168,10 +170,9 @@ where
/// Options for [`use_mouse_in_element_with_options`]. /// Options for [`use_mouse_in_element_with_options`].
#[derive(DefaultBuilder)] #[derive(DefaultBuilder)]
pub struct UseMouseInElementOptions<El, T, Ex> pub struct UseMouseInElementOptions<El, M, Ex>
where where
El: Clone + Into<ElementMaybeSignal<T, web_sys::EventTarget>>, El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
T: Into<web_sys::EventTarget> + Clone + 'static,
Ex: UseMouseEventExtractor + Clone, Ex: UseMouseEventExtractor + Clone,
{ {
/// How to extract the x, y coordinates from mouse events or touches /// How to extract the x, y coordinates from mouse events or touches
@ -195,10 +196,13 @@ where
handle_outside: bool, handle_outside: bool,
#[builder(skip)] #[builder(skip)]
_marker: PhantomData<T>, _marker: PhantomData<M>,
} }
impl Default for UseMouseInElementOptions<UseWindow, web_sys::Window, Infallible> { impl<M> Default for UseMouseInElementOptions<UseWindow, M, Infallible>
where
UseWindow: IntoElementMaybeSignal<web_sys::EventTarget, M>,
{
fn default() -> Self { fn default() -> Self {
Self { Self {
coord_type: UseMouseCoordType::default(), coord_type: UseMouseCoordType::default(),

View file

@ -1,7 +1,7 @@
use crate::core::ElementsMaybeSignal; use crate::core::IntoElementsMaybeSignal;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
@ -50,13 +50,12 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] {
/// ## Server-Side Rendering /// ## Server-Side Rendering
/// ///
/// On the server this amounts to a no-op. /// On the server this amounts to a no-op.
pub fn use_mutation_observer<El, T, F>( pub fn use_mutation_observer<El, M, F>(
target: El, target: El,
callback: F, callback: F,
) -> UseMutationObserverReturn<impl Fn() + Clone> ) -> UseMutationObserverReturn<impl Fn() + Clone>
where where
El: Into<ElementsMaybeSignal<T, web_sys::Element>>, El: IntoElementsMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
F: FnMut(Vec<web_sys::MutationRecord>, web_sys::MutationObserver) + 'static, F: FnMut(Vec<web_sys::MutationRecord>, web_sys::MutationObserver) + 'static,
{ {
use_mutation_observer_with_options(target, callback, UseMutationObserverOptions::default()) use_mutation_observer_with_options(target, callback, UseMutationObserverOptions::default())
@ -64,14 +63,13 @@ where
/// Version of [`use_mutation_observer`] that takes a `UseMutationObserverOptions`. See [`use_mutation_observer`] for how to use. /// Version of [`use_mutation_observer`] that takes a `UseMutationObserverOptions`. See [`use_mutation_observer`] for how to use.
#[cfg_attr(feature = "ssr", allow(unused_variables, unused_mut))] #[cfg_attr(feature = "ssr", allow(unused_variables, unused_mut))]
pub fn use_mutation_observer_with_options<El, T, F>( pub fn use_mutation_observer_with_options<El, M, F>(
target: El, target: El,
mut callback: F, mut callback: F,
options: UseMutationObserverOptions, options: UseMutationObserverOptions,
) -> UseMutationObserverReturn<impl Fn() + Clone> ) -> UseMutationObserverReturn<impl Fn() + Clone>
where where
El: Into<ElementsMaybeSignal<T, web_sys::Element>>, El: IntoElementsMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
F: FnMut(Vec<web_sys::MutationRecord>, web_sys::MutationObserver) + 'static, F: FnMut(Vec<web_sys::MutationRecord>, web_sys::MutationObserver) + 'static,
{ {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
@ -90,7 +88,7 @@ where
let closure_js = Closure::<dyn FnMut(js_sys::Array, web_sys::MutationObserver)>::new( let closure_js = Closure::<dyn FnMut(js_sys::Array, web_sys::MutationObserver)>::new(
move |entries: js_sys::Array, observer| { move |entries: js_sys::Array, observer| {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
callback( callback(
entries entries
@ -120,7 +118,7 @@ where
} }
}; };
let targets = target.into(); let targets = target.into_elements_maybe_signal();
let stop_watch = { let stop_watch = {
let cleanup = cleanup.clone(); let cleanup = cleanup.clone();
@ -136,7 +134,7 @@ where
.expect("failed to create MutationObserver"); .expect("failed to create MutationObserver");
for target in targets.iter().flatten() { for target in targets.iter().flatten() {
let target: web_sys::Element = target.clone().into(); let target = target.clone();
let _ = obs.observe_with_options(&target, &options.clone().into()); let _ = obs.observe_with_options(&target, &options.clone().into());
} }

View file

@ -1,5 +1,5 @@
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::fmt::Display; use std::fmt::Display;
/// Reactive [Permissions API](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API). /// Reactive [Permissions API](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API).
@ -46,7 +46,7 @@ pub fn use_permission(permission_name: &str) -> Signal<PermissionState> {
} }
}; };
leptos::spawn::spawn_local({ leptos::task::spawn_local({
let permission_name = permission_name.to_owned(); let permission_name = permission_name.to_owned();
async move { async move {

View file

@ -1,6 +1,6 @@
use crate::use_media_query; use crate::use_media_query;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::fmt::Display; use std::fmt::Display;
/// Reactive [prefers-contrast](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-contrast) media query. /// Reactive [prefers-contrast](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-contrast) media query.

View file

@ -14,6 +14,7 @@ use std::sync::Arc;
/// ``` /// ```
/// # use leptos::prelude::*; /// # use leptos::prelude::*;
/// # use leptos_use::use_prefers_reduced_motion; /// # use leptos_use::use_prefers_reduced_motion;
/// # #[cfg(feature = "docs")]
/// # use leptos_use::docs::BooleanDisplay; /// # use leptos_use::docs::BooleanDisplay;
/// # /// #
/// # #[component] /// # #[component]

View file

@ -102,7 +102,7 @@ pub fn use_raf_fn_with_options(
}; };
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let zone = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
callback(UseRafFnCallbackArgs { delta, timestamp }); callback(UseRafFnCallbackArgs { delta, timestamp });

View file

@ -1,7 +1,7 @@
use crate::core::ElementsMaybeSignal; use crate::core::IntoElementsMaybeSignal;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
use crate::use_supported; use crate::use_supported;
@ -52,13 +52,12 @@ cfg_if! { if #[cfg(not(feature = "ssr"))] {
/// ## See also /// ## See also
/// ///
/// * [`fn@crate::use_element_size`] /// * [`fn@crate::use_element_size`]
pub fn use_resize_observer<El, T, F>( pub fn use_resize_observer<Els, M, F>(
target: El, // TODO : multiple elements? target: Els,
callback: F, callback: F,
) -> UseResizeObserverReturn<impl Fn() + Clone> ) -> UseResizeObserverReturn<impl Fn() + Clone>
where where
El: Into<ElementsMaybeSignal<T, web_sys::Element>>, Els: IntoElementsMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
F: FnMut(Vec<web_sys::ResizeObserverEntry>, web_sys::ResizeObserver) + 'static, F: FnMut(Vec<web_sys::ResizeObserverEntry>, web_sys::ResizeObserver) + 'static,
{ {
use_resize_observer_with_options(target, callback, UseResizeObserverOptions::default()) use_resize_observer_with_options(target, callback, UseResizeObserverOptions::default())
@ -66,14 +65,13 @@ where
/// Version of [`use_resize_observer`] that takes a `web_sys::ResizeObserverOptions`. See [`use_resize_observer`] for how to use. /// Version of [`use_resize_observer`] that takes a `web_sys::ResizeObserverOptions`. See [`use_resize_observer`] for how to use.
#[cfg_attr(feature = "ssr", allow(unused_variables, unused_mut))] #[cfg_attr(feature = "ssr", allow(unused_variables, unused_mut))]
pub fn use_resize_observer_with_options<El, T, F>( pub fn use_resize_observer_with_options<Els, M, F>(
target: El, // TODO : multiple elements? target: Els,
mut callback: F, mut callback: F,
options: UseResizeObserverOptions, options: UseResizeObserverOptions,
) -> UseResizeObserverReturn<impl Fn() + Clone> ) -> UseResizeObserverReturn<impl Fn() + Clone>
where where
El: Into<ElementsMaybeSignal<T, web_sys::Element>>, Els: IntoElementsMaybeSignal<web_sys::Element, M>,
T: Into<web_sys::Element> + Clone + 'static,
F: FnMut(Vec<web_sys::ResizeObserverEntry>, web_sys::ResizeObserver) + 'static, F: FnMut(Vec<web_sys::ResizeObserverEntry>, web_sys::ResizeObserver) + 'static,
{ {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
@ -91,7 +89,7 @@ where
let closure_js = Closure::<dyn FnMut(js_sys::Array, web_sys::ResizeObserver)>::new( let closure_js = Closure::<dyn FnMut(js_sys::Array, web_sys::ResizeObserver)>::new(
move |entries: js_sys::Array, observer| { move |entries: js_sys::Array, observer| {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
callback( callback(
entries entries
@ -121,7 +119,7 @@ where
} }
}; };
let targets = target.into(); let targets = target.into_elements_maybe_signal();
let stop_watch = { let stop_watch = {
let cleanup = cleanup.clone(); let cleanup = cleanup.clone();
@ -138,7 +136,7 @@ where
.expect("failed to create ResizeObserver"); .expect("failed to create ResizeObserver");
for target in targets.iter().flatten() { for target in targets.iter().flatten() {
let target: web_sys::Element = target.clone().into(); let target = target.clone();
obs.observe_with_options(&target, &options.clone().into()); obs.observe_with_options(&target, &options.clone().into());
} }
observer.replace(Some(obs)); observer.replace(Some(obs));

View file

@ -1,9 +1,9 @@
use crate::core::{Direction, Directions, ElementMaybeSignal}; use crate::core::{Direction, Directions, IntoElementMaybeSignal};
use crate::UseEventListenerOptions; use crate::UseEventListenerOptions;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::rc::Rc; use std::rc::Rc;
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
@ -176,27 +176,23 @@ const ARRIVED_STATE_THRESHOLD_PIXELS: f64 = 1.0;
/// ## Server-Side Rendering /// ## Server-Side Rendering
/// ///
/// On the server this returns signals that don't change and setters that are noops. /// On the server this returns signals that don't change and setters that are noops.
pub fn use_scroll<El, T>( pub fn use_scroll<El, M>(
element: El, element: El,
) -> UseScrollReturn<impl Fn(f64) + Clone, impl Fn(f64) + Clone, impl Fn() + Clone> ) -> UseScrollReturn<impl Fn(f64) + Clone, impl Fn(f64) + Clone, impl Fn() + Clone>
where where
El: Clone, El: IntoElementMaybeSignal<web_sys::Element, M>,
El: Into<ElementMaybeSignal<T, web_sys::Element>>,
T: Into<web_sys::Element> + Clone + 'static,
{ {
use_scroll_with_options(element, Default::default()) use_scroll_with_options(element, Default::default())
} }
/// Version of [`use_scroll`] with options. See [`use_scroll`] for how to use. /// Version of [`use_scroll`] with options. See [`use_scroll`] for how to use.
#[cfg_attr(feature = "ssr", allow(unused_variables))] #[cfg_attr(feature = "ssr", allow(unused_variables))]
pub fn use_scroll_with_options<El, T>( pub fn use_scroll_with_options<El, M>(
element: El, element: El,
options: UseScrollOptions, options: UseScrollOptions,
) -> UseScrollReturn<impl Fn(f64) + Clone, impl Fn(f64) + Clone, impl Fn() + Clone> ) -> UseScrollReturn<impl Fn(f64) + Clone, impl Fn(f64) + Clone, impl Fn() + Clone>
where where
El: Clone, El: IntoElementMaybeSignal<web_sys::Element, M>,
El: Into<ElementMaybeSignal<T, web_sys::Element>>,
T: Into<web_sys::Element> + Clone + 'static,
{ {
let (internal_x, set_internal_x) = signal(0.0); let (internal_x, set_internal_x) = signal(0.0);
let (internal_y, set_internal_y) = signal(0.0); let (internal_y, set_internal_y) = signal(0.0);
@ -216,12 +212,20 @@ where
bottom: false, bottom: false,
}); });
cfg_if! { if #[cfg(feature = "ssr")] { let set_x;
let set_x = |_| {}; let set_y;
let set_y = |_| {}; let measure;
let measure = || {};
} else { #[cfg(feature = "ssr")]
let signal = element.into(); {
set_x = |_| {};
set_y = |_| {};
measure = || {};
}
#[cfg(not(feature = "ssr"))]
{
let signal = element.into_element_maybe_signal();
let behavior = options.behavior; let behavior = options.behavior;
let scroll_to = { let scroll_to = {
@ -231,8 +235,6 @@ where
let element = signal.get_untracked(); let element = signal.get_untracked();
if let Some(element) = element { if let Some(element) = element {
let element = element.into();
let scroll_options = web_sys::ScrollToOptions::new(); let scroll_options = web_sys::ScrollToOptions::new();
scroll_options.set_behavior(behavior.get_untracked().into()); scroll_options.set_behavior(behavior.get_untracked().into());
@ -248,12 +250,12 @@ where
} }
}; };
let set_x = { set_x = {
let scroll_to = scroll_to.clone(); let scroll_to = scroll_to.clone();
move |x| scroll_to(Some(x), None) move |x| scroll_to(Some(x), None)
}; };
let set_y = move |y| scroll_to(None, Some(y)); set_y = move |y| scroll_to(None, Some(y));
let on_scroll_end = { let on_scroll_end = {
let on_stop = Rc::clone(&options.on_stop); let on_stop = Rc::clone(&options.on_stop);
@ -367,14 +369,14 @@ where
} }
}; };
let target = { let target = Signal::derive_local({
let signal = signal.clone(); let signal = signal.clone();
Signal::derive_local(move || { move || {
let element = signal.get(); let element = signal.get();
element.map(|element| element.into().unchecked_into::<web_sys::EventTarget>()) element.map(|element| element.unchecked_into::<web_sys::EventTarget>())
}) }
}; });
if throttle >= 0.0 { if throttle >= 0.0 {
let throttled_scroll_handler = use_throttle_fn_with_arg_and_options( let throttled_scroll_handler = use_throttle_fn_with_arg_and_options(
@ -393,14 +395,14 @@ where
let _ = use_event_listener_with_options::< let _ = use_event_listener_with_options::<
_, _,
Signal<Option<web_sys::EventTarget>, LocalStorage>, Signal<Option<web_sys::EventTarget>, LocalStorage>,
web_sys::EventTarget, _,
_, _,
>(target, ev::scroll, handler, options.event_listener_options); >(target, ev::scroll, handler, options.event_listener_options);
} else { } else {
let _ = use_event_listener_with_options::< let _ = use_event_listener_with_options::<
_, _,
Signal<Option<web_sys::EventTarget>, LocalStorage>, Signal<Option<web_sys::EventTarget>, LocalStorage>,
web_sys::EventTarget, _,
_, _,
>( >(
target, target,
@ -413,7 +415,7 @@ where
let _ = use_event_listener_with_options::< let _ = use_event_listener_with_options::<
_, _,
Signal<Option<web_sys::EventTarget>, LocalStorage>, Signal<Option<web_sys::EventTarget>, LocalStorage>,
web_sys::EventTarget, _,
_, _,
>( >(
target, target,
@ -422,13 +424,12 @@ where
options.event_listener_options, options.event_listener_options,
); );
let measure = move || { measure = move || {
if let Some(el) = signal.try_get_untracked().flatten() { if let Some(el) = signal.try_get_untracked().flatten() {
let el = el.into();
set_arrived_state(el); set_arrived_state(el);
} }
}; };
}} }
UseScrollReturn { UseScrollReturn {
x: internal_x.into(), x: internal_x.into(),

View file

@ -1,7 +1,7 @@
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::actions::Action; use leptos::reactive::actions::Action;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use send_wrapper::SendWrapper; use send_wrapper::SendWrapper;
use std::sync::Arc; use std::sync::Arc;
use wasm_bindgen::{prelude::Closure, JsCast, JsValue}; use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
@ -54,7 +54,7 @@ pub fn use_service_worker_with_options(
let on_controller_change = options.on_controller_change.clone(); let on_controller_change = options.on_controller_change.clone();
let js_closure = Closure::wrap(Box::new(move |_event: JsValue| { let js_closure = Closure::wrap(Box::new(move |_event: JsValue| {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_controller_change(); on_controller_change();
}) as Box<dyn FnMut(JsValue)>) }) as Box<dyn FnMut(JsValue)>)

View file

@ -1,5 +1,5 @@
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::ops::DerefMut; use std::ops::DerefMut;

View file

@ -0,0 +1,330 @@
use crate::core::{ElementMaybeSignal, IntoElementMaybeSignal, MaybeRwSignal};
use default_struct_builder::DefaultBuilder;
use leptos::prelude::*;
use std::sync::Arc;
/// Automatically update the height of a textarea depending on the content.
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_textarea_autosize)
///
/// ## Usage
///
/// ### Simple example
///
/// ```
/// # use leptos::prelude::*;
/// # use leptos::html::Textarea;
/// # use leptos_use::{use_textarea_autosize, UseTextareaAutosizeReturn};
/// #
/// # #[component]
/// # fn Demo() -> impl IntoView {
/// let textarea = NodeRef::new();
///
/// let UseTextareaAutosizeReturn {
/// content,
/// set_content,
/// trigger_resize
/// } = use_textarea_autosize(textarea);
///
/// view! {
/// <textarea
/// prop:value=content
/// on:input=move |evt| set_content.set(event_target_value(&evt))
/// node_ref=textarea
/// class="resize-none"
/// placeholder="What's on your mind?"
/// />
/// }
/// # }
/// ```
///
/// > Make sure that you set `box-sizing: border-box` on the textarea element.
/// >
/// > It's also recommended to reset the scrollbar styles for the textarea element to avoid
/// > incorrect height values for large amounts of text.
///
/// ```css
/// textarea {
/// -ms-overflow-style: none;
/// scrollbar-width: none;
/// }
///
/// textarea::-webkit-scrollbar {
/// display: none;
/// }
/// ```
///
/// ### With `rows` attribute
///
/// If you need support for the rows attribute on a textarea element, then you should set the
/// `style_prop` option to `"min-height"`.
///
/// ```
/// # use leptos::prelude::*;
/// # use leptos::html::Textarea;
/// # use leptos_use::{use_textarea_autosize_with_options, UseTextareaAutosizeOptions, UseTextareaAutosizeReturn};
/// #
/// # #[component]
/// # fn Demo() -> impl IntoView {
/// let textarea = NodeRef::new();
///
/// let UseTextareaAutosizeReturn {
/// content,
/// set_content,
/// ..
/// } = use_textarea_autosize_with_options(
/// textarea,
/// UseTextareaAutosizeOptions::default().style_prop("min-height"),
/// );
///
/// view! {
/// <textarea
/// prop:value=content
/// on:input=move |evt| set_content.set(event_target_value(&evt))
/// node_ref=textarea
/// class="resize-none"
/// placeholder="What's on your mind?"
/// rows="3"
/// />
/// }
/// # }
/// ```
///
/// ## Server-Side Rendering
///
/// On the server this will always return an empty string as ´content` and a no-op `trigger_resize`.
// #[doc(cfg(feature = "use_textarea_autosize"))]
pub fn use_textarea_autosize<El, M>(el: El) -> UseTextareaAutosizeReturn<impl Fn() + Clone>
where
El: IntoElementMaybeSignal<web_sys::Element, M> + Clone,
{
use_textarea_autosize_with_options::<El, M>(el, UseTextareaAutosizeOptions::default())
}
/// Version of [`fn@crate::use_textarea_autosize`] that takes a `UseTextareaAutosizeOptions`. See [`fn@crate::use_textarea_autosize`] for how to use.
// #[doc(cfg(feature = "use_textarea_autosize"))]
pub fn use_textarea_autosize_with_options<El, M>(
el: El,
options: UseTextareaAutosizeOptions,
) -> UseTextareaAutosizeReturn<impl Fn() + Clone>
where
El: IntoElementMaybeSignal<web_sys::Element, M> + Clone,
{
#[cfg(not(feature = "ssr"))]
{
use wasm_bindgen::JsCast;
let el = el.into_element_maybe_signal();
let textarea = Signal::derive_local(move || {
el.get()
.map(|el| el.unchecked_into::<web_sys::HtmlTextAreaElement>())
});
let UseTextareaAutosizeOptions {
content,
watch: watch_fn,
on_resize,
style_target,
style_prop,
} = options;
let (content, set_content) = content.into_signal();
let (textarea_scroll_height, set_textarea_scroll_height) = signal(1);
let (textarea_old_width, set_textarea_old_width) = signal(0.0);
let trigger_resize = move || {
textarea.with_untracked(|textarea| {
if let Some(textarea) = textarea {
let mut height = "".to_string();
let border_offset =
if let Ok(Some(style)) = window().get_computed_style(textarea) {
(parse_num(
&style
.get_property_value("border-top-width")
.unwrap_or_default(),
) + parse_num(
&style
.get_property_value("border-bottom-width")
.unwrap_or_default(),
)) as i32
} else {
0
};
web_sys::HtmlElement::style(textarea)
.set_property(&style_prop, "1px")
.ok();
set_textarea_scroll_height.set(textarea.scroll_height() + border_offset + 1);
if let Some(style_target) = style_target.get() {
// If style target is provided update its height
style_target
.unchecked_into::<web_sys::HtmlElement>()
.style()
.set_property(
&style_prop,
&format!("{}px", textarea_scroll_height.get_untracked()),
)
.ok();
} else {
// else update textarea's height by updating height variable
height = format!("{}px", textarea_scroll_height.get_untracked());
}
web_sys::HtmlElement::style(textarea)
.set_property(&style_prop, &height)
.ok();
}
})
};
Effect::watch(
move || {
content.with(|_| ());
textarea.with(|_| ());
},
{
let trigger_resize = trigger_resize.clone();
move |_, _, _| {
trigger_resize();
}
},
true,
);
Effect::watch(
move || textarea_scroll_height.track(),
move |_, _, _| {
on_resize();
},
false,
);
crate::use_resize_observer(textarea, {
let trigger_resize = trigger_resize.clone();
move |entries, _| {
for entry in entries {
let width = entry.content_rect().width();
if width != textarea_old_width.get_untracked() {
set_textarea_old_width.set(width);
trigger_resize();
}
}
}
});
Effect::watch(
move || watch_fn(),
{
let trigger_resize = trigger_resize.clone();
move |_, _, _| {
trigger_resize();
}
},
false,
);
UseTextareaAutosizeReturn {
content,
set_content,
trigger_resize,
}
}
#[cfg(feature = "ssr")]
{
let _ = el;
let _ = options;
let (content, set_content) = signal("".to_string());
UseTextareaAutosizeReturn {
content: content.into(),
set_content,
trigger_resize: || {},
}
}
}
/// Options for [`fn@crate::use_textarea_autosize_with_options`].
// #[doc(cfg(feature = "use_textarea_autosize"))]
#[derive(DefaultBuilder)]
#[cfg_attr(feature = "ssr", allow(dead_code))]
pub struct UseTextareaAutosizeOptions {
/// Textarea content
#[builder(into)]
content: MaybeRwSignal<String>,
/// Watch sources that should trigger a textarea resize
watch: Arc<dyn Fn() + Send + Sync>,
/// Function called when the textarea size changes
on_resize: Arc<dyn Fn() + Send + Sync>,
/// Specify style target to apply the height based on textarea content.
/// If not provided it will use textarea it self.
#[builder(skip)]
style_target: ElementMaybeSignal<web_sys::Element>,
/// Specify the style property that will be used to manipulate height.
/// Should be `"height"` or `"min-height"`. Default value is `"height"`.
#[builder(into)]
style_prop: String,
}
impl Default for UseTextareaAutosizeOptions {
fn default() -> Self {
Self {
content: MaybeRwSignal::default(),
watch: Arc::new(|| ()),
on_resize: Arc::new(|| ()),
style_target: Default::default(),
style_prop: "height".to_string(),
}
}
}
impl UseTextareaAutosizeOptions {
/// List of elementss that should not trigger the callback. Defaults to `[]`.
#[cfg_attr(feature = "ssr", allow(dead_code))]
pub fn style_target<M>(
self,
style_target: impl IntoElementMaybeSignal<web_sys::Element, M>,
) -> Self {
Self {
style_target: style_target.into_element_maybe_signal(),
..self
}
}
}
/// Return type of [`fn@crate::use_textarea_autosize`].
// #[doc(cfg(feature = "use_textarea_autosize"))]
pub struct UseTextareaAutosizeReturn<F>
where
F: Fn() + Clone,
{
/// The textarea content
pub content: Signal<String>,
/// Set the textarea content
pub set_content: WriteSignal<String>,
/// Function to trigger a textarea resize manually
pub trigger_resize: F,
}
#[cfg(not(feature = "ssr"))]
fn parse_num(s: &str) -> u32 {
s.chars()
.map_while(|c| c.to_digit(10))
.fold(0, |acc, digit| acc * 10 + digit)
}

View file

@ -93,9 +93,7 @@ where
*timer.lock().unwrap() = None; *timer.lock().unwrap() = None;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(
);
callback(arg); callback(arg);
} }

View file

@ -5,7 +5,7 @@ use crate::{
}; };
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::rc::Rc; use std::rc::Rc;
/// Reactive current timestamp. /// Reactive current timestamp.
@ -87,7 +87,7 @@ pub fn use_timestamp_with_controls_and_options(options: UseTimestampOptions) ->
update(); update();
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
callback(ts.get_untracked()); callback(ts.get_untracked());
} }

View file

@ -1,5 +1,4 @@
use crate::core::MaybeRwSignal; use crate::core::MaybeRwSignal;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen::{JsCast, JsValue};
@ -62,7 +61,8 @@ pub fn use_user_media_with_options(
let (stream, set_stream) = signal_local(None::<Result<web_sys::MediaStream, JsValue>>); let (stream, set_stream) = signal_local(None::<Result<web_sys::MediaStream, JsValue>>);
let _start = move || async move { let _start = move || async move {
cfg_if! { if #[cfg(not(feature = "ssr"))] { #[cfg(not(feature = "ssr"))]
{
if stream.get_untracked().is_some() { if stream.get_untracked().is_some() {
return; return;
} }
@ -70,10 +70,13 @@ pub fn use_user_media_with_options(
let stream = create_media(video, audio).await; let stream = create_media(video, audio).await;
set_stream.update(|s| *s = Some(stream)); set_stream.update(|s| *s = Some(stream));
} else { }
#[cfg(feature = "ssr")]
{
let _ = video; let _ = video;
let _ = audio; let _ = audio;
}} }
}; };
let _stop = move || { let _stop = move || {
@ -87,8 +90,9 @@ pub fn use_user_media_with_options(
}; };
let start = move || { let start = move || {
cfg_if! { if #[cfg(not(feature = "ssr"))] { #[cfg(not(feature = "ssr"))]
leptos::spawn::spawn_local(async move { {
leptos::task::spawn_local(async move {
_start().await; _start().await;
stream.with_untracked(move |stream| { stream.with_untracked(move |stream| {
if let Some(Ok(_)) = stream { if let Some(Ok(_)) = stream {
@ -96,7 +100,7 @@ pub fn use_user_media_with_options(
} }
}); });
}); });
}} }
}; };
let stop = move || { let stop = move || {
@ -108,7 +112,7 @@ pub fn use_user_media_with_options(
move || enabled.get(), move || enabled.get(),
move |enabled, _, _| { move |enabled, _, _| {
if *enabled { if *enabled {
leptos::spawn::spawn_local(async move { leptos::task::spawn_local(async move {
_start().await; _start().await;
}); });
} else { } else {

View file

@ -31,7 +31,7 @@ pub use web_sys::LockMode;
/// ///
/// # #[component] /// # #[component]
/// # fn Demo() -> impl IntoView { /// # fn Demo() -> impl IntoView {
/// leptos::spawn::spawn_local(async { /// leptos::task::spawn_local(async {
/// let res = use_web_lock("my_lock", my_process).await; /// let res = use_web_lock("my_lock", my_process).await;
/// assert!(matches!(res, Ok(42))); /// assert!(matches!(res, Ok(42)));
/// }); /// });
@ -44,7 +44,6 @@ pub use web_sys::LockMode;
/// ///
/// On the server this returns `Err(UseWebLockError::Server)` and the task is not executed. /// On the server this returns `Err(UseWebLockError::Server)` and the task is not executed.
// #[doc(cfg(feature = "use_web_lock"))] // #[doc(cfg(feature = "use_web_lock"))]
pub async fn use_web_lock<C, F, R>(name: &str, callback: C) -> Result<R, UseWebLockError> pub async fn use_web_lock<C, F, R>(name: &str, callback: C) -> Result<R, UseWebLockError>
where where
C: FnOnce(web_sys::Lock) -> F + 'static, C: FnOnce(web_sys::Lock) -> F + 'static,

View file

@ -2,7 +2,7 @@ use crate::{use_supported, use_window};
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
use std::rc::Rc; use std::rc::Rc;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
@ -76,7 +76,7 @@ pub fn use_web_notification_with_options(
let on_click = Rc::clone(&options.on_click); let on_click = Rc::clone(&options.on_click);
move |e: web_sys::Event| { move |e: web_sys::Event| {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_click(e); on_click(e);
} }
@ -87,7 +87,7 @@ pub fn use_web_notification_with_options(
let on_close = Rc::clone(&options.on_close); let on_close = Rc::clone(&options.on_close);
move |e: web_sys::Event| { move |e: web_sys::Event| {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_close(e); on_close(e);
} }
@ -98,7 +98,7 @@ pub fn use_web_notification_with_options(
let on_error = Rc::clone(&options.on_error); let on_error = Rc::clone(&options.on_error);
move |e: web_sys::Event| { move |e: web_sys::Event| {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_error(e); on_error(e);
} }
@ -109,7 +109,7 @@ pub fn use_web_notification_with_options(
let on_show = Rc::clone(&options.on_show); let on_show = Rc::clone(&options.on_show);
move |e: web_sys::Event| { move |e: web_sys::Event| {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let _z = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_show(e); on_show(e);
} }
@ -134,7 +134,7 @@ pub fn use_web_notification_with_options(
let on_error_closure = on_error_closure.clone(); let on_error_closure = on_error_closure.clone();
let on_show_closure = on_show_closure.clone(); let on_show_closure = on_show_closure.clone();
leptos::spawn::spawn_local(async move { leptos::task::spawn_local(async move {
set_permission.set(request_web_notification_permission().await); set_permission.set(request_web_notification_permission().await);
let mut notification_options = web_sys::NotificationOptions::from(&options); let mut notification_options = web_sys::NotificationOptions::from(&options);
@ -169,7 +169,7 @@ pub fn use_web_notification_with_options(
} }
}; };
leptos::spawn::spawn_local(async move { leptos::task::spawn_local(async move {
set_permission.set(request_web_notification_permission().await); set_permission.set(request_web_notification_permission().await);
}); });

View file

@ -285,7 +285,7 @@ where
let (ready_state, set_ready_state) = signal(ConnectionReadyState::Closed); let (ready_state, set_ready_state) = signal(ConnectionReadyState::Closed);
let (message, set_message) = signal(None); let (message, set_message) = signal(None);
let ws_ref: StoredValue<Option<WebSocket>, _> = StoredValue::new_local(None); let ws_signal = RwSignal::new_local(None::<WebSocket>);
let reconnect_timer_ref: StoredValue<Option<TimeoutHandle>> = StoredValue::new(None); let reconnect_timer_ref: StoredValue<Option<TimeoutHandle>> = StoredValue::new(None);
@ -308,8 +308,8 @@ where
if !manually_closed_ref.get_value() if !manually_closed_ref.get_value()
&& !reconnect_limit.is_exceeded_by(reconnect_times_ref.get_value()) && !reconnect_limit.is_exceeded_by(reconnect_times_ref.get_value())
&& ws_ref && ws_signal
.get_value() .get_untracked()
.map_or(false, |ws: WebSocket| ws.ready_state() != WebSocket::OPEN) .map_or(false, |ws: WebSocket| ws.ready_state() != WebSocket::OPEN)
{ {
reconnect_timer_ref.set_value( reconnect_timer_ref.set_value(
@ -338,7 +338,7 @@ where
Some(Arc::new(move || { Some(Arc::new(move || {
reconnect_timer_ref.set_value(None); reconnect_timer_ref.set_value(None);
if let Some(web_socket) = ws_ref.get_value() { if let Some(web_socket) = ws_signal.get_untracked() {
let _ = web_socket.close(); let _ = web_socket.close();
} }
@ -371,7 +371,7 @@ where
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let zone = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_open(e); on_open(e);
@ -412,7 +412,7 @@ where
let txt = String::from(&txt); let txt = String::from(&txt);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let zone = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_message_raw(&txt); on_message_raw(&txt);
@ -422,7 +422,7 @@ where
match C::decode_str(&txt) { match C::decode_str(&txt) {
Ok(val) => { Ok(val) => {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let prev = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let prev = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_message(&val); on_message(&val);
@ -443,7 +443,7 @@ where
let array = array.to_vec(); let array = array.to_vec();
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let zone = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_message_raw_bytes(&array); on_message_raw_bytes(&array);
@ -453,7 +453,7 @@ where
match C::decode_bin(array.as_slice()) { match C::decode_bin(array.as_slice()) {
Ok(val) => { Ok(val) => {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let prev = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let prev = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_message(&val); on_message(&val);
@ -489,7 +489,7 @@ where
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let zone = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_error(UseWebSocketError::Event(e)); on_error(UseWebSocketError::Event(e));
@ -518,7 +518,7 @@ where
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let zone = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
on_close(e); on_close(e);
@ -532,7 +532,7 @@ where
onclose_closure.forget(); onclose_closure.forget();
} }
ws_ref.set_value(Some(web_socket)); ws_signal.set(Some(web_socket));
})) }))
}); });
} }
@ -541,7 +541,7 @@ where
let send_str = { let send_str = {
Box::new(move |data: &str| { Box::new(move |data: &str| {
if ready_state.get_untracked() == ConnectionReadyState::Open { if ready_state.get_untracked() == ConnectionReadyState::Open {
if let Some(web_socket) = ws_ref.get_value() { if let Some(web_socket) = ws_signal.get_untracked() {
let _ = web_socket.send_with_str(data); let _ = web_socket.send_with_str(data);
} }
} }
@ -551,7 +551,7 @@ where
// Send bytes // Send bytes
let send_bytes = move |data: &[u8]| { let send_bytes = move |data: &[u8]| {
if ready_state.get_untracked() == ConnectionReadyState::Open { if ready_state.get_untracked() == ConnectionReadyState::Open {
if let Some(web_socket) = ws_ref.get_value() { if let Some(web_socket) = ws_signal.get_untracked() {
let _ = web_socket.send_with_u8_array(data); let _ = web_socket.send_with_u8_array(data);
} }
} }
@ -589,7 +589,7 @@ where
move || { move || {
manually_closed_ref.set_value(true); manually_closed_ref.set_value(true);
if let Some(web_socket) = ws_ref.get_value() { if let Some(web_socket) = ws_signal.get_untracked() {
let _ = web_socket.close(); let _ = web_socket.close();
} }
} }
@ -611,7 +611,7 @@ where
UseWebSocketReturn { UseWebSocketReturn {
ready_state: ready_state.into(), ready_state: ready_state.into(),
message: message.into(), message: message.into(),
ws: ws_ref.get_value(), ws: ws_signal.into(),
open, open,
close, close,
send, send,
@ -716,7 +716,7 @@ where
/// Latest message received from `WebSocket`. /// Latest message received from `WebSocket`.
pub message: Signal<Option<Rx>>, pub message: Signal<Option<Rx>>,
/// The `WebSocket` instance. /// The `WebSocket` instance.
pub ws: Option<WebSocket>, pub ws: Signal<Option<WebSocket>, LocalStorage>,
/// Opens the `WebSocket` connection /// Opens the `WebSocket` connection
pub open: OpenFn, pub open: OpenFn,
/// Closes the `WebSocket` connection /// Closes the `WebSocket` connection

View file

@ -52,7 +52,7 @@ where
let last_return_val = Arc::clone(&last_return_value); let last_return_val = Arc::clone(&last_return_value);
let invoke = move || { let invoke = move || {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let zone = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
let return_value = _invoke(); let return_value = _invoke();

View file

@ -58,7 +58,7 @@ where
let last_return_val = Arc::clone(&last_return_value); let last_return_val = Arc::clone(&last_return_value);
let invoke = move || { let invoke = move || {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let zone = leptos::reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
let return_value = _invoke(); let return_value = _invoke();

View file

@ -1,4 +1,4 @@
use leptos::reactive_graph::wrappers::read::Signal; use leptos::reactive::wrappers::read::Signal;
/// Pausable effect /// Pausable effect
pub struct Pausable<PauseFn, ResumeFn> pub struct Pausable<PauseFn, ResumeFn>

Some files were not shown because too many files have changed in this diff Show more