From 8ba482f0b61775bda4f815b098e34a01eecd8dc5 Mon Sep 17 00:00:00 2001 From: James Kerr Date: Tue, 19 Mar 2024 14:23:34 -0700 Subject: [PATCH] Disable Drop Works --- .../src/controllers/node-controller.ts | 11 ++++++- .../src/controllers/tree-controller.ts | 32 ++++++++++++++++--- .../react-arborist/src/dnd/safe-to-drop.ts | 31 ++++++++++++++++++ modules/react-arborist/src/dnd/types.ts | 7 ++++ .../react-arborist/src/dnd/use-node-drop.ts | 2 +- .../src/nodes/default-node-objects.ts | 4 +-- .../src/types/tree-view-props.ts | 3 +- 7 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 modules/react-arborist/src/dnd/safe-to-drop.ts diff --git a/modules/react-arborist/src/controllers/node-controller.ts b/modules/react-arborist/src/controllers/node-controller.ts index c297920..da50e75 100644 --- a/modules/react-arborist/src/controllers/node-controller.ts +++ b/modules/react-arborist/src/controllers/node-controller.ts @@ -43,7 +43,7 @@ export class NodeController { return this.object.parent?.id || null; } - get parent() { + get parent(): NodeController | null { if (this.parentId) { return this.tree.get(this.parentId); } else { @@ -94,6 +94,15 @@ export class NodeController { } as Record; } + isDescendantOf(node: NodeController) { + let cursor: NodeController | null = this; + while (cursor) { + if (cursor.id === node.id) return true; + cursor = cursor.parent; + } + return false; + } + /* Open State */ get isOpen() { return this.isInternal && this.tree.isOpen(this.id); diff --git a/modules/react-arborist/src/controllers/tree-controller.ts b/modules/react-arborist/src/controllers/tree-controller.ts index 44fdc20..ba8cad9 100644 --- a/modules/react-arborist/src/controllers/tree-controller.ts +++ b/modules/react-arborist/src/controllers/tree-controller.ts @@ -1,4 +1,5 @@ import { CursorState } from "../cursor/types"; +import { safeToDrop } from "../dnd/safe-to-drop"; import { TreeViewProps } from "../types/tree-view-props"; import { NodeController } from "./node-controller"; @@ -58,15 +59,38 @@ export class TreeController { return len === 0 ? null : this.rows[len - 1]; } - get(id: string) { + get dragNodes() { + return this.getAll(this.props.dnd.value.dragItems); + } + + get dragSourceNode() { + return this.get(this.props.dnd.value.dragSourceId); + } + + get dropTargetParentNode() { + return this.get(this.props.dnd.value.targetParentId); + } + + get dropTargetIndex() { + return this.props.dnd.value.targetIndex; + } + + get(id: string | null): NodeController | null { + if (id === null) return null; const index = this.indexOf(id); if (index) { - this.rows[index] || null; + return this.rows[index] || null; } else { return null; } } + getAll(ids: string[]) { + return ids + .map((id) => this.get(id)) + .filter((n) => !!n) as NodeController[]; + } + nodeBefore(node: NodeController) { return this.rows[node.rowIndex - 1] || null; } @@ -211,9 +235,7 @@ export class TreeController { } canDrop() { - // todo - // mmove this into a default prop or something - return true; + return safeToDrop(this); } drop() { diff --git a/modules/react-arborist/src/dnd/safe-to-drop.ts b/modules/react-arborist/src/dnd/safe-to-drop.ts new file mode 100644 index 0000000..10b62be --- /dev/null +++ b/modules/react-arborist/src/dnd/safe-to-drop.ts @@ -0,0 +1,31 @@ +import { TreeController } from "../controllers/tree-controller"; + +export function safeToDrop(tree: TreeController) { + const targetParentNode = tree.dropTargetParentNode; + const targetIndex = tree.dropTargetIndex; + const dragNodes = tree.dragNodes; + + /* Basic Defaul Check */ + if (targetParentNode === null && targetIndex === null) return false; + + for (const draggingNode of tree.dragNodes) { + if ( + draggingNode.isInternal && + targetParentNode?.isDescendantOf(draggingNode) + ) { + return false; + } + } + + /* User Provided Check */ + const disableCheck = tree.props.disableDrop; + if (typeof disableCheck == "function") { + return !disableCheck({ dragNodes, targetParentNode, targetIndex }); + } else if (typeof disableCheck == "string" && targetParentNode) { + return !targetParentNode.data[disableCheck]; + } else if (typeof disableCheck === "boolean") { + return !disableCheck; + } else { + return true; + } +} diff --git a/modules/react-arborist/src/dnd/types.ts b/modules/react-arborist/src/dnd/types.ts index 4e4e49a..417ed12 100644 --- a/modules/react-arborist/src/dnd/types.ts +++ b/modules/react-arborist/src/dnd/types.ts @@ -1,3 +1,4 @@ +import { NodeController } from "../controllers/node-controller"; import { PartialController } from "../types/utils"; export type DndState = { @@ -26,3 +27,9 @@ export type DndPartialController = PartialController< DndState, DndOnChangeEvent >; + +export type DisableDropCheck = (args: { + dragNodes: NodeController[]; + targetParentNode: NodeController | null; + targetIndex: number | null; +}) => boolean; diff --git a/modules/react-arborist/src/dnd/use-node-drop.ts b/modules/react-arborist/src/dnd/use-node-drop.ts index 1fb4342..29e8221 100644 --- a/modules/react-arborist/src/dnd/use-node-drop.ts +++ b/modules/react-arborist/src/dnd/use-node-drop.ts @@ -16,7 +16,7 @@ export function useNodeDrop(node: NodeController, ref: any) { }); if (drop) node.tree.draggingOver(drop.parentId, drop.index!); - if (true /* canDrop? */) { + if (node.tree.canDrop()) { if (cursor) node.tree.showCursor(cursor); } else { node.tree.hideCursor(); diff --git a/modules/react-arborist/src/nodes/default-node-objects.ts b/modules/react-arborist/src/nodes/default-node-objects.ts index e530e44..f9a832c 100644 --- a/modules/react-arborist/src/nodes/default-node-objects.ts +++ b/modules/react-arborist/src/nodes/default-node-objects.ts @@ -41,11 +41,11 @@ export const defaultNodeObjects: DefaultNodeObject[] = [ name: "Mastodon", }, { - id: "10", + id: "12", name: "GitHub Profile", }, { - id: "11", + id: "13", name: "Email", }, ], diff --git a/modules/react-arborist/src/types/tree-view-props.ts b/modules/react-arborist/src/types/tree-view-props.ts index 05e32cd..36bc0a9 100644 --- a/modules/react-arborist/src/types/tree-view-props.ts +++ b/modules/react-arborist/src/types/tree-view-props.ts @@ -3,7 +3,7 @@ import { OpensPartialController } from "../opens/types"; import { PartialController } from "./utils"; import { EditOnChangeEvent, EditPartialController } from "../edit/types"; import { SelectionPartialController } from "../selection/types"; -import { DndPartialController } from "../dnd/types"; +import { DisableDropCheck, DndPartialController } from "../dnd/types"; import { CursorPartialController } from "../cursor/types"; import { FocusPartialController } from "../focus/types"; @@ -33,4 +33,5 @@ export type TreeViewProps = { /* Configurations */ openByDefault: boolean; + disableDrop?: string | boolean | DisableDropCheck; };