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

Support programatically showing the hotkeys help dialog #1590

Closed
Hexxeh opened this issue Sep 21, 2017 · 21 comments · Fixed by #4532
Closed

Support programatically showing the hotkeys help dialog #1590

Hexxeh opened this issue Sep 21, 2017 · 21 comments · Fixed by #4532

Comments

@Hexxeh
Copy link

Hexxeh commented Sep 21, 2017

The Shift-/ combo is not particularly discoverable. It'd be nice if we could include some UI menu option that pops the hotkeys dialog to the user so they can learn the shortcuts. I can't see a way to do this though unfortunately within Blueprint as is currently?

@llorca
Copy link
Contributor

llorca commented Sep 21, 2017

There's a helper function that's exported: http://github.com/palantir/blueprint/blob/874b757e729576a330ee3a8b67a382c7ff4a312e/packages/core/src/components/hotkeys/hotkeysDialog.tsx#L149

Granted, we should document this better

@Hexxeh
Copy link
Author

Hexxeh commented Sep 21, 2017 via email

@giladgray
Copy link
Contributor

giladgray commented Sep 27, 2017

hmm yeah looks like this is not easily possible 😢. that method @llorca linked is prohibitive to use as you'd need to somehow get the list of global hotkeys yourself, but managing that list is why we built the component in the first place!

@tnrich
Copy link
Contributor

tnrich commented Nov 20, 2017

I'm also looking for this functionality. Any chance that we can trigger a fake "shift + ?" event on the document.body and trigger it that way as a stopgap?

@chiubaka
Copy link

chiubaka commented Dec 17, 2017

Just wanted to drop by and say I'm also looking for this. Also spent a bit of time trying to figure out how to simulate the keyboard event explicitly. Not entirely sure if this is possible, as I noticed there's an isTrusted property that identifies events created by scripts rather than by the user. In case anyone else has more time to play with this, in ES6 a KeyboardEvent with approximately the right properties can be created and then triggered like so:

    const event = new KeyboardEvent("keyup", {
      key: "?",
      code: "Slash",
      shiftKey: true,
      bubbles: true,
      cancelable: true,
      // keyCode: 191,
      // which: 191,
    });
    document.dispatchEvent(event);

Can't set the keyCode or which properties through the ES6 typings since these are deprecated properties, but I'm doubtful that that's the issue. If I had to guess beyond the isTrusted event property, the reason this isn't working is that the target of user-generated key events is div.pt-overlay-backdrop, while mine is currently document. The div.pt-overlay-backdrop element seems only to show up when the overlay itself is actually shown, however, so not quite sure how to grab it programmatically to dispatch the event on it.

@adidahiya
Copy link
Contributor

@chiubaka (disclaimer: I haven't tried this myself) you might have success with something like our dispatchTestKeyboardEvent helper:

export function dispatchTestKeyboardEvent(target: EventTarget, eventType: string, key: string, shift = false) {

@michael-yx-wu
Copy link
Contributor

Is it possible to programmatically disable the shift+/ listener all together? If you were to build something like a text editor where most of the user's time is spent on the text box, you probably want to expose the shortcut dialog primarily through a button of some sort and let the user type ? in peace

@tnrich
Copy link
Contributor

tnrich commented Apr 5, 2018

Anyone have any updates on this one? Thanks!

@giladgray
Copy link
Contributor

@tnrich not a drop. would you like to submit some updates? 😄

@tnrich
Copy link
Contributor

tnrich commented Apr 5, 2018 via email

@giladgray
Copy link
Contributor

giladgray commented Apr 9, 2018

@tnrich got some time to put a PR together? happy to review.

@warpdesign
Copy link
Contributor

warpdesign commented Jan 9, 2019

If anyone is interested, you can open the dialog by generating a fake keyboard with code like this:

document.dispatchEvent(new KeyboardEvent('keydown', { which: 47, keyCode: 47, shiftKey: true, bubbles: true }))

Note that it will show every shortcuts. To show the shortcuts for a specific element (as if a specific element had focus), replace document by any element.

It's not ideal, but it may help.

@adidahiya
Copy link
Contributor

Ideally, we could have an API similar to ContextMenu's imperative API...

import { HotkeysDialogSingleton } from "@blueprintjs/core";
HotkeysDialogSingleton.show();

however this is tricky because of the state managed through HotkeysEvents. Maybe we could allow users to instantiate HotkeysEvents themselves and provide those to HotkeysTarget to mutate:

import { createHotkeysTargetEvents, HotkeysTarget, showHotkeysDialog } from "@blueprintjs/core";

const targetEvents = createHotkeysTargetEvents();

@HotkeysTarget({ events: targetEvents })
export class MyComponent extends React.Component {
    ...
}

// programmatically show the dialog for this target
// HotkeysTarget will have registered the actions in the "events" objects at render time
showHotkeysDialog(targetEvents.getActions());

where core provides:

export function createHotkeysTargetEvents() {
    const globalEvents = new HotkeysEvents(HotkeyScope.GLOBAL);
    const localEvents = new HotkeysEvents(HotkeyScope.LOCAL);
    // HotkeysEvents#getActions() will need to be exposed as a new public method
    const getActions = [ ...globalEvents.getActions(), ...localEvents.getActions() ];

    return {
        globalEvents,
        localEvents,
        getActions,
    };
}

it's a little janky but could potentially work, if someone wants to attempt a PR...

@tnrich
Copy link
Contributor

tnrich commented Feb 13, 2019

import { HotkeysDialogSingleton } from "@blueprintjs/core";
HotkeysDialogSingleton.show();

definitely makes the most sense to me. I don't see why the same API couldn't be used internally as well?

@adidahiya
Copy link
Contributor

@tnrich because we don't know which hotkeys dialog to show (there can be local hotkeys). We could ask the .show() caller to provide a DOM element where hotkeys have been attached, but then it's no better than calling document.dispatchEvent (and not very react idiomatic)

@tnrich
Copy link
Contributor

tnrich commented Feb 13, 2019

@adidahiya makes sense. Maybe each <Hotkeys> element could accept a user-specified identifier. Then the user could say:

//file 1
<Hotkeys id="quitSaveHotKeys">
    <Hotkey label="Quit" combo="ctrl+q" global onKeyDown={handleQuit} />
    <Hotkey label="Save" combo="ctrl+s" group="File" onKeyDown={handleSave} />
</Hotkeys>


//file 2
<Hotkeys id="awesomeHotkeys">
            <Hotkey
                global={true}
                combo="shift + a"
                label="Be awesome all the time"
                onKeyDown={() => console.log("Awesome!")}
            />
            <Hotkey
                group="Fancy shortcuts"
                combo="shift + f"
                label="Be fancy only when focused"
                onKeyDown={() => console.log("So fancy!")}
            />
        </Hotkeys>;

//file 3
HotkeysDialogSingleton.show(); //trigger global hotkeys if it exists
HotkeysDialogSingleton.show("quitSaveHotKeys"); //trigger the quitSaveHotKeys
HotkeysDialogSingleton.show("awesomeHotkeys"); //trigger the awesomeHotkeys

Could that work? It seems like it would be a lot simpler than the alternative.

@adidahiya
Copy link
Contributor

@tnrich yeah, that could work too... although the id would have to be provided to HotkeysTarget (Hotkeys is just a pure rendering component). And then we'd have to import the HOTKEYS_DIALOG singleton into hotkeysTarget.tsx which makes the module side-effect situation even worse... I kind of prefer the approach I outlined because it allows further customization of the dialog... I think eventually we should consider removing the dialog singleton object entirely and require users to render <HotkeysDialog> on their own in the render tree

@adidahiya
Copy link
Contributor

With #4532, it will be possible to programmatically show the hotkeys dialog but this is not documented:

import { HotkeysContext } from "@blueprintjs/core";
import { useContext, useEffect } from "react";

function MyComponent() {
  const [, dispatch] = useContext(HotkeysContext);
  useEffect(() => {
    dispatch({ type: "OPEN_DIALOG" });
  }, []);

  return <div />;
}

@lawsonw
Copy link

lawsonw commented Apr 3, 2023

To bump a question from 5 years ago: Is there a way to disable the ? hotkey from opening the hotkeys dialog? I need to bind that hotkey to something else in what I'm working on, but it keeps calling both that action and opening the dialog.

@michael-yx-wu
Copy link
Contributor

michael-yx-wu commented Apr 5, 2023

In your own event handler, you can try to call event#stopImmediatePropagation. Both the local and global hotkey listeners check for ?. This should prevent the global listener from firing. It might also prevent the local listener from firing, provided your handler is invoked first.

The correct long-term solution probably involves providing a prop to let you disable this behavior explicitly. By the way, are you using HotkeysTarget or HotkeysTarget2?

@lawsonw
Copy link

lawsonw commented Apr 6, 2023

I'm using HotkeysProvider and feeding hotkeys into it via useHotkeys(). I don't have any direct calls to HotkeysTarget or HotkeysTarget2, but I am using Table2 in some places, which internally calls a HotkeysTarget2.

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

Successfully merging a pull request may close this issue.

9 participants