From 7e58836b581f21a3c783a9f78fd6b1c6bf1634bd Mon Sep 17 00:00:00 2001 From: Evgeni Dimov Date: Tue, 12 Dec 2023 10:02:42 +0200 Subject: [PATCH 1/4] adds hover.atTop check in computeDrop --- packages/react-arborist/src/dnd/compute-drop.ts | 7 +++++-- packages/react-arborist/src/dnd/drop-hook.ts | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/react-arborist/src/dnd/compute-drop.ts b/packages/react-arborist/src/dnd/compute-drop.ts index c492aa8..75d9204 100644 --- a/packages/react-arborist/src/dnd/compute-drop.ts +++ b/packages/react-arborist/src/dnd/compute-drop.ts @@ -151,8 +151,11 @@ export function computeDrop(args: Args): ComputedDrop { }; } - /* The above node is an item or a closed folder */ - if (isItem(above) || isClosed(above)) { + /** + * The above node is an item, a closed folder or hovering over + * the top of next node, proposing to append as next sibling. + */ + if (isItem(above) || isClosed(above) || hover.atTop) { const level = getDropLevel(hover, above, below, args.indent); return { drop: walkUpFrom(above, level), diff --git a/packages/react-arborist/src/dnd/drop-hook.ts b/packages/react-arborist/src/dnd/drop-hook.ts index 6be259d..9938bdf 100644 --- a/packages/react-arborist/src/dnd/drop-hook.ts +++ b/packages/react-arborist/src/dnd/drop-hook.ts @@ -20,9 +20,9 @@ export function useDropHook( () => ({ accept: "NODE", canDrop: () => tree.canDrop(), - hover: (_item, m) => { + hover: (item, m) => { const offset = m.getClientOffset(); - if (!el.current || !offset) return; + if (!el.current || !offset || item.id === node.id) return; const { cursor, drop } = computeDrop({ element: el.current, offset: offset, From c9af3ab34ab5f91015ded71459f6f1307a2164af Mon Sep 17 00:00:00 2001 From: Evgeni Dimov Date: Wed, 13 Dec 2023 12:42:11 +0200 Subject: [PATCH 2/4] adds isOpenWithEmptyChildren to hover.atTop check on computeDrop --- packages/react-arborist/src/dnd/compute-drop.ts | 4 ++-- packages/react-arborist/src/utils.ts | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/react-arborist/src/dnd/compute-drop.ts b/packages/react-arborist/src/dnd/compute-drop.ts index 75d9204..c5357fe 100644 --- a/packages/react-arborist/src/dnd/compute-drop.ts +++ b/packages/react-arborist/src/dnd/compute-drop.ts @@ -1,6 +1,6 @@ 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) { @@ -155,7 +155,7 @@ export function computeDrop(args: Args): ComputedDrop { * The above node is an item, a closed folder or hovering over * the top of next node, proposing to append as next sibling. */ - if (isItem(above) || isClosed(above) || hover.atTop) { + if (isItem(above) || isClosed(above) || (!below && hover.inBottomHalf) || (hover.atTop && isOpenWithEmptyChildren(above))) { const level = getDropLevel(hover, above, below, args.indent); return { drop: walkUpFrom(above, level), 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 */ From 1f433fddbc665f9342e6eba3c6515526d0e35cbb Mon Sep 17 00:00:00 2001 From: James Kerr Date: Wed, 13 Dec 2023 14:07:37 -0800 Subject: [PATCH 3/4] Add Ability to Drop as Sibling of Open, Empty Folder --- .../react-arborist/src/dnd/compute-drop.ts | 78 +++++++++++-------- packages/react-arborist/src/dnd/drop-hook.ts | 3 +- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/packages/react-arborist/src/dnd/compute-drop.ts b/packages/react-arborist/src/dnd/compute-drop.ts index c5357fe..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, isOpenWithEmptyChildren } 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,19 +141,43 @@ export function computeDrop(args: Args): ComputedDrop { }; } - /** - * The above node is an item, a closed folder or hovering over - * the top of next node, proposing to append as next sibling. - */ - if (isItem(above) || isClosed(above) || (!below && hover.inBottomHalf) || (hover.atTop && isOpenWithEmptyChildren(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/dnd/drop-hook.ts b/packages/react-arborist/src/dnd/drop-hook.ts index 9938bdf..0cfd908 100644 --- a/packages/react-arborist/src/dnd/drop-hook.ts +++ b/packages/react-arborist/src/dnd/drop-hook.ts @@ -22,7 +22,8 @@ export function useDropHook( canDrop: () => tree.canDrop(), hover: (item, m) => { const offset = m.getClientOffset(); - if (!el.current || !offset || item.id === node.id) return; + + if (!el.current || !offset) return; const { cursor, drop } = computeDrop({ element: el.current, offset: offset, From b0339b34945205d9e9e46eb90a4ddb5772d044a1 Mon Sep 17 00:00:00 2001 From: James Kerr Date: Wed, 13 Dec 2023 14:23:31 -0800 Subject: [PATCH 4/4] Back Out Change --- packages/react-arborist/src/dnd/drop-hook.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-arborist/src/dnd/drop-hook.ts b/packages/react-arborist/src/dnd/drop-hook.ts index 0cfd908..6be259d 100644 --- a/packages/react-arborist/src/dnd/drop-hook.ts +++ b/packages/react-arborist/src/dnd/drop-hook.ts @@ -20,9 +20,8 @@ export function useDropHook( () => ({ accept: "NODE", canDrop: () => tree.canDrop(), - hover: (item, m) => { + hover: (_item, m) => { const offset = m.getClientOffset(); - if (!el.current || !offset) return; const { cursor, drop } = computeDrop({ element: el.current,