Configurable keyboard shortcuts
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"
}
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.
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.
I should have used tree.current.createLeaf(). Thanks for the response.
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 You can always intercept the onKeyDown event in your RowRenderer and call e.stopPropagation(). That's a workaround for today.
@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)
}
}
@hilmanauz , what worked for me was to host the library on my own github after removing the onKeyDown listener from the DefaultContainer file.
it works for me @jameskerr, thanks for your advice. I forgot there's an onKeyDown prop in every element.
Hi @jameskerr see #76 ... keen for a review :smile:
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>;
}
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.
People do really want to use their own keybindings :) Thanks!
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!