Blueprint: Support programatically showing the hotkeys help dialog

Created on 21 Sep 2017  Â·  18Comments  Â·  Source: palantir/blueprint

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?

core feature request help wanted

Most helpful comment

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.

All 18 comments

I found that, but couldn't call it correctly. What should the argument be
to show global hotkeys?

Also it's not exported at the top level so you have to import with the
whole path.

On 21 Sep 2017 13:40, "Antoine Llorca" notifications@github.com wrote:

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

Granted, we should document this better

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/palantir/blueprint/issues/1590#issuecomment-331229356,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AASJzEaiV3o0S--M8Ft19rMugtklMrvfks5skp-kgaJpZM4Pfi51
.

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!

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?

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.

@chiubaka (disclaimer: I haven't tried this myself) you might have success with something like our dispatchTestKeyboardEvent helper: https://github.com/palantir/blueprint/blob/487a1449c119dca6b8c2a888181ed8da947b138f/packages/test-commons/src/utils.ts#L19

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

Anyone have any updates on this one? Thanks!

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

Yeah I'd ideally like to allow some more customizability of the hotkeys
dialog itself as well.

On Thu, Apr 5, 2018, 2:58 PM Gilad Gray notifications@github.com wrote:

@tnrich https://github.com/tnrich not a drop. would you like to submit
some updates? 😄

—
You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
https://github.com/palantir/blueprint/issues/1590#issuecomment-379089352,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACmqcbbymGDYrKYXfXM7QHLGrgGFSWISks5tlpNfgaJpZM4Pfi51
.

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

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.

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...

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?

@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)

@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.

@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

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 />;
}
Was this page helpful?
0 / 5 - 0 ratings