Awesome
leptos-hotkeys
<!-- markdownlint-disable --> <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- markdownlint-restore -->Declaratively create and pair keybindings with callbacks for Leptos applications.
<!-- markdownlint-disable --> <a href="https://github.com/gaucho-labs/leptos-hotkeys"> <img width="570" height="211" alt="A person playing a burning piano at a sandy beach under a cloudy sky" src="https://raw.githubusercontent.com/gaucho-labs/leptos-hotkeys/main/images/readme-cover.png" /> </a> <!-- markdownlint-restore -->Leptos-hotkeys creates and manages keyboard shortcuts. It provides macros and functions that simplify the definition of keybindings, since the management of event lifecycle associated with keyboard interactions has been done for you!
Live example
Curious to see how it works? See the demo.
To get started, follow the Quick Start section.
Features
[!NOTE] This crate has three types of hotkeys: global, scoped, and focus-trapped.
The use_hotkeys!
Macro
Use this macro to declare global and scoped hotkeys. This macro has js idioms while preserving Leptos standards. More about the macro.
Global Hotkeys
This example creates two global hotkeys: W
and F
.
use leptos_hotkeys::use_hotkeys;
#[component]
pub fn SomeComponent() -> impl IntoView {
let (count, set_count) = create_signal(0);
// creating a global scope for the W key
use_hotkeys!(("keyw") => move |_| {
logging::log!("w has been pressed");
set_count.update(|c| *c += 1);
});
// this is also a global scope for the F key!
use_hotkeys!(("keyf", "*") => move |_| {
logging::log!("f has been pressed");
set_count.update(|c| *c -= 1);
});
view! { <p>Num Respects: {count}</p> }
}
[!TIP] How do I write certain keys? See Key Grammar.
[!NOTE] The
*
symbol is reserved for the global scope_.The
W
hotkey omitted the scope parameter, implicitly making it global.
Scoped Hotkeys
Scopes provide context behind hotkeys. This context can be chained to a component, a state, or logic.
This example shows an inner and outer scope and hotkeys that toggle scopes.
use leptos_hotkeys::{use_hotkeys, use_hotkeys_context, HotkeysContext};
#[component]
pub fn SomeComponent() -> impl IntoView {
let HotkeysContext { enable_scope, disable_scope, .. } = use_hotkeys_context();
// switch into the inner scope
use_hotkeys!(("keyi", "outer") => move |_| {
disable_scope.call("outer".to_string());
enable_scope.call("inner".to_string());
});
// switch into the outer scope
use_hotkeys!(("keyo", "inner") => move |_| {
disable_scope.call("inner".to_string());
enable_scope.call("outer".to_string());
});
view! {
<div id="outer">
// outer logic residing...
<div id="inner">
// inner logic
</div>
</div>
}
}
[!NOTE] Scopes are case-insensitive. That means
my_scope
andmY_sCoPe
are considered the same scope.
Focus trapped Hotkeys (the use_hotkeys_ref!
macro)
This example embeds a hotkey to a <p>
tag. This hotkey will fire iff the element is focused and the scope is correct.
use leptos_hotkeys::use_hotkeys_ref;
#[component]
pub fn SomeComponent() -> impl IntoView {
let p_ref = use_hotkeys_ref!(("keyk", "*") => move |_| {
// some logic
});
view! {
<p tabIndex=-1 _ref=p_ref>
p tag with node ref
</p>
}
}
Quick Start
Installation
cargo add leptos_hotkeys
We also offer other feature flags that enhance developer experience, see features.
provide_hotkeys_context()
Call provide_hotkeys_context()
in the App()
component. This will provide the HotkeysContext
for the current reactive node and all of its descendents. This function takes three parameters, the node_ref
, a flag to disable blur events and a list of initially_active_scopes
.
provide_hotkeys_context()
returns a HotkeyContext
. To manage hotkeys, you can pull necessary signals out of HotkeysContext
.
use leptos_hotkeys::{provide_hotkeys_context, HotkeysContext, scopes};
#[component]
pub fn App() -> impl IntoView {
provide_meta_context();
let main_ref = create_node_ref::<html::Main>();
let HotkeysContext { .. } = provide_hotkeys_context(main_ref, false, scopes!());
view! {
<Router>
<main _ref=main_ref> // <-- attach main ref here!
<Routes>
<Route path="/" view=HomePage/>
<Route path="/:else" view=ErrorPage/>
</Routes>
</main>
</Router>
}
}
[!NOTE] If you're using scopes, you can initialize with a specific scope.
That's it! You can create global, scoped, and focus-trapped hotkeys!
Keybinding Grammar
leptos_hotkeys
matches code values from KeyboardEvent's code
property. For reference, here's a list of all code values for keyboard events.
You can bind multiple hotkeys to a callback. For example:
"KeyG+KeyR,MetaLeft+KeyO,ControlLeft+keyK"
Keys are case-agnostic and whitspace-agnostic. For a hotkey with multiple keys, use the ,
as a delimiter in a sequence of keys.
scopes!()
Maybe you want to initialize a certain scope upon load, that's where the prop initially_active_scopes
comes into play.
Instead of having to create a vec!["scope_name".to_string()]
, use the scopes!()
macro.
use leptos_hotkeys::{provide_hotkeys_context, scopes};
#[component]
pub fn App() -> impl IntoView {
let main_ref = create_node_ref::<html::Main>();
provide_hotkeys_context(main_ref, false, scopes!("scope_a", "settings_scope"));
view! {
<Router>
<main _ref=main_ref>
<Routes>
// ... routes
</Routes>
</main>
</Router>
}
}
The debug
feature flag
Improve developer experience by introducing the debug
flag which adds logging to your console in CSR. It logs the current pressed key code
values, hotkeys fires, and scopes toggling.
Just simply:
leptos_hotkeys = { path = "0.2.1", features = ["debug"] }