react-arborist icon indicating copy to clipboard operation
react-arborist copied to clipboard

Configurable keyboard shortcuts

Open jameskerr opened this issue 3 years ago • 13 comments

All the keyboard shortcuts are listed in the DefaultContainer component. I could see how people would want to use their own keybindings.

Something like:

{
  "a": "createLeaf",
  "shift+a": "createInternal"
  "space": "activate",
  "meta+DownArrow": "activate"
}

jameskerr avatar Oct 28 '22 15:10 jameskerr

Yes! I wish there was a way to do this. Also, I am trying to bind these functions to some buttons outside of the tree. I tried creating a ref and passing it to the Tree component, and the using the ref to do tree.createLeaf(). But it doesn't seem to be working.

amarnath-cohesive avatar Oct 30 '22 18:10 amarnath-cohesive

Can you post the code that's not working?

If it's a ref, you may need to tree.current.createLeaf().

Also, if you're using the data prop. you'll need to have an onCreate prop on the tree in order for createLeaf() work.

jameskerr avatar Nov 01 '22 19:11 jameskerr

I should have used tree.current.createLeaf(). Thanks for the response.

amarnath-cohesive avatar Nov 02 '22 14:11 amarnath-cohesive

hi @jameskerr, is there any way to disable the keyboard shortcuts from the Container props? I want to disable space shortcuts to toggle the parent folder

hilmanauz avatar Nov 25 '22 08:11 hilmanauz

@hilmanauz You can always intercept the onKeyDown event in your RowRenderer and call e.stopPropagation(). That's a workaround for today.

jameskerr avatar Nov 28 '22 21:11 jameskerr

@holloway I want to break up that giant onKeyDown event listener found in DefaultContainer. Each of those cases should be a named function with the TreeApi as the only argument. All of those could go in their own file called commands.ts.

The commands should be put in an object; the key is the name, the value is function.

Another object will map a keybinding string to the string name of the command function. A user could then configure this by passing their own object.

Then in the DefaultContainer#onKeyDown handler, we have some logic that turns that event into a keybinding string. We could follow VSCode's example for how to structure the string (Shift+ArrowDown) for example. Then we run whatever command is mapped to that keybinding string using the object above.

I'm wondering if we want to only make a few of the keybindings available for customization. For example, "ArrowDown" should probably not be configurable. But "Space" should be.

Something Like

<DefaultContainer
  onKeyDown={(e) => {
    const keybinding = toKeyBinding(e)
    tree.runCommandAt(keybinding)
  }}
/>

class TreeApi() {
  runCommandAt(key: string) {
    const commandName = this.keybindings[key]
    const command = this.commands[commandName]
    if (command) command(this)
  }
}

jameskerr avatar Nov 28 '22 21:11 jameskerr

@hilmanauz , what worked for me was to host the library on my own github after removing the onKeyDown listener from the DefaultContainer file.

amarnath-cohesive avatar Nov 29 '22 05:11 amarnath-cohesive

it works for me @jameskerr, thanks for your advice. I forgot there's an onKeyDown prop in every element.

hilmanauz avatar Nov 29 '22 07:11 hilmanauz

Hi @jameskerr see #76 ... keen for a review :smile:

holloway avatar Dec 05 '22 20:12 holloway

Work on this is progressing. Today I just finished it in the use-nodes branch.

Keyboard shortcuts look like this:

import { ShortcutAttrs } from "./types";

export const defaultShortcuts: ShortcutAttrs[] = [
  /* Keyboard Navigation */
  { key: "ArrowDown", command: "focusNext" },
  { key: "ArrowUp", command: "focusPrev" },
  { key: "ArrowLeft", command: "focusParent", when: "isLeaf || isClosed" },
  { key: "ArrowLeft", command: "close", when: "isOpen" },
  { key: "ArrowRight", command: "open", when: "isClosed" },
  { key: "ArrowRight", command: "focusNext", when: "isOpen" },
  { key: "Home", command: "focusFirst" },
  { key: "End", command: "focusLast" },
  { key: "PageDown", command: "focusNextPage" },
  { key: "PageUp", command: "focusPrevPage" },

  /* Tabbing Around */
  { key: "Tab", command: "focusOutsideNext" },
  { key: "Shift+Tab", command: "focusOutsidePrev" },

  /* CRUD */
  { key: "Backspace", command: "destroy" },
  { key: "a", command: "createLeaf" },
  { key: "Shift+A", command: "createInternal" },
  { key: "Enter", command: "edit" },

  /* Selection */
  { key: "Shift+ArrowUp", command: "moveSelectionStart" },
  { key: "Shift+ArrowDown", command: "moveSelectionEnd" },
  { key: " ", command: "toggle", when: "isInternal" },
  { key: " ", command: "select", when: "isLeaf" },
  { key: "Meta+a", command: "selectAll" },
  { key: "Control+a", command: "selectAll" },
];

The commands look like so (just a few examples)

export function focusFirst(tree: Tree) {
  if (tree.firstNode) tree.focus(tree.firstNode.id);
}

export function focusLast(tree: Tree) {
  if (tree.lastNode) tree.focus(tree.lastNode.id);
}

export function focusNext(tree: Tree) {
  const next = tree.nextNode || tree.firstNode;
  if (next) tree.focus(next.id);
}

export function focusPrev(tree: Tree) {
  const prev = tree.prevNode || tree.lastNode;
  if (prev) tree.focus(prev.id);
}

The they get connected in the new <TreeView /> component.

export type TreeViewProps<T> = {
  /* Commands and Shortcuts */
  shortcuts: ShortcutAttrs[];
  commands: CommandObject<T>;
}

jameskerr avatar Apr 03 '24 17:04 jameskerr

Thank you for all the work you're doing, I'm really looking forward to this feature.

Just one thought: since a mouse click will "select" when clicking a leaf node and both "toggle" and "select" when clicking a node that has children, it may make sense to have the spacebar key behave the same way in that it both selects and toggles an isInternal.

jessicarush avatar Aug 15 '24 21:08 jessicarush

People do really want to use their own keybindings :) Thanks!

sauron918 avatar Aug 31 '24 11:08 sauron918

Thanks for the comments and suggestions. I'm excited to ship more of these features, but I'm busy with other projects at the moment. Everything takes longer than first expected!

jameskerr avatar Sep 03 '24 19:09 jameskerr