react-arborist 0.1.12 → 0.2.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/{lib/components → components}/drop-cursor.d.ts +1 -0
  2. package/dist/{lib/components → components}/preview.d.ts +1 -0
  3. package/dist/{lib/components → components}/row.d.ts +0 -0
  4. package/dist/{lib/components → components}/tree.d.ts +0 -0
  5. package/dist/{lib/context.d.ts → context.d.ts} +4 -3
  6. package/dist/{lib/data → data}/enrich-tree.d.ts +1 -1
  7. package/dist/{lib/data → data}/flatten-tree.d.ts +0 -0
  8. package/dist/dnd/compute-drop.d.ts +37 -0
  9. package/dist/{lib/dnd → dnd}/drag-hook.d.ts +0 -0
  10. package/dist/{lib/dnd → dnd}/drop-hook.d.ts +0 -0
  11. package/dist/{lib/dnd → dnd}/outer-drop-hook.d.ts +0 -0
  12. package/dist/index.d.ts +4 -0
  13. package/dist/index.js +1354 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/module.js +1346 -0
  16. package/dist/module.js.map +1 -0
  17. package/dist/{lib/provider.d.ts → provider.d.ts} +1 -0
  18. package/dist/{lib/reducer.d.ts → reducer.d.ts} +4 -3
  19. package/dist/{lib/selection → selection}/range.d.ts +0 -0
  20. package/dist/{lib/selection → selection}/selection-hook.d.ts +0 -0
  21. package/dist/{lib/selection → selection}/selection.d.ts +0 -0
  22. package/dist/{lib/tree-api-hook.d.ts → tree-api-hook.d.ts} +0 -0
  23. package/dist/{lib/tree-api.d.ts → tree-api.d.ts} +4 -3
  24. package/dist/{lib/types.d.ts → types.d.ts} +8 -3
  25. package/dist/types.d.ts.map +1 -0
  26. package/dist/{lib/utils.d.ts → utils.d.ts} +2 -0
  27. package/package.json +16 -43
  28. package/src/components/drop-cursor.tsx +47 -0
  29. package/src/components/preview.tsx +108 -0
  30. package/src/components/row.tsx +119 -0
  31. package/src/components/tree.tsx +118 -0
  32. package/src/context.tsx +52 -0
  33. package/src/data/enrich-tree.ts +74 -0
  34. package/src/data/flatten-tree.ts +17 -0
  35. package/src/data/make-tree.ts +37 -0
  36. package/src/dnd/compute-drop.ts +184 -0
  37. package/src/dnd/drag-hook.ts +48 -0
  38. package/src/dnd/drop-hook.ts +66 -0
  39. package/src/dnd/measure-hover.ts +26 -0
  40. package/src/dnd/outer-drop-hook.ts +50 -0
  41. package/src/index.ts +5 -0
  42. package/src/provider.tsx +61 -0
  43. package/src/reducer.ts +161 -0
  44. package/src/selection/range.ts +41 -0
  45. package/src/selection/selection-hook.ts +24 -0
  46. package/src/selection/selection.test.ts +111 -0
  47. package/src/selection/selection.ts +186 -0
  48. package/src/tree-api-hook.ts +34 -0
  49. package/src/tree-api.ts +129 -0
  50. package/src/types.ts +147 -0
  51. package/src/utils.ts +35 -0
  52. package/tsconfig.json +28 -0
  53. package/README.md +0 -197
  54. package/dist/lib/components/drop-cursor.js +0 -53
  55. package/dist/lib/components/preview.js +0 -91
  56. package/dist/lib/components/row.js +0 -122
  57. package/dist/lib/components/tree.js +0 -76
  58. package/dist/lib/context.js +0 -57
  59. package/dist/lib/data/enrich-tree.js +0 -48
  60. package/dist/lib/data/flatten-tree.js +0 -20
  61. package/dist/lib/data/make-tree.d.ts +0 -5
  62. package/dist/lib/data/make-tree.js +0 -40
  63. package/dist/lib/data/visible-nodes-hook.d.ts +0 -2
  64. package/dist/lib/data/visible-nodes-hook.js +0 -19
  65. package/dist/lib/dnd/compute-drop.d.ts +0 -24
  66. package/dist/lib/dnd/compute-drop.js +0 -113
  67. package/dist/lib/dnd/drag-hook.js +0 -36
  68. package/dist/lib/dnd/drop-hook.js +0 -60
  69. package/dist/lib/dnd/measure-hover.d.ts +0 -8
  70. package/dist/lib/dnd/measure-hover.js +0 -21
  71. package/dist/lib/dnd/outer-drop-hook.js +0 -50
  72. package/dist/lib/index.d.ts +0 -3
  73. package/dist/lib/index.js +0 -7
  74. package/dist/lib/provider.js +0 -44
  75. package/dist/lib/reducer.js +0 -149
  76. package/dist/lib/selection/range.js +0 -45
  77. package/dist/lib/selection/selection-hook.js +0 -24
  78. package/dist/lib/selection/selection.js +0 -192
  79. package/dist/lib/selection/selection.test.d.ts +0 -1
  80. package/dist/lib/selection/selection.test.js +0 -102
  81. package/dist/lib/tree-api-hook.js +0 -26
  82. package/dist/lib/tree-api.js +0 -130
  83. package/dist/lib/tree-monitor.d.ts +0 -15
  84. package/dist/lib/tree-monitor.js +0 -32
  85. package/dist/lib/types.js +0 -2
  86. package/dist/lib/utils.js +0 -31
@@ -0,0 +1,119 @@
1
+ import React, { useCallback, useMemo, useRef } from "react";
2
+ import {
3
+ useCursorParentId,
4
+ useEditingId,
5
+ useIsCursorOverFolder,
6
+ useIsSelected,
7
+ useStaticContext,
8
+ } from "../context";
9
+ import { useDragHook } from "../dnd/drag-hook";
10
+ import { useDropHook } from "../dnd/drop-hook";
11
+
12
+ type Props = {
13
+ style: React.CSSProperties;
14
+ index: number;
15
+ };
16
+
17
+ export const Row = React.memo(function Row({ index, style }: Props) {
18
+ const tree = useStaticContext();
19
+ const selected = useIsSelected();
20
+ const node = tree.api.visibleNodes[index];
21
+ const next = tree.api.visibleNodes[index + 1] || null;
22
+ const prev = tree.api.visibleNodes[index - 1] || null;
23
+ const cursorParentId = useCursorParentId();
24
+ const cursorOverFolder = useIsCursorOverFolder();
25
+ const el = useRef<HTMLDivElement | null>(null);
26
+ const [{ isDragging }, dragRef] = useDragHook(node);
27
+ const [, dropRef] = useDropHook(el, node, prev, next);
28
+ const isEditing = node.id === useEditingId();
29
+ const isSelected = selected(index);
30
+ const nextSelected = next && selected(index + 1);
31
+ const prevSelected = prev && selected(index - 1);
32
+ const isHoveringOverChild = node.id === cursorParentId;
33
+ const isOverFolder = node.id === cursorParentId && cursorOverFolder;
34
+ const isOpen = node.isOpen;
35
+ const indent = tree.indent * node.level;
36
+ const state = useMemo(() => {
37
+ return {
38
+ isEditing,
39
+ isDragging,
40
+ isFirstOfSelected: isSelected && !prevSelected,
41
+ isLastOfSelected: isSelected && !nextSelected,
42
+ isSelected,
43
+ isHoveringOverChild,
44
+ isOpen,
45
+ isOverFolder,
46
+ };
47
+ }, [
48
+ isEditing,
49
+ isSelected,
50
+ prevSelected,
51
+ nextSelected,
52
+ isHoveringOverChild,
53
+ isOpen,
54
+ isDragging,
55
+ isOverFolder,
56
+ ]);
57
+ if (isSelected) {
58
+ console.log({id: node.id, state})
59
+ }
60
+
61
+ const ref = useCallback(
62
+ (n: HTMLDivElement | null) => {
63
+ el.current = n;
64
+ dragRef(dropRef(n));
65
+ },
66
+ [dragRef, dropRef]
67
+ );
68
+
69
+ const styles = useMemo(
70
+ () => ({
71
+ row: { ...style },
72
+ indent: { paddingLeft: indent },
73
+ }),
74
+ [indent, style]
75
+ );
76
+
77
+ const handlers = useMemo(() => {
78
+ return {
79
+ select: (e: React.MouseEvent, selectOnClick: boolean = true) => {
80
+ if (node.rowIndex === null) return;
81
+ if (selectOnClick || e.metaKey || e.shiftKey) {
82
+ tree.api.select(node.rowIndex, e.metaKey, e.shiftKey);
83
+ } else {
84
+ tree.api.select(null, false, false);
85
+ }
86
+ },
87
+ toggle: (e: React.MouseEvent) => {
88
+ e.stopPropagation();
89
+ tree.onToggle(node.id, !node.isOpen);
90
+ },
91
+ edit: () => {
92
+ tree.api.edit(node.id);
93
+ },
94
+ submit: (name: string) => {
95
+ if (name.trim()) tree.onEdit(node.id, name);
96
+ tree.api.edit(null);
97
+ },
98
+ reset: () => {
99
+ tree.api.edit(null);
100
+ },
101
+ };
102
+ }, [tree, node]);
103
+
104
+ const Renderer = useMemo(() => {
105
+ return React.memo(tree.renderer);
106
+ }, [tree.renderer]);
107
+
108
+ return (
109
+ <Renderer
110
+ innerRef={ref}
111
+ data={node.model}
112
+ styles={styles}
113
+ state={state}
114
+ handlers={handlers}
115
+ preview={false}
116
+ tree={tree.api}
117
+ />
118
+ );
119
+ });
@@ -0,0 +1,118 @@
1
+ import { forwardRef, MouseEventHandler, ReactElement, useMemo, useRef } from "react";
2
+ import { DndProvider } from "react-dnd";
3
+ import { HTML5Backend } from "react-dnd-html5-backend";
4
+ import { FixedSizeList } from "react-window";
5
+ import { useStaticContext } from "../context";
6
+ import { enrichTree } from "../data/enrich-tree";
7
+ import { useOuterDrop } from "../dnd/outer-drop-hook";
8
+ import { TreeViewProvider } from "../provider";
9
+ import { TreeApi } from "../tree-api";
10
+ import { IdObj, Node, TreeProps } from "../types";
11
+ import { noop } from "../utils";
12
+ import { DropCursor } from "./drop-cursor";
13
+ import { Preview } from "./preview";
14
+ import { Row } from "./row";
15
+
16
+ const OuterElement = forwardRef(function Outer(
17
+ props: React.HTMLProps<HTMLDivElement>,
18
+ ref
19
+ ) {
20
+ const { children, ...rest } = props;
21
+ const tree = useStaticContext();
22
+ return (
23
+ // @ts-ignore
24
+ <div ref={ref} {...rest} onClick={tree.onClick}>
25
+ <div
26
+ style={{
27
+ height: tree.api.visibleNodes.length * tree.rowHeight,
28
+ width: "100%",
29
+ overflow: "hidden",
30
+ position: "absolute",
31
+ left: "0",
32
+ right: "0",
33
+ }}
34
+ >
35
+ <DropCursor />
36
+ </div>
37
+ {children}
38
+ </div>
39
+ );
40
+ });
41
+
42
+ function List(props: { className?: string}) {
43
+ const tree = useStaticContext();
44
+ return (
45
+ <div style={{ height: tree.height, width: tree.width, overflow: "hidden" }}>
46
+ <FixedSizeList
47
+ className={props.className}
48
+ outerRef={tree.listEl}
49
+ itemCount={tree.api.visibleNodes.length}
50
+ height={tree.height}
51
+ width={tree.width}
52
+ itemSize={tree.rowHeight}
53
+ itemKey={(index) => tree.api.visibleNodes[index]?.id || index}
54
+ outerElementType={OuterElement}
55
+ // @ts-ignore
56
+ ref={tree.list}
57
+ >
58
+ {Row}
59
+ </FixedSizeList>
60
+ </div>
61
+ );
62
+ }
63
+
64
+ function OuterDrop(props: { children: ReactElement }) {
65
+ useOuterDrop();
66
+ return props.children;
67
+ }
68
+
69
+ export const Tree = forwardRef(function Tree<T extends IdObj>(
70
+ props: TreeProps<T>,
71
+ ref: React.Ref<TreeApi<T>>
72
+ ) {
73
+ const root = useMemo<Node<T>>(
74
+ () =>
75
+ enrichTree<T>(
76
+ props.data,
77
+ props.hideRoot,
78
+ props.getChildren,
79
+ props.isOpen,
80
+ props.disableDrag,
81
+ props.disableDrop,
82
+ props.openByDefault
83
+ ),
84
+ [
85
+ props.data,
86
+ props.hideRoot,
87
+ props.getChildren,
88
+ props.isOpen,
89
+ props.disableDrag,
90
+ props.disableDrop,
91
+ props.openByDefault,
92
+ ]
93
+ );
94
+ return (
95
+ <TreeViewProvider
96
+ imperativeHandle={ref}
97
+ root={root}
98
+ listEl={useRef<HTMLDivElement | null>(null)}
99
+ renderer={props.children}
100
+ width={props.width === undefined ? 300 : props.width}
101
+ height={props.height === undefined ? 500 : props.height}
102
+ indent={props.indent === undefined ? 24 : props.indent}
103
+ rowHeight={props.rowHeight === undefined ? 24 : props.rowHeight}
104
+ onMove={props.onMove || noop}
105
+ onToggle={props.onToggle || noop}
106
+ onEdit={props.onEdit || noop}
107
+ onClick={props.onClick}
108
+ onContextMenu={props.onContextMenu}
109
+ >
110
+ <DndProvider backend={HTML5Backend}>
111
+ <OuterDrop>
112
+ <List className={props.className}/>
113
+ </OuterDrop>
114
+ <Preview />
115
+ </DndProvider>
116
+ </TreeViewProvider>
117
+ );
118
+ });
@@ -0,0 +1,52 @@
1
+ import { createContext, useContext, useMemo } from "react";
2
+ import { Cursor } from "./dnd/compute-drop";
3
+ import { Selection } from "./selection/selection";
4
+ import { IdObj, SelectionState, StaticContext } from "./types";
5
+
6
+ export const CursorParentId = createContext<string | null>(null);
7
+ export function useCursorParentId() {
8
+ return useContext(CursorParentId);
9
+ }
10
+
11
+ export const IsCursorOverFolder = createContext<boolean>(false);
12
+ export function useIsCursorOverFolder() {
13
+ return useContext(IsCursorOverFolder);
14
+ }
15
+
16
+ export const CursorLocationContext = createContext<Cursor | null>(null);
17
+ export function useCursorLocation() {
18
+ return useContext(CursorLocationContext);
19
+ }
20
+
21
+ export const Static = createContext<StaticContext<IdObj> | null>(null);
22
+ export function useStaticContext() {
23
+ const value = useContext(Static);
24
+ if (!value) throw new Error("Context must be in a provider");
25
+ return value;
26
+ }
27
+
28
+ export const DispatchContext = createContext(null);
29
+ export function useDispatch() {
30
+ const dispatch = useContext(DispatchContext);
31
+ if (!dispatch) throw new Error("No dispatch provided");
32
+ return dispatch;
33
+ }
34
+
35
+ export const SelectionContext = createContext<SelectionState | null>(null);
36
+ export function useSelectedIds(): string[] {
37
+ const value = useContext(SelectionContext);
38
+ if (!value) throw new Error("Must provide selection context");
39
+ return value.ids;
40
+ }
41
+
42
+ export function useIsSelected(): (index: number | null) => boolean {
43
+ const value = useContext(SelectionContext);
44
+ if (!value) throw new Error("Must provide selection context");
45
+ const s = useMemo(() => Selection.parse(value.data, []), [value.data]);
46
+ return (i) => s.contains(i);
47
+ }
48
+
49
+ export const EditingIdContext = createContext<string | null>(null);
50
+ export function useEditingId(): string | null {
51
+ return useContext(EditingIdContext);
52
+ }
@@ -0,0 +1,74 @@
1
+ import { TreeProps, IdObj, Node } from "../types";
2
+
3
+ function createNode<T extends IdObj>(
4
+ model: T,
5
+ level: number,
6
+ parent: Node<T> | null,
7
+ children: Node<T>[] | null,
8
+ isOpen: boolean,
9
+ isDraggable: boolean,
10
+ isDroppable: boolean
11
+ ): Node<T> {
12
+ return {
13
+ id: model.id,
14
+ level,
15
+ parent,
16
+ children,
17
+ isOpen,
18
+ isDraggable,
19
+ isDroppable,
20
+ model,
21
+ rowIndex: null,
22
+ };
23
+ }
24
+
25
+ function access(obj: any, accessor: string | boolean | Function) {
26
+ if (typeof accessor === "boolean") {
27
+ return accessor;
28
+ }
29
+
30
+ if (typeof accessor === "string") {
31
+ return obj[accessor];
32
+ }
33
+
34
+ return accessor(obj);
35
+ }
36
+
37
+ export function enrichTree<T extends IdObj>(
38
+ model: T,
39
+ hideRoot: boolean = false,
40
+ getChildren: TreeProps<T>["getChildren"] = "children",
41
+ isOpen: TreeProps<T>["isOpen"] = "isOpen",
42
+ disableDrag: TreeProps<T>["disableDrag"] = false,
43
+ disableDrop: TreeProps<T>["disableDrop"] = false,
44
+ openByDefault: boolean = true
45
+ ): Node<T> {
46
+ function visitSelfAndChildren(
47
+ model: T,
48
+ level: number,
49
+ parent: Node<T> | null
50
+ ) {
51
+ const open = access(model, isOpen) as boolean;
52
+ const draggable = !access(model, disableDrag) as boolean;
53
+ const droppable = !access(model, disableDrop) as boolean;
54
+ const node = createNode<T>(
55
+ model,
56
+ level,
57
+ parent,
58
+ null,
59
+ open === undefined ? openByDefault : open,
60
+ draggable,
61
+ droppable
62
+ );
63
+ const children = access(model, getChildren) as T[];
64
+
65
+ if (children) {
66
+ node.children = children.map((child: T) =>
67
+ visitSelfAndChildren(child, level + 1, node)
68
+ );
69
+ }
70
+ return node;
71
+ }
72
+
73
+ return visitSelfAndChildren(model, hideRoot ? -1 : 0, null);
74
+ }
@@ -0,0 +1,17 @@
1
+ import { Node } from "../types";
2
+
3
+ export function flattenTree<T>(root: Node<T>): Node<T>[] {
4
+ const list: Node<T>[] = [];
5
+ let index = 0;
6
+ function collect(node: Node<T>) {
7
+ if (node.level >= 0) {
8
+ node.rowIndex = index++;
9
+ list.push(node);
10
+ }
11
+ if (node.isOpen) {
12
+ node.children?.forEach(collect);
13
+ }
14
+ }
15
+ collect(root);
16
+ return list;
17
+ }
@@ -0,0 +1,37 @@
1
+ // A function that turns a string of text into a tree
2
+ // Each line is a node
3
+ // The number of spaces at the beginning indicate the level
4
+
5
+ export function makeTree(string: string) {
6
+ const root = { id: "ROOT", name: "ROOT", isOpen: true };
7
+ let prevNode = root;
8
+ let prevLevel = -1;
9
+ let id = 1;
10
+ string.split("\n").forEach((line) => {
11
+ const name = line.trimStart();
12
+ const level = line.length - name.length;
13
+ const diff = level - prevLevel;
14
+ const node = { id: (id++).toString(), name, isOpen: false };
15
+ if (diff === 1) {
16
+ // First child
17
+ //@ts-ignore
18
+ node.parent = prevNode;
19
+ //@ts-ignore
20
+ prevNode.children = [node];
21
+ } else {
22
+ // Find the parent and go up
23
+ //@ts-ignore
24
+ let parent = prevNode.parent;
25
+ for (let i = diff; i < 0; i++) {
26
+ parent = parent.parent;
27
+ }
28
+ //@ts-ignore
29
+ node.parent = parent;
30
+ parent.children.push(node);
31
+ }
32
+ prevNode = node;
33
+ prevLevel = level;
34
+ });
35
+
36
+ return root;
37
+ }
@@ -0,0 +1,184 @@
1
+ import { XYCoord } from "react-dnd";
2
+ import { Node } from "../types";
3
+ import { bound, indexOf, isClosed, isFolder, isItem } from "../utils";
4
+ import { DropResult } from "./drop-hook";
5
+
6
+ function measureHover(el: HTMLElement, offset: XYCoord) {
7
+ const rect = el.getBoundingClientRect();
8
+ const x = offset.x - Math.round(rect.x);
9
+ const y = offset.y - Math.round(rect.y);
10
+ const height = rect.height;
11
+ const inTopHalf = y < height / 2;
12
+ const inBottomHalf = !inTopHalf;
13
+ const pad = height / 4;
14
+ const inMiddle = y > pad && y < height - pad;
15
+ const atTop = !inMiddle && inTopHalf;
16
+ const atBottom = !inMiddle && inBottomHalf;
17
+ return { x, inTopHalf, inBottomHalf, inMiddle, atTop, atBottom };
18
+ }
19
+
20
+ type HoverData = ReturnType<typeof measureHover>;
21
+
22
+ function getNodesAroundCursor(
23
+ node: Node | null,
24
+ prev: Node | null,
25
+ next: Node | null,
26
+ hover: HoverData
27
+ ): [Node | null, Node | null] {
28
+ if (!node) {
29
+ // We're hoving over the empty part of the list, not over an item,
30
+ // Put the cursor below the last item which is "prev"
31
+ return [prev, null];
32
+ }
33
+ if (isFolder(node)) {
34
+ if (hover.atTop) {
35
+ return [prev, node];
36
+ } else if (hover.inMiddle) {
37
+ return [node, node];
38
+ } else {
39
+ return [node, next];
40
+ }
41
+ } else {
42
+ if (hover.inTopHalf) {
43
+ return [prev, node];
44
+ } else {
45
+ return [node, next];
46
+ }
47
+ }
48
+ }
49
+
50
+ type Args = {
51
+ element: HTMLElement;
52
+ offset: XYCoord;
53
+ indent: number;
54
+ node: Node | null;
55
+ prevNode: Node | null;
56
+ nextNode: Node | null;
57
+ };
58
+
59
+ function getDropLevel(
60
+ hovering: HoverData,
61
+ aboveCursor: Node | null,
62
+ belowCursor: Node | null,
63
+ indent: number
64
+ ) {
65
+ const hoverLevel = Math.round(Math.max(0, hovering.x - indent) / indent);
66
+ let min, max;
67
+ if (!aboveCursor) {
68
+ max = 0;
69
+ min = 0;
70
+ } else if (!belowCursor) {
71
+ max = aboveCursor.level;
72
+ min = 0;
73
+ } else {
74
+ max = aboveCursor.level;
75
+ min = belowCursor.level;
76
+ }
77
+
78
+ return bound(hoverLevel, min, max);
79
+ }
80
+
81
+ function canDrop(above: Node | null, below: Node | null) {
82
+ if (!above) {
83
+ return true;
84
+ }
85
+
86
+ let n: Node | null = above;
87
+ if (isClosed(above) && above !== below) n = above.parent;
88
+
89
+ while (n) {
90
+ if (!n.isDroppable) return false;
91
+ n = n.parent;
92
+ }
93
+ return true;
94
+ }
95
+
96
+ export type ComputedDrop = {
97
+ drop: DropResult | null;
98
+ cursor: Cursor | null;
99
+ };
100
+
101
+ function dropAt(parentId: string | undefined, index: number): DropResult {
102
+ return { parentId: parentId || null, index };
103
+ }
104
+
105
+ function lineCursor(index: number, level: number) {
106
+ return {
107
+ type: "line" as "line",
108
+ index,
109
+ level,
110
+ };
111
+ }
112
+
113
+ function noCursor() {
114
+ return {
115
+ type: "none" as "none",
116
+ };
117
+ }
118
+
119
+ function highlightCursor(id: string) {
120
+ return {
121
+ type: "highlight" as "highlight",
122
+ id,
123
+ };
124
+ }
125
+
126
+ function walkUpFrom(node: Node, level: number) {
127
+ let drop = node;
128
+ while (drop.parent && drop.level > level) {
129
+ drop = drop.parent;
130
+ }
131
+ const parentId = drop.parent?.id || null;
132
+ const index = indexOf(drop) + 1;
133
+ return { parentId, index };
134
+ }
135
+
136
+ export type LineCursor = ReturnType<typeof lineCursor>;
137
+ export type NoCursor = ReturnType<typeof noCursor>;
138
+ export type HighlightCursor = ReturnType<typeof highlightCursor>;
139
+ export type Cursor = LineCursor | NoCursor | HighlightCursor;
140
+
141
+ /**
142
+ * This is the most complex, tricky function in the whole repo.
143
+ * It could be simplified and made more understandable.
144
+ */
145
+ export function computeDrop(args: Args): ComputedDrop {
146
+ const hover = measureHover(args.element, args.offset);
147
+ const { node, nextNode, prevNode } = args;
148
+ const [above, below] = getNodesAroundCursor(node, prevNode, nextNode, hover);
149
+
150
+ if (!canDrop(above, below)) {
151
+ return { drop: null, cursor: noCursor() };
152
+ }
153
+
154
+ /* Hovering over the middle of a folder */
155
+ if (node && isFolder(node) && hover.inMiddle) {
156
+ return {
157
+ drop: dropAt(node.id, 0),
158
+ cursor: highlightCursor(node.id),
159
+ };
160
+ }
161
+
162
+ /* At the top of the list */
163
+ if (!above) {
164
+ return {
165
+ drop: dropAt(below?.parent?.id, 0),
166
+ cursor: lineCursor(0, 0),
167
+ };
168
+ }
169
+
170
+ /* The above node is an item or a closed folder */
171
+ if (isItem(above) || isClosed(above)) {
172
+ const level = getDropLevel(hover, above, below, args.indent);
173
+ return {
174
+ drop: walkUpFrom(above, level),
175
+ cursor: lineCursor(above.rowIndex! + 1, level),
176
+ };
177
+ }
178
+
179
+ /* The above node is an open folder */
180
+ return {
181
+ drop: dropAt(above?.id, 0),
182
+ cursor: lineCursor(above.rowIndex! + 1, above.level + 1),
183
+ };
184
+ }
@@ -0,0 +1,48 @@
1
+ import { useEffect } from "react";
2
+ import { ConnectDragSource, useDrag } from "react-dnd";
3
+ import { getEmptyImage } from "react-dnd-html5-backend";
4
+ import { useIsSelected, useSelectedIds, useStaticContext } from "../context";
5
+ import { DragItem, Node } from "../types";
6
+ import { DropResult } from "./drop-hook";
7
+
8
+ type CollectedProps = { isDragging: boolean };
9
+
10
+ export function useDragHook(
11
+ node: Node
12
+ ): [{ isDragging: boolean }, ConnectDragSource] {
13
+ const tree = useStaticContext();
14
+ const isSelected = useIsSelected();
15
+ const ids = useSelectedIds();
16
+ const [{ isDragging }, ref, preview] = useDrag<
17
+ DragItem,
18
+ DropResult,
19
+ CollectedProps
20
+ >(
21
+ () => ({
22
+ canDrag: () => node.isDraggable,
23
+ type: "NODE",
24
+ item: () => ({
25
+ id: node.id,
26
+ dragIds: isSelected(node.rowIndex) ? ids : [node.id],
27
+ }),
28
+ collect: (m) => ({
29
+ isDragging: m.isDragging(),
30
+ }),
31
+ end: (item, monitor) => {
32
+ tree.api.hideCursor();
33
+ const drop = monitor.getDropResult();
34
+ if (drop && drop.parentId) {
35
+ tree.onMove(item.dragIds, drop.parentId, drop.index);
36
+ tree.onToggle(drop.parentId, true);
37
+ }
38
+ },
39
+ }),
40
+ [ids, node]
41
+ );
42
+
43
+ useEffect(() => {
44
+ preview(getEmptyImage());
45
+ }, [preview]);
46
+
47
+ return [{ isDragging }, ref];
48
+ }