Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,9 @@ interface TreeProps<T> {
searchTerm?: string;
searchMatch?: (node: NodeApi<T>, searchTerm: string) => boolean;

/* Keybinding */
keybinding?: Keybinding;

/* Extra */
className?: string | undefined;
rowClassName?: string | undefined;
Expand Down
200 changes: 29 additions & 171 deletions packages/react-arborist/src/components/default-container.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { FixedSizeList } from "react-window";
import { useDataUpdates, useTreeApi } from "../context";
import { focusNextElement, focusPrevElement } from "../utils";
import { ListOuterElement } from "./list-outer-element";
import { ListInnerElement } from "./list-inner-element";
import { RowContainer } from "./row-container";

let focusSearchTerm = "";
let timeoutId: any = null;
import { SearchForNode } from "../interfaces/commands";
import {
parseKeybinding,
filterFalseyToString,
} from "../interfaces/keybinding";

/**
* All these keyboard shortcuts seem like they should be configurable.
Expand Down Expand Up @@ -41,177 +42,34 @@ export function DefaultContainer() {
if (tree.isEditing) {
return;
}
if (e.key === "Backspace") {
if (!tree.props.onDelete) return;
const ids = Array.from(tree.selectedIds);
if (ids.length > 1) {
let nextFocus = tree.mostRecentNode;
while (nextFocus && nextFocus.isSelected) {
nextFocus = nextFocus.nextSibling;
}
if (!nextFocus) nextFocus = tree.lastNode;
tree.focus(nextFocus, { scroll: false });
tree.delete(Array.from(ids));
} else {
const node = tree.focusedNode;
if (node) {
const sib = node.nextSibling;
const parent = node.parent;
tree.focus(sib || parent, { scroll: false });
tree.delete(node);
}
}
return;
}
if (e.key === "Tab" && !e.shiftKey) {
e.preventDefault();
focusNextElement(e.currentTarget);
return;
}
if (e.key === "Tab" && e.shiftKey) {
e.preventDefault();
focusPrevElement(e.currentTarget);
return;
}
if (e.key === "ArrowDown") {
e.preventDefault();
const next = tree.nextNode;
if (e.metaKey) {
tree.select(tree.focusedNode);
tree.activate(tree.focusedNode);
return;
} else if (!e.shiftKey || tree.props.disableMultiSelection) {
tree.focus(next);
return;
} else {
if (!next) return;
const current = tree.focusedNode;
if (!current) {
tree.focus(tree.firstNode);
} else if (current.isSelected) {
tree.selectContiguous(next);
} else {
tree.selectMulti(next);
}
return;
}
}
if (e.key === "ArrowUp") {
e.preventDefault();
const prev = tree.prevNode;
if (!e.shiftKey || tree.props.disableMultiSelection) {
tree.focus(prev);
return;
} else {
if (!prev) return;
const current = tree.focusedNode;
if (!current) {
tree.focus(tree.lastNode); // ?
} else if (current.isSelected) {
tree.selectContiguous(prev);
} else {
tree.selectMulti(prev);
}
return;
}
}
if (e.key === "ArrowRight") {
const node = tree.focusedNode;
if (!node) return;
if (node.isInternal && node.isOpen) {
tree.focus(tree.nextNode);
} else if (node.isInternal) tree.open(node.id);
return;
}
if (e.key === "ArrowLeft") {
const node = tree.focusedNode;
if (!node || node.isRoot) return;
if (node.isInternal && node.isOpen) tree.close(node.id);
else if (!node.parent?.isRoot) {
tree.focus(node.parent);
}
return;
}
if (e.key === "a" && e.metaKey) {
e.preventDefault();
tree.selectAll();
return;
}
if (e.key === "a" && !e.metaKey) {
if (!tree.props.onCreate) return;
tree.createLeaf();
return;
}
if (e.key === "A" && !e.metaKey) {
if (!tree.props.onCreate) return;
tree.createInternal();
return;
}

if (e.key === "Home") {
// add shift keys
e.preventDefault();
tree.focus(tree.firstNode);
return;
}
if (e.key === "End") {
// add shift keys
const keybinding = tree.keybinding;

const keysToControls = parseKeybinding(keybinding);

const currentKeys = [
e.key.toLowerCase(),
e.shiftKey ? "shift" : false,
e.metaKey ? "meta" : false,
].filter(filterFalseyToString);

const matches = keysToControls.filter((keysToControl) => {
const keys = keysToControl[0];
return (
keys.length === currentKeys.length &&
keys.every((key) => currentKeys.includes(key))
);
});

if (matches.length > 0) {
e.preventDefault();
tree.focus(tree.lastNode);
return;
}
if (e.key === "Enter") {
if (!tree.props.onRename) return;
setTimeout(() => {
if (tree.focusedNode) tree.edit(tree.focusedNode);
matches.forEach((match) => {
const control = match[1];
control(tree, e);
});
return;
} else {
SearchForNode(tree, e);
}
if (e.key === " ") {
e.preventDefault();
const node = tree.focusedNode;
if (!node) return;
if (node.isLeaf) {
node.select();
node.activate();
} else {
node.toggle();
}
return;
}
if (e.key === "*") {
const node = tree.focusedNode;
if (!node) return;
tree.openSiblings(node);
return;
}
if (e.key === "PageUp") {
e.preventDefault();
tree.pageUp();
return;
}
if (e.key === "PageDown") {
e.preventDefault();
tree.pageDown();
}

// If they type a sequence of characters
// collect them. Reset them after a timeout.
// Use it to search the tree for a node, then focus it.
// Clean this up a bit later
clearTimeout(timeoutId);
focusSearchTerm += e.key;
timeoutId = setTimeout(() => {
focusSearchTerm = "";
}, 600);
const node = tree.visibleNodes.find((n) => {
// @ts-ignore
const name = n.data.name;
if (typeof name === "string") {
return name.toLowerCase().startsWith(focusSearchTerm);
} else return false;
});
if (node) tree.focus(node.id);
}}
>
<FixedSizeList
Expand Down
1 change: 1 addition & 0 deletions packages/react-arborist/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from "./interfaces/node-api";
export * from "./interfaces/tree-api";
export * from "./data/simple-tree";
export * from "./hooks/use-simple-tree";
export { DEFAULT_KEYBINDING } from "./interfaces/keybinding";
Loading