Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Link Key bindings to buttons #641

Open
JovanVeljanoski opened this issue May 9, 2024 · 6 comments
Open

Feature Request: Link Key bindings to buttons #641

JovanVeljanoski opened this issue May 9, 2024 · 6 comments

Comments

@JovanVeljanoski
Copy link
Collaborator

Would it be possible to make keys / key-bindings / correspond to button clicks.
I.e. one button can be linked to Return, another to arrow-key, etc..

@JovanVeljanoski JovanVeljanoski changed the title Link Key bindings to buttons May 9, 2024
@iisakkirotko
Copy link
Collaborator

Hey Jovan!

You're right, it isn't natively possible to link key presses to interactions with an element. I suppose you could in principle accomplish it via a component_vue to inject some code that does this on the front-end with javascript. You would have to identify the elements you're trying to link to manually, which would most likely be a bother though... In case it helps with your use-case, you can bind keystrokes to functions using the use_change-hook, defined here:

def use_change(el: reacton.core.Element, on_value: Callable[[Any], Any], enabled=True, update_events=["blur", "keyup.enter"]):
"""Trigger a callback when a blur events occurs or the enter key is pressed."""
on_value_ref = solara.use_ref(on_value)
on_value_ref.current = on_value
def add_events():
def on_change(widget, event, data):
if enabled:
on_value_ref.current(widget.v_model)
widget = cast(ipyvue.VueWidget, solara.get_widget(el))
if enabled:
for event in update_events:
widget.on_event(event, on_change)
def cleanup():
if enabled:
for event in update_events:
widget.on_event(event, on_change, remove=True)
return cleanup
solara.use_effect(add_events, [enabled])

You can see how we use it (sorry to quote chat.py to you twice in a day :) ) to hook up events to ChatInput here:

use_change(message_input, send, update_events=["keyup.enter"])
button = solara.v.Btn(color="primary", icon=True, children=[solara.v.Icon(children=["mdi-send"])], disabled=message == "")
use_change(button, send, update_events=["click"])

@maartenbreddels
Copy link
Contributor

What about putting something like this in solara.lab?

from typing import Callable
import solara

show_dialog = solara.reactive(False)


@solara.component_vue("hotkey.vue")
def HotKey(
    key: str,
    ctrl: bool = False,
    shift: bool = False,
    meta: bool = False,
    prevent_default: bool = False,
    event_pressed: Callable[[], None] = None,
): ...


@solara.component
def Page():
    with solara.Column() as main:
        solara.InputText("No focus")
        solara.InputText("No focus")
        solara.Button("Show dialog", on_click=lambda: show_dialog.set(True))

        with solara.v.Dialog(
            v_model=show_dialog.value,
            on_v_model=show_dialog.set,
            persistent=True,
            max_width="500px",
        ):
            with solara.v.Sheet(class_="pa-4") as sheet:
                solara.InputText("No focus")
                solara.InputText("No focus")
                close = solara.Button("Close", on_click=lambda: show_dialog.set(False))

        def close_dialog_on_q(widget, event, data):
            print("event", event)
            # if event.key == "Escape":
            show_dialog.set(False)

        HotKey(key="q", ctrl=True, event_pressed=lambda ignore: show_dialog.set(False))
        HotKey(key="o", event_pressed=lambda ignore: show_dialog.set(True))

// hotkey.vue

<template>
    <span style="display: none;">key {{key}}</span>
</template>
<script>
module.exports = {
    mounted() {
        console.log("mounted", this.key)
        document.addEventListener('keydown', this.hotkeyPress);
    },
    destroyed() {
        document.removeEventListener('keydown', this.hotkeyPress);
    },
    methods: {
        hotkeyPress: function(e) {
            if (e.key === this.key) {
                console.log("key", this.key, e.key, e)
                if(this.metaKey && !e.metaKey) {
                    return
                }
                if(this.ctrl && !e.ctrlKey) {
                    return
                }
                if(this.shift && !e.shiftKey) {
                    return
                }
                if(this.alt && !e.altKey) {
                    return
                }
                if(this.meta && !e.metaKey) {
                    return
                }
                if(this. prevent_default) {
                    e.preventDefault()
                }
                console.log("pressed", this.key)
                this.pressed()
            }
        }
    }
};
</script>

@JovanVeljanoski you can use this now in your app, and see if a pattern like this works for you, we can iterate on it and see if we want it in lab based on your feedback.

@JovanVeljanoski
Copy link
Collaborator Author

Hi,

Thanks for these examples!
@iisakkirotko - with a simple example (actually trying to modify the example provided by @maartenbreddels ), i could not get the use_chage to work. I understand what should be happening but I am doing something wrong perhaps..

@maartenbreddels that is a nice idea indeed. Works well, and if it was already in solara.lab it would be even better.
One comment I would add -> an adjustment so one can use the enter/return key (without any other key, i guess like in the chat component) would be great!

Otherwise I like this approach!

@JovanVeljanoski
Copy link
Collaborator Author

Some other small feedback (can be ignored ofc)
In the HotKey function above, it would be nice if the event_pressed has the same behaviour as on_click from a button component for consistency.

@maartenbreddels
Copy link
Contributor

Made some minor improvements, and made the event handler on_pressed take no arguments (assuming you meant that):
https://py.cafe/maartenbreddels/solara-hotkeys
It also shows you can use the escape or enter keycode.

@JovanVeljanoski
Copy link
Collaborator Author

Great! This works very nice!
Although, at least for me, only the changes in hotkey.vue where needed, the rest work very well with the python code from earlier.

Thanks - feel free to close this. Would be great if something like this is one day in solara(.lab).

Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants