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;
/**
* All these keyboard shortcuts seem like they should be configurable.
* Each operation should be a given a name and separated from
* the event handler. Future clean up welcome.
*/
export function DefaultContainer() {
useDataUpdates();
const tree = useTreeApi();
return (
{
if (!e.currentTarget.contains(e.relatedTarget)) {
tree.onFocus();
}
}}
onBlur={(e) => {
if (!e.currentTarget.contains(e.relatedTarget)) {
tree.onBlur();
}
}}
onKeyDown={(e) => {
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 && !tree.props.disableMultiSelection) {
e.preventDefault();
tree.selectAll();
return;
}
if (e.key === "a" && !e.metaKey && tree.props.onCreate) {
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
e.preventDefault();
tree.focus(tree.lastNode);
return;
}
if (e.key === "Enter") {
const node = tree.focusedNode;
if (!node) return;
if (!node.isEditable || !tree.props.onRename) return;
setTimeout(() => {
if (node) tree.edit(node);
});
return;
}
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);
}}
>
{/* @ts-ignore */}
tree.visibleNodes[index]?.id || index}
outerElementType={ListOuterElement}
innerElementType={ListInnerElement}
onScroll={tree.props.onScroll}
onItemsRendered={tree.onItemsRendered.bind(tree)}
ref={tree.list}
>
{RowContainer}
);
}