mirror of
https://github.com/adoyle0/leptos-use.git
synced 2025-02-02 10:54:15 -05:00
added use_event_listener
This commit is contained in:
parent
a683430dbc
commit
b72acc8f65
11 changed files with 424 additions and 16 deletions
70
.github/workflows/ci.yml
vendored
Normal file
70
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# Pattern matched against refs/tags
|
||||||
|
tags:
|
||||||
|
- '*' # Push events to every tag not containing /
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
name: Publish
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
components: rustfmt
|
||||||
|
- name: Cache
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Check formatting
|
||||||
|
run: cargo fmt --check
|
||||||
|
# - name: Check if the README is up to date.
|
||||||
|
# run: |
|
||||||
|
# cargo install cargo-rdme
|
||||||
|
# cargo rdme --check
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --all-features
|
||||||
|
- name: Publish crate leptos-use
|
||||||
|
uses: katyo/publish-crates@v2
|
||||||
|
with:
|
||||||
|
registry-token: ${{ secrets.CRATES_TOKEN }}
|
||||||
|
|
||||||
|
# coverage:
|
||||||
|
# name: Coverage
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
#
|
||||||
|
# steps:
|
||||||
|
# - name: Checkout sources
|
||||||
|
# uses: actions/checkout@v2
|
||||||
|
#
|
||||||
|
# - name: Install rust
|
||||||
|
# uses: actions-rs/toolchain@v1
|
||||||
|
# with:
|
||||||
|
# toolchain: stable
|
||||||
|
# profile: minimal
|
||||||
|
# override: true
|
||||||
|
#
|
||||||
|
# - name: Cache
|
||||||
|
# uses: Swatinem/rust-cache@v1
|
||||||
|
#
|
||||||
|
# - name: Install cargo-tarpaulin
|
||||||
|
# uses: actions-rs/cargo@v1
|
||||||
|
# with:
|
||||||
|
# command: install
|
||||||
|
# args: cargo-tarpaulin
|
||||||
|
#
|
||||||
|
# - name: Run cargo tarpaulin
|
||||||
|
# uses: actions-rs/cargo@v1
|
||||||
|
# with:
|
||||||
|
# command: tarpaulin
|
||||||
|
# args: --output-dir coverage --out Lcov
|
||||||
|
#
|
||||||
|
# - name: Publish to Coveralls
|
||||||
|
# uses: coverallsapp/github-action@master
|
||||||
|
# with:
|
||||||
|
# github-token: ${{ secrets.GITHUB_TOKEN }}
|
3
.idea/leptos-use.iml
generated
3
.idea/leptos-use.iml
generated
|
@ -2,7 +2,10 @@
|
||||||
<module type="CPP_MODULE" version="4">
|
<module type="CPP_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/examples/use_event_listener/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/examples/use_event_listener/target" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "leptos-use"
|
name = "leptos-use"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Marc-Stefan Cassola"]
|
authors = ["Marc-Stefan Cassola"]
|
||||||
categories = ["gui", "web-programming"]
|
categories = ["gui", "web-programming"]
|
||||||
|
@ -8,9 +8,11 @@ description = "Collection of essential Leptos utilities inspired by SolidJS USE
|
||||||
exclude = ["examples/", "tests/"]
|
exclude = ["examples/", "tests/"]
|
||||||
keywords = ["leptos", "utilities"]
|
keywords = ["leptos", "utilities"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
# readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/Synphonyte/leptos-use"
|
repository = "https://github.com/Synphonyte/leptos-use"
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
leptos = "0.2"
|
leptos = "0.2"
|
||||||
|
web-sys = "0.3"
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
|
10
README.md
Normal file
10
README.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Leptos-Use
|
||||||
|
|
||||||
|
[![Crates.io](https://img.shields.io/crates/v/leptos-use.svg)](https://crates.io/crates/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)
|
||||||
|
[![Build Status](https://github.com/synphonyte/leptos-use/actions/workflows/ci.yml/badge.svg)](https://github.com/synphonyte/leptos-use/actions/workflows/ci.yml)
|
||||||
|
|
||||||
|
Collection of essential Leptos utilities inspired by SolidJS-USE / VueUse
|
||||||
|
|
||||||
|
This is still very much work in progress and all contributions are welcome!
|
16
examples/use_event_listener/Cargo.toml
Normal file
16
examples/use_event_listener/Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "use_event_listener"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
leptos = "0.2"
|
||||||
|
console_error_panic_hook = "0.1"
|
||||||
|
console_log = "1"
|
||||||
|
log = "0.4"
|
||||||
|
leptos-use = { path = "../.." }
|
||||||
|
web-sys = "0.3"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
wasm-bindgen-test = "0.3.0"
|
16
examples/use_event_listener/README.md
Normal file
16
examples/use_event_listener/README.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
A simple example for `use_event_listener`.
|
||||||
|
|
||||||
|
If you don't have it installed already, install [Trunk](https://trunkrs.dev/)
|
||||||
|
as well as the nightly toolchain for Rust and the wasm32-unknown-unknown target:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install trunk
|
||||||
|
rustup toolchain install nightly
|
||||||
|
rustup target add wasm32-unknown-unknown
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, to run this example, execute in a terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
trunk serve --open
|
||||||
|
```
|
5
examples/use_event_listener/index.html
Normal file
5
examples/use_event_listener/index.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body></body>
|
||||||
|
</html>
|
2
examples/use_event_listener/rust-toolchain.toml
Normal file
2
examples/use_event_listener/rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
48
examples/use_event_listener/src/main.rs
Normal file
48
examples/use_event_listener/src/main.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use leptos::ev::click;
|
||||||
|
use leptos::*;
|
||||||
|
use leptos_use::use_event_listener_ref;
|
||||||
|
use web_sys::HtmlDivElement;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn Demo(cx: Scope) -> impl IntoView {
|
||||||
|
let element = create_node_ref(cx);
|
||||||
|
|
||||||
|
let _ = use_event_listener_ref(cx, element, click, |evt| {
|
||||||
|
log!(
|
||||||
|
"click from element {:?}",
|
||||||
|
event_target::<HtmlDivElement>(&evt)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let (cond, set_cond) = create_signal(cx, true);
|
||||||
|
|
||||||
|
view! { cx,
|
||||||
|
<p>"Check in the dev tools console"</p>
|
||||||
|
<p>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox" on:change=move |evt| set_cond(event_target_checked(&evt))
|
||||||
|
prop:checked=cond
|
||||||
|
/>
|
||||||
|
"Condition enabled"
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
<Show
|
||||||
|
when=move || cond()
|
||||||
|
fallback=move |cx| view! { cx, <div node_ref=element>"Condition false [click me]"</div> }
|
||||||
|
>
|
||||||
|
<div node_ref=element>"Condition true [click me]"</div>
|
||||||
|
</Show>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
_ = console_log::init_with_level(log::Level::Debug);
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
|
mount_to_body(|cx| {
|
||||||
|
view! {cx,
|
||||||
|
<Demo />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
15
src/lib.rs
15
src/lib.rs
|
@ -1,14 +1,3 @@
|
||||||
pub fn add(left: usize, right: usize) -> usize {
|
mod use_event_listener;
|
||||||
left + right
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
pub use use_event_listener::*;
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
let result = add(2, 2);
|
|
||||||
assert_eq!(result, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
247
src/use_event_listener.rs
Normal file
247
src/use_event_listener.rs
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
use leptos::ev::EventDescriptor;
|
||||||
|
use leptos::html::ElementDescriptor;
|
||||||
|
use leptos::*;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use wasm_bindgen::closure::Closure;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
|
/// Use EventListener with ease. Register using [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) on mounted,
|
||||||
|
/// and [removeEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener) automatically on cleanup.
|
||||||
|
///
|
||||||
|
/// ## Usage
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use leptos::*;
|
||||||
|
/// use leptos::ev::visibilitychange;
|
||||||
|
/// use leptos_use::use_event_listener;
|
||||||
|
///
|
||||||
|
/// #[component]
|
||||||
|
/// fn Demo(cx: Scope) -> impl IntoView {
|
||||||
|
/// use_event_listener(cx, Some(document()), visibilitychange, |evt| {
|
||||||
|
/// log!("{:?}", evt);
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// view! { cx, }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You can also pass a [NodeRef](leptos::NodeRef) as the event target, `use_event_listener` will unregister the previous event and register
|
||||||
|
/// the new one when you change the target. (For now you have to use `use_event_listener_ref` to do that)
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use leptos::*;
|
||||||
|
/// use leptos::ev::click;
|
||||||
|
/// use leptos_use::use_event_listener_ref;
|
||||||
|
///
|
||||||
|
/// #[component]
|
||||||
|
/// fn Demo(cx: Scope) -> impl IntoView {
|
||||||
|
/// let element = create_node_ref(cx);
|
||||||
|
///
|
||||||
|
/// use_event_listener_ref(cx, element, click, |evt| {
|
||||||
|
/// log!("click from element {:?}", event_target::<web_sys::HtmlDivElement>(&evt));
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// let (cond, set_cond) = create_signal(cx, true);
|
||||||
|
///
|
||||||
|
/// view! { cx,
|
||||||
|
/// <Show
|
||||||
|
/// when=move || cond()
|
||||||
|
/// fallback=move |cx| view! { cx, <div node_ref=element>"Condition false"</div> }
|
||||||
|
/// >
|
||||||
|
/// <div node_ref=element>"Condition true"</div>
|
||||||
|
/// </Show>
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You can also call the returned to unregister the listener.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use leptos::*;
|
||||||
|
/// # use leptos::ev::keydown;
|
||||||
|
/// # use web_sys::KeyboardEvent;
|
||||||
|
/// # use leptos_use::use_event_listener;
|
||||||
|
/// #
|
||||||
|
/// # #[component]
|
||||||
|
/// # fn Demo(cx: Scope) -> impl IntoView {
|
||||||
|
/// let cleanup = use_event_listener(cx, Some(document()), keydown, |evt: KeyboardEvent| {
|
||||||
|
/// log!("{}", &evt.key());
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// cleanup();
|
||||||
|
/// #
|
||||||
|
/// # view! { cx, }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note if your components also run in SSR (Server Side Rendering), you might get errors
|
||||||
|
/// because DOM APIs like document and window are not available outside of the browser.
|
||||||
|
/// To avoid that you can put the logic inside a [`create_effect`](leptos::create_effect) hook
|
||||||
|
/// which only runs client side.
|
||||||
|
#[allow(unused_must_use)]
|
||||||
|
pub fn use_event_listener<Ev, El, Inner, F>(
|
||||||
|
cx: Scope,
|
||||||
|
target: El,
|
||||||
|
event: Ev,
|
||||||
|
handler: F,
|
||||||
|
) -> Box<dyn Fn()>
|
||||||
|
where
|
||||||
|
Ev: EventDescriptor + 'static,
|
||||||
|
El: Into<MaybeSignal<Option<Inner>>>,
|
||||||
|
Inner: Into<web_sys::EventTarget> + Clone + 'static,
|
||||||
|
F: FnMut(<Ev as EventDescriptor>::EventType) + 'static,
|
||||||
|
{
|
||||||
|
let event_name = event.name();
|
||||||
|
let closure_js = Closure::wrap(Box::new(handler) as Box<dyn FnMut(_)>).into_js_value();
|
||||||
|
|
||||||
|
let closure = closure_js.clone();
|
||||||
|
let cleanup_fn = move |element: &web_sys::EventTarget| {
|
||||||
|
let _ = element
|
||||||
|
.remove_event_listener_with_callback(&event_name, closure.as_ref().unchecked_ref());
|
||||||
|
};
|
||||||
|
let cleanup = cleanup_fn.clone();
|
||||||
|
|
||||||
|
let event_name = event.name();
|
||||||
|
|
||||||
|
match target.into() {
|
||||||
|
MaybeSignal::Static(element) => {
|
||||||
|
if let Some(element) = element {
|
||||||
|
let element = element.into();
|
||||||
|
_ = element.add_event_listener_with_callback(
|
||||||
|
&event_name,
|
||||||
|
closure_js.as_ref().unchecked_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let cleanup_fn = move || {
|
||||||
|
cleanup(&element);
|
||||||
|
};
|
||||||
|
on_cleanup(cx, cleanup_fn.clone());
|
||||||
|
|
||||||
|
Box::new(cleanup_fn)
|
||||||
|
} else {
|
||||||
|
Box::new(|| {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MaybeSignal::Dynamic(signal) => {
|
||||||
|
let element = signal.get_untracked();
|
||||||
|
|
||||||
|
let cleanup_prev_element = if let Some(element) = element {
|
||||||
|
let element = element.into();
|
||||||
|
|
||||||
|
_ = element.add_event_listener_with_callback(
|
||||||
|
&event_name,
|
||||||
|
closure_js.as_ref().unchecked_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let clean = cleanup.clone();
|
||||||
|
Rc::new(RefCell::new(Box::new(move || {
|
||||||
|
clean(&element);
|
||||||
|
}) as Box<dyn Fn()>))
|
||||||
|
} else {
|
||||||
|
Rc::new(RefCell::new(Box::new(move || {}) as Box<dyn Fn()>))
|
||||||
|
};
|
||||||
|
|
||||||
|
let cleanup_prev_el = Rc::clone(&cleanup_prev_element);
|
||||||
|
let closure = closure_js.clone();
|
||||||
|
create_effect(cx, move |_| {
|
||||||
|
cleanup_prev_el.borrow()();
|
||||||
|
|
||||||
|
let element = signal();
|
||||||
|
|
||||||
|
if let Some(element) = element {
|
||||||
|
let element = element.into();
|
||||||
|
|
||||||
|
_ = element.add_event_listener_with_callback(
|
||||||
|
&event_name,
|
||||||
|
closure.as_ref().unchecked_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let clean = cleanup.clone();
|
||||||
|
cleanup_prev_el.replace(Box::new(move || {
|
||||||
|
clean(&element);
|
||||||
|
}) as Box<dyn Fn()>);
|
||||||
|
} else {
|
||||||
|
cleanup_prev_el.replace(Box::new(move || {}) as Box<dyn Fn()>);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let cleanup_fn = move || cleanup_prev_element.borrow()();
|
||||||
|
on_cleanup(cx, cleanup_fn.clone());
|
||||||
|
|
||||||
|
Box::new(cleanup_fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Version for using with [NodeRef](leptos::NodeRef). See [use_event_listener] for how to use.
|
||||||
|
#[allow(unused_must_use)]
|
||||||
|
pub fn use_event_listener_ref<Ev, El, F>(
|
||||||
|
cx: Scope,
|
||||||
|
target: NodeRef<El>,
|
||||||
|
event: Ev,
|
||||||
|
handler: F,
|
||||||
|
) -> Box<dyn Fn()>
|
||||||
|
where
|
||||||
|
Ev: EventDescriptor + 'static,
|
||||||
|
El: ElementDescriptor + Clone,
|
||||||
|
F: FnMut(<Ev as EventDescriptor>::EventType) + 'static,
|
||||||
|
{
|
||||||
|
let event_name = event.name();
|
||||||
|
let closure_js = Closure::wrap(Box::new(handler) as Box<dyn FnMut(_)>).into_js_value();
|
||||||
|
|
||||||
|
let closure = closure_js.clone();
|
||||||
|
let cleanup_fn = move |element: &web_sys::EventTarget| {
|
||||||
|
let _ = element
|
||||||
|
.remove_event_listener_with_callback(&event_name, closure.as_ref().unchecked_ref());
|
||||||
|
};
|
||||||
|
let cleanup = cleanup_fn.clone();
|
||||||
|
|
||||||
|
let event_name = event.name();
|
||||||
|
|
||||||
|
let element = target.get();
|
||||||
|
|
||||||
|
let cleanup_prev_element = if let Some(element) = element {
|
||||||
|
let element = element.into_any();
|
||||||
|
let element: web_sys::EventTarget = element.deref().clone().into();
|
||||||
|
|
||||||
|
_ = element
|
||||||
|
.add_event_listener_with_callback(&event_name, closure_js.as_ref().unchecked_ref());
|
||||||
|
|
||||||
|
let clean = cleanup.clone();
|
||||||
|
Rc::new(RefCell::new(Box::new(move || {
|
||||||
|
clean(&element);
|
||||||
|
}) as Box<dyn Fn()>))
|
||||||
|
} else {
|
||||||
|
Rc::new(RefCell::new(Box::new(move || {}) as Box<dyn Fn()>))
|
||||||
|
};
|
||||||
|
|
||||||
|
let cleanup_prev_el = Rc::clone(&cleanup_prev_element);
|
||||||
|
let closure = closure_js.clone();
|
||||||
|
create_effect(cx, move |_| {
|
||||||
|
cleanup_prev_el.borrow()();
|
||||||
|
|
||||||
|
let element = target.get();
|
||||||
|
|
||||||
|
if let Some(element) = element {
|
||||||
|
let element = element.into_any();
|
||||||
|
let element: web_sys::EventTarget = element.deref().clone().into();
|
||||||
|
|
||||||
|
_ = element
|
||||||
|
.add_event_listener_with_callback(&event_name, closure.as_ref().unchecked_ref());
|
||||||
|
|
||||||
|
let clean = cleanup.clone();
|
||||||
|
cleanup_prev_el.replace(Box::new(move || {
|
||||||
|
clean(&element);
|
||||||
|
}) as Box<dyn Fn()>);
|
||||||
|
} else {
|
||||||
|
cleanup_prev_el.replace(Box::new(move || {}) as Box<dyn Fn()>);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let cleanup_fn = move || cleanup_prev_element.borrow()();
|
||||||
|
on_cleanup(cx, cleanup_fn.clone());
|
||||||
|
|
||||||
|
Box::new(cleanup_fn)
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue