Declaratively create and pair keybindings with callbacks for Leptos applications.
Note
This library is ready for use. If you're curious about updates read the CHANGELOG.
Curious to see how it works? See the demo and its source code!
To get started, follow the Quick Start section. It's worth the read!
For simplicity and ease, use the use_hotkeys!
macro to declare global and scoped hotkeys. We brought some js idioms while maintaining the leptos look. Learn more about the macro.
If you prefer writing out your callbacks the leptos way, we also have non-macro hotkeys. Learn more about trad hotkeys.
This example creates two global hotkeys: W
and S
.
Tip
For more information about how to write your keybindings, check out Key Grammar.
Note
The *
symbol is reserved for the global scope_
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 S key!
use_hotkeys!(("keys", "*") => move |_| {
logging::log!("s has been pressed");
set_count.update(|c| *c -= 1);
});
view! {
<p>Current count: {count}</p>
}
}
This example shows an inner and outer scope and hotkeys that switch between the scopes.
Tip
Assign hotkeys specific to individual sections without collisions using scopes. Use functions in HotkeysContext
for scope management. For more information about how to write your keybindings, check out Key Grammar.
Note
Scopes are case-insensitive. That means my_scope
and mY_sCoPe
are considered the same scope.
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("outer");
enable_scope("inner");
});
// switch into the outer scope
use_hotkeys!(("keyo", "inner") => move |_| {
disable_scope("inner");
enable_scope("outer");
});
view! {
<div id="outer">
//...some outer scope html...
<div id="inner">
//...some inner scope html...
</div>
//...some outer scope html....
</div>
}
}
Tip
Embed a hotkey with an html element and the hotkey will only fire if the element is focused and the scope is enabled.
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>
}
}
cargo add leptos_hotkeys
Note
leptos-hotkeys
supports both client-side rendered and server-side rendered applications.
For client side rendered:
leptos_hotkeys = "0.2.0"
For server side rendered:
leptos_hotkeys = { version = "0.2.0", features = ["ssr"] }
For client side and server side rendered:
leptos_hotkeys = "0.2.0"
[features]
ssr = ["leptos_hotkeys/ssr"]
We also offer other feature flags that enhance developer experience, see features.
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
.
Note
provide_hotkeys_context()
returns a HotkeysContext
. See HotkeysContext.
use leptos_hotkeys::{provide_hotkeys_context, scopes};
#[component]
pub fn App() -> impl IntoView {
provide_meta_context();
let main_ref = create_node_ref::<html::Main>();
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>
}
}
If you're using scopes, you can initialize with a specific scope.
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!("some_scope_id"));
view! {
<Router>
<main _ref=main_ref>
<Routes>
// ... routes
</Routes>
</main>
</Router>
}
}
leptos_hotkeys
matches key values from KeyboardEvent's key
property. For reference, here's a list of all key values for keyboard events.
You can bind multiple hotkeys to a callback. For example:
"G+R,meta+O,control+k"
The above example creates three hotkeys: G+R, Meta+O, and Ctrl+K. The +
symbol is used to create a combo hotkey. A combo hotkey is a keybinding requiring more than one key press.
Note
Keys are case-agnostic and whitespace-agnostic. You use the ,
as a delimiter in a sequence of multiple hotkeys.
We wanted to strip the verbosity that comes with str
and String
type handling. We kept leptos best practices in mind, keeping the move |_|
idiom in our macro.
Here is a general look at the macro:
use leptos_hotkeys::use_hotkeys;
use_hotkeys!(("keys", "scope") => move |_| {
// callback logic here
});
For global hotkeys, you can omit the second parameter as it will implicitly add the global scope.
use_hotkeys!(("keys") => move |_| {
// callback logic here
});
This macro is used when you want to focus trap with a specific html element.
use leptos_hotkeys::use_hotkeys_ref;
#[component]
pub fn SomeComponent() -> impl IntoView {
let some_ref = use_hotkeys_ref!(("keys", "scope") => move |_| {
// callback logic here
});
view! {
<div tabIndex=-1 _ref=some_ref>
</div>
}
}
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>
}
}
We want to improve developer experience by introducing the debug
flag which adds logging to your console in CSR. It logs the current pressed key values, hotkeys fires, and scopes toggling.
Just simply:
leptos_hotkeys = { path = "0.2.0", features = ["debug"] }
Field Name | Type | Description |
---|---|---|
pressed_keys |
RwSignal<HashSet<String>> |
A reactive signal tracking the set of keys currently pressed by the user. |
active_ref_target |
RwSignal<Option<EventTarget>> |
A reactive signal holding the currently active event target, useful for focusing events. |
set_ref_target |
Callback<Option<EventTarget>> |
A method to update the currently active event target. |
active_scopes |
RwSignal<HashSet<String>> |
A reactive signal tracking the set of currently active scopes, allowing for scoped hotkey management. |
enable_scope |
Callback<String> |
A method to activate a given hotkey scope. |
disable_scope |
Callback<String> |
A method to deactivate a given hotkey scope. |
toggle_scope |
Callback<String> |
A method to toggle the activation state of a given hotkey scope. |
Field Name | Type | Description |
---|---|---|
alt |
bool |
Indicates if the Alt key modifier is active (true) or not (false). |
ctrl |
bool |
Indicates if the Control (Ctrl) key modifier is active (true) or not (false). |
meta |
bool |
Indicates if the Meta (Command on macOS, Windows key on Windows) key modifier is active (true) or not (false). |
shift |
bool |
Indicates if the Shift key modifier is active (true) or not (false). |
Field Name | Type | Description |
---|---|---|
modifiers |
KeyboardModifiers |
The set of key modifiers (Alt, Ctrl, Meta, Shift) associated with the hotkey. |
keys |
Vec<String> |
The list of keys that, along with any modifiers, define the hotkey. |
description |
String |
A human-readable description of what the hotkey does. Intended for future use with scopes. |
If the macro isn't to your liking, we offer three hotkeys: global, scoped, and focus trapped.
use leptos_hotkeys::{use_hotkeys_scoped};
#[component]
fn Component() -> impl IntoView {
let (count, set_count) = create_signal(0);
use_hotkeys_scoped(
"keyf", // the F key
Callback::new(move |_| {
set_count.update(|count| { *count += 1 })
}),
vec!["*"]
);
view! {
<p>
Press 'F' to pay respect.
{count} times
</p>
}
}
use leptos_hotkeys::{
use_hotkeys_scoped, use_hotkeys_context, HotkeysContext
};
#[component]
fn Component() -> impl IntoView {
let hotkeys_context: HotkeysContext = use_hotkeys_context();
let toggle = hotkeys_context.toggle_scope;
let enable = hotkeys_context.enable_scope;
let disable = hotkeys_context.disable_scope;
use_hotkeys_scoped(
"arrowup",
Callback::new(move |_| {
// move character up
}),
vec!["game_scope"]
);
use_hotkeys_scoped(
"arrowdown",
Callback::new(move |_| {
// move character down
}),
vec!["game_scope"]
);
view! {
<button
// activates the 'game_scope' scope
on:click=move |_| enable("game_scope")
>
Start game
</button>
<button
// toggles the 'game_scope' from enabled to disabled
on:click=move |_| toggle("game_scope")
>
Pause game
</button>
<button
// disables the 'game_scope' scope
on:click=move |_| disable("game_scope")
>
End game
</button>
}
}
use leptos_hotkeys::use_hotkeys_ref;
#[component]
fn Component() -> impl IntoView {
let node_ref = use_hotkeys_ref("keyl", Callback::new(move |_| {
// some logic here
}));
view! {
<body>
<div _ref=node_ref>
// when this div is focused, the "l" hotkey will fire
</div>
</body>
}
}
Álvaro Mondéjar 💻 |
Robert Junkins 💻 |
LeoniePhiline 📖 |
Gábor Szabó 📖 |
Phillip Baird 🐛 |