mirror of
https://github.com/adoyle0/leptos-use.git
synced 2025-01-22 16:49:22 -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">
|
||||
<component name="NewModuleRootManager">
|
||||
<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" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/examples/use_event_listener/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "leptos-use"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
authors = ["Marc-Stefan Cassola"]
|
||||
categories = ["gui", "web-programming"]
|
||||
|
@ -8,9 +8,11 @@ description = "Collection of essential Leptos utilities inspired by SolidJS USE
|
|||
exclude = ["examples/", "tests/"]
|
||||
keywords = ["leptos", "utilities"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
# readme = "README.md"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Synphonyte/leptos-use"
|
||||
|
||||
|
||||
[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 {
|
||||
left + right
|
||||
}
|
||||
mod use_event_listener;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
pub use use_event_listener::*;
|
||||
|
|
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