From b16cc861b2f76f30659e63bb804d5a7941181ef5 Mon Sep 17 00:00:00 2001 From: James Kerr Date: Wed, 13 Dec 2023 14:30:15 -0800 Subject: [PATCH] Drop as Sibling of Empty, Open Folder (#202) Based off #199 Thanks to the understanding and work of @edimov, we can now drop a node as a sibling (or child) of an open, but childless node. This should unlock people who want to create trees where every single node is an open, internal node. Co-authored-by: Evgeni Dimov --- .../react-arborist/src/dnd/compute-drop.ts | 75 ++++++++++++------- packages/react-arborist/src/utils.ts | 4 + 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/packages/react-arborist/src/dnd/compute-drop.ts b/packages/react-arborist/src/dnd/compute-drop.ts index c492aa8..4f4a69b 100644 --- a/packages/react-arborist/src/dnd/compute-drop.ts +++ b/packages/react-arborist/src/dnd/compute-drop.ts @@ -1,6 +1,12 @@ import { XYCoord } from "react-dnd"; import { NodeApi } from "../interfaces/node-api"; -import { bound, indexOf, isClosed, isItem } from "../utils"; +import { + bound, + indexOf, + isClosed, + isItem, + isOpenWithEmptyChildren, +} from "../utils"; import { DropResult } from "./drop-hook"; function measureHover(el: HTMLElement, offset: XYCoord) { @@ -56,28 +62,6 @@ type Args = { nextNode: NodeApi | null; }; -function getDropLevel( - hovering: HoverData, - aboveCursor: NodeApi | null, - belowCursor: NodeApi | null, - indent: number -) { - const hoverLevel = Math.round(Math.max(0, hovering.x - indent) / indent); - let min, max; - if (!aboveCursor) { - max = 0; - min = 0; - } else if (!belowCursor) { - max = aboveCursor.level; - min = 0; - } else { - max = aboveCursor.level; - min = belowCursor.level; - } - - return bound(hoverLevel, min, max); -} - export type ComputedDrop = { drop: DropResult | null; cursor: Cursor | null; @@ -128,10 +112,11 @@ export type Cursor = LineCursor | NoCursor | HighlightCursor; /** * This is the most complex, tricky function in the whole repo. - * It could be simplified and made more understandable. */ export function computeDrop(args: Args): ComputedDrop { const hover = measureHover(args.element, args.offset); + const indent = args.indent; + const hoverLevel = Math.round(Math.max(0, hover.x - indent) / indent); const { node, nextNode, prevNode } = args; const [above, below] = getNodesAroundCursor(node, prevNode, nextNode, hover); @@ -143,7 +128,12 @@ export function computeDrop(args: Args): ComputedDrop { }; } - /* At the top of the list */ + /* + * Now we only need to care about the node above the cursor + * ----------- ------- + */ + + /* There is no node above the cursor line */ if (!above) { return { drop: dropAt(below?.parent?.id, 0), @@ -151,16 +141,43 @@ export function computeDrop(args: Args): ComputedDrop { }; } - /* The above node is an item or a closed folder */ - if (isItem(above) || isClosed(above)) { - const level = getDropLevel(hover, above, below, args.indent); + /* The node above the cursor line is an item */ + if (isItem(above)) { + const level = bound(hoverLevel, below?.level || 0, above.level); + return { + drop: walkUpFrom(above, level), + cursor: lineCursor(above.rowIndex! + 1, level), + }; + } + + /* The node above the cursor line is a closed folder */ + if (isClosed(above)) { + const level = bound(hoverLevel, below?.level || 0, above.level); return { drop: walkUpFrom(above, level), cursor: lineCursor(above.rowIndex! + 1, level), }; } - /* The above node is an open folder */ + /* The node above the cursor line is an open folder with no children */ + if (isOpenWithEmptyChildren(above)) { + const level = bound(hoverLevel, 0, above.level + 1); + if (level > above.level) { + /* Will be the first child of the empty folder */ + return { + drop: dropAt(above.id, 0), + cursor: lineCursor(above.rowIndex! + 1, level), + }; + } else { + /* Will be a sibling or grandsibling of the empty folder */ + return { + drop: walkUpFrom(above, level), + cursor: lineCursor(above.rowIndex! + 1, level), + }; + } + } + + /* The node above the cursor is a an open folder with children */ return { drop: dropAt(above?.id, 0), cursor: lineCursor(above.rowIndex! + 1, above.level + 1), diff --git a/packages/react-arborist/src/utils.ts b/packages/react-arborist/src/utils.ts index aeec8e0..4d69307 100644 --- a/packages/react-arborist/src/utils.ts +++ b/packages/react-arborist/src/utils.ts @@ -14,6 +14,10 @@ export function isClosed(node: NodeApi | null) { return node && node.isInternal && !node.isOpen; } +export function isOpenWithEmptyChildren(node: NodeApi | null) { + return node && node.isOpen && !node.children?.length; +} + /** * Is first param a descendant of the second param */