diff --git a/modules/react-arborist/src/commands/default-commands.ts b/modules/react-arborist/src/commands/default-commands.ts index 9f59f52..fe78d29 100644 --- a/modules/react-arborist/src/commands/default-commands.ts +++ b/modules/react-arborist/src/commands/default-commands.ts @@ -1,4 +1,7 @@ +import { NodeController } from "../controllers/node-controller"; import { TreeController } from "../controllers/tree-controller"; +import { NodeObject } from "../nodes/node-object"; +import { NodeType } from "../nodes/source-data-accessor"; import { focusNextElement, focusPrevElement } from "../utils"; export type Tree = TreeController; @@ -73,12 +76,39 @@ export function focusOutsidePrev(tree: Tree) { export function destroy(tree: Tree) { if (confirm("Are you sure you want to delete?")) { if (tree.selectedIds.length) { - tree.props.nodes.onChange({ type: "destroy", ids: tree.selectedIds }); + tree.destroy(tree.selectedIds); } else if (tree.focusedNode) { - tree.props.nodes.onChange({ - type: "destroy", - ids: [tree.focusedNode.id], - }); + tree.destroy([tree.focusedNode.id]); } } } + +function create(tree: Tree, nodeType: NodeType) { + const node = tree.focusedNode; + const parentId = getInsertParentId(node); + const index = getInsertIndex(tree, node); + const data = tree.props.nodes.initialize({ nodeType }); + tree.create({ parentId, index, data }); + tree.edit(data.id); + tree.focus(data.id); +} + +export function createLeaf(tree: Tree) { + create(tree, "leaf"); +} + +export function createInternal(tree: Tree) { + create(tree, "internal"); +} + +function getInsertParentId(focus: NodeController | null) { + if (!focus) return null; + if (focus.isOpen) return focus.id; + return focus.parentId; +} + +function getInsertIndex(tree: Tree, focus: NodeController | null) { + if (!focus) return tree.props.nodes.value.length; + if (focus.isOpen) return 0; + return focus.childIndex + 1; +} diff --git a/modules/react-arborist/src/components/tree-view.tsx b/modules/react-arborist/src/components/tree-view.tsx index cac2a84..fa21652 100644 --- a/modules/react-arborist/src/components/tree-view.tsx +++ b/modules/react-arborist/src/components/tree-view.tsx @@ -169,7 +169,6 @@ function NodeRenderer(props: { node: NodeController; }) { const { node, attrs } = props; - const style = { attrs }; function onSubmit(e: any) { e.preventDefault(); @@ -208,6 +207,7 @@ function NodeRenderer(props: { {node.isEditing ? (
{ }); } + /* CRUD Operations */ + + create(args: { parentId: string | null; index: number; data: T }) { + this.props.nodes.onChange({ type: "create", ...args }); + } + + update(args: { id: string; changes: Partial }) { + this.props.nodes.onChange({ type: "update", ...args }); + } + + destroy(ids: string[]) { + this.props.nodes.onChange({ type: "destroy", ids: ids }); + } + /* Edit State */ + get isEditing() { + return this.props.edit.value !== null; + } + isEditId(id: string) { return this.props.edit.value === id; } @@ -194,8 +212,9 @@ export class TreeController { } submit(id: string, changes: Partial) { - this.props.nodes.onChange({ type: "update", id, changes }); + this.update({ id, changes }); this.props.edit.onChange({ value: null }); + this.focus(id); } /* Selection State */ @@ -365,6 +384,7 @@ export class TreeController { /* Keyboard Shortcuts */ handleKeyDown(e: React.KeyboardEvent) { + if (this.isEditing) return; const focused = this.focusedNode; const shortcut = new ShortcutManager(this.props.shortcuts).find( e.nativeEvent, diff --git a/modules/react-arborist/src/nodes/default-accessors.ts b/modules/react-arborist/src/nodes/default-accessors.ts index 1290958..edf787f 100644 --- a/modules/react-arborist/src/nodes/default-accessors.ts +++ b/modules/react-arborist/src/nodes/default-accessors.ts @@ -23,6 +23,14 @@ export function createDefaultAccessors(): SourceDataAccessors { return true; } }, + initialize: ({ nodeType }) => { + const data = { + id: new Date().getTime().toString(), + name: "", + } as any; + if (nodeType === "internal") data.children = []; + return data; + }, sortBy: [], sortOrder: [], }; diff --git a/modules/react-arborist/src/nodes/source-data-accessor.ts b/modules/react-arborist/src/nodes/source-data-accessor.ts index 9620847..8f4f38b 100644 --- a/modules/react-arborist/src/nodes/source-data-accessor.ts +++ b/modules/react-arborist/src/nodes/source-data-accessor.ts @@ -3,11 +3,15 @@ import { createDefaultAccessors } from "./default-accessors"; type GetSortField = (d: T) => number | string | boolean; type SortOrder = "asc" | "desc"; +type Initializer = (args: { nodeType: NodeType }) => T; + +export type NodeType = "leaf" | "internal"; export type SourceDataAccessors = { id: (d: T) => string; children: (d: T) => T[] | null; isLeaf: (d: T) => boolean; + initialize: Initializer; sortBy: GetSortField | GetSortField[]; sortOrder: SortOrder | SortOrder[]; }; @@ -19,6 +23,10 @@ export class SourceDataAccessor { this.access = { ...createDefaultAccessors(), ...accessors }; } + initialize(args: { nodeType: NodeType }) { + return this.access.initialize(args); + } + getId(d: T): string { return this.access.id(d); } diff --git a/modules/react-arborist/src/nodes/tree-manager.ts b/modules/react-arborist/src/nodes/tree-manager.ts index 6b43478..8c6e6d4 100644 --- a/modules/react-arborist/src/nodes/tree-manager.ts +++ b/modules/react-arborist/src/nodes/tree-manager.ts @@ -1,4 +1,5 @@ import { + NodeType, SourceDataAccessor, SourceDataAccessors, } from "./source-data-accessor"; @@ -18,6 +19,10 @@ export class TreeManager { }); } + initialize(args: { nodeType: NodeType }): T { + return this.accessor.initialize(args); + } + create(args: { parentId: string | null; index: number; data: T }) { const { parentId, index, data } = args; if (!parentId) { diff --git a/modules/react-arborist/src/nodes/types.ts b/modules/react-arborist/src/nodes/types.ts index 7389960..767d03a 100644 --- a/modules/react-arborist/src/nodes/types.ts +++ b/modules/react-arborist/src/nodes/types.ts @@ -1,5 +1,6 @@ import { PartialController } from "../types/utils"; import { NodeObject } from "./node-object"; +import { NodeType } from "./source-data-accessor"; export type CreateEvent = { type: "create"; @@ -36,4 +37,4 @@ export type NodesState = NodeObject[]; export type NodesPartialController = PartialController< NodesState, NodesOnChangeEvent ->; +> & { initialize: (args: { nodeType: NodeType }) => T }; diff --git a/modules/react-arborist/src/nodes/use-nodes.ts b/modules/react-arborist/src/nodes/use-nodes.ts index ef38a54..b2f857c 100644 --- a/modules/react-arborist/src/nodes/use-nodes.ts +++ b/modules/react-arborist/src/nodes/use-nodes.ts @@ -13,6 +13,7 @@ export function useNodes( return { setSourceData, + initialize: (args) => treeManager.initialize(args), value: treeManager.nodes as NodeObject[], onChange: (event: NodesOnChangeEvent) => { switch (event.type) { @@ -23,7 +24,6 @@ export function useNodes( treeManager.move(event); break; case "update": - console.log(event); treeManager.update(event); break; case "destroy": diff --git a/modules/react-arborist/src/shortcuts/default-shortcuts.ts b/modules/react-arborist/src/shortcuts/default-shortcuts.ts index 45539fe..6ee94b2 100644 --- a/modules/react-arborist/src/shortcuts/default-shortcuts.ts +++ b/modules/react-arborist/src/shortcuts/default-shortcuts.ts @@ -17,10 +17,13 @@ export const defaultShortcuts: ShortcutAttrs[] = [ { key: "Tab", command: "focusOutsideNext" }, { key: "Shift+Tab", command: "focusOutsidePrev" }, + /* CRUD */ { key: "Backspace", command: "destroy" }, - // { key: "Tab", command: "focusNextOutside" }, - // { key: "Shift+Tab", command: "focusNextOutside" }, - // { key: "Shift+Tab", command: "focusNextOutside" }, + { key: "a", command: "createLeaf" }, + { key: "Shift+A", command: "createInternal" }, + + /* Selection */ + // { key: "Meta+ArrowDown", command: "activate" }, // { key: "Shift+ArrowDown", command: "extendSelectionDown" }, // { key: "Shift+ArrowUp", command: "extendSelectionUp" },