react-arborist 1.1.0 → 2.0.0-rc
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.
- package/dist/components/{drop-cursor.d.ts → cursor.d.ts} +0 -0
- package/dist/components/default-container.d.ts +2 -0
- package/dist/components/default-cursor.d.ts +3 -0
- package/dist/components/default-drag-preview.d.ts +3 -0
- package/dist/components/default-node.d.ts +4 -0
- package/dist/components/default-row.d.ts +4 -0
- package/dist/components/drag-preview-container.d.ts +2 -0
- package/dist/components/list-inner-element.d.ts +2 -0
- package/dist/components/list-outer-element.d.ts +2 -0
- package/dist/components/outer-drop.d.ts +4 -0
- package/dist/components/provider.d.ts +11 -0
- package/dist/components/row-container.d.ts +8 -0
- package/dist/components/tree-container.d.ts +2 -0
- package/dist/components/tree.d.ts +5 -4
- package/dist/context.d.ts +23 -18
- package/dist/data/create-index.d.ts +5 -0
- package/dist/data/create-list.d.ts +4 -0
- package/dist/data/create-root.d.ts +5 -0
- package/dist/data/flatten-tree.d.ts +4 -2
- package/dist/data/simple-tree.d.ts +43 -0
- package/dist/dnd/compute-drop.d.ts +4 -4
- package/dist/dnd/drag-hook.d.ts +3 -4
- package/dist/dnd/drop-hook.d.ts +2 -3
- package/dist/hooks/use-fresh-node.d.ts +2 -0
- package/dist/hooks/use-simple-tree.d.ts +13 -0
- package/dist/hooks/use-uncontrolled-tree.d.ts +24 -0
- package/dist/hooks/use-validated-props.d.ts +3 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.js +2093 -1184
- package/dist/index.js.map +1 -1
- package/dist/interfaces/node-api.d.ts +67 -0
- package/dist/interfaces/tree-api.d.ts +112 -0
- package/dist/module.js +2082 -1192
- package/dist/module.js.map +1 -1
- package/dist/state/dnd-slice.d.ts +20 -0
- package/dist/state/drag-slice.d.ts +7 -0
- package/dist/state/edit-slice.d.ts +8 -0
- package/dist/state/focus-slice.d.ts +12 -0
- package/dist/state/initial.d.ts +3 -0
- package/dist/state/open-slice.d.ts +30 -0
- package/dist/state/root-reducer.d.ts +13 -0
- package/dist/state/selection-slice.d.ts +36 -0
- package/dist/types/dnd.d.ts +9 -0
- package/dist/types/handlers.d.ts +24 -0
- package/dist/types/renderers.d.ts +30 -0
- package/dist/types/state.d.ts +2 -0
- package/dist/types/tree-props.d.ts +43 -0
- package/dist/types/utils.d.ts +21 -0
- package/dist/utils/props.d.ts +3 -0
- package/dist/utils.d.ts +15 -6
- package/package.json +10 -7
- package/src/components/cursor.tsx +15 -0
- package/src/components/default-container.tsx +229 -0
- package/src/components/{drop-cursor.tsx → default-cursor.tsx} +15 -20
- package/src/components/default-drag-preview.tsx +92 -0
- package/src/components/default-node.tsx +15 -0
- package/src/components/default-row.tsx +21 -0
- package/src/components/drag-preview-container.tsx +26 -0
- package/src/components/list-inner-element.tsx +22 -0
- package/src/components/list-outer-element.tsx +45 -0
- package/src/components/outer-drop.ts +7 -0
- package/src/components/provider.tsx +97 -0
- package/src/components/row-container.tsx +82 -0
- package/src/components/tree-container.tsx +13 -0
- package/src/components/tree.tsx +17 -126
- package/src/context.ts +36 -0
- package/src/data/create-index.ts +9 -0
- package/src/data/create-list.ts +56 -0
- package/src/data/create-root.ts +53 -0
- package/src/data/simple-tree.ts +103 -0
- package/src/dnd/compute-drop.ts +16 -16
- package/src/dnd/drag-hook.ts +28 -23
- package/src/dnd/drop-hook.ts +35 -21
- package/src/dnd/outer-drop-hook.ts +6 -6
- package/src/hooks/use-fresh-node.ts +16 -0
- package/src/hooks/use-simple-tree.ts +55 -0
- package/src/hooks/use-validated-props.ts +35 -0
- package/src/index.ts +9 -5
- package/src/interfaces/node-api.ts +187 -0
- package/src/interfaces/tree-api.ts +552 -0
- package/src/state/dnd-slice.ts +36 -0
- package/src/state/drag-slice.ts +31 -0
- package/src/state/edit-slice.ts +19 -0
- package/src/state/focus-slice.ts +28 -0
- package/src/state/initial.ts +14 -0
- package/src/state/open-slice.ts +53 -0
- package/src/state/root-reducer.ts +21 -0
- package/src/state/selection-slice.ts +75 -0
- package/src/types/dnd.ts +10 -0
- package/src/types/handlers.ts +24 -0
- package/src/types/renderers.ts +34 -0
- package/src/types/state.ts +3 -0
- package/src/types/tree-props.ts +63 -0
- package/src/types/utils.ts +26 -0
- package/src/utils/props.ts +8 -0
- package/src/utils.ts +125 -11
- package/README.md +0 -220
- package/dist/components/preview.d.ts +0 -2
- package/dist/components/row.d.ts +0 -7
- package/dist/data/enrich-tree.d.ts +0 -2
- package/dist/provider.d.ts +0 -3
- package/dist/reducer.d.ts +0 -46
- package/dist/selection/range.d.ts +0 -13
- package/dist/selection/selection-hook.d.ts +0 -3
- package/dist/selection/selection.d.ts +0 -33
- package/dist/tree-api-hook.d.ts +0 -6
- package/dist/tree-api.d.ts +0 -34
- package/dist/types.d.ts +0 -131
- package/src/components/preview.tsx +0 -108
- package/src/components/row.tsx +0 -114
- package/src/context.tsx +0 -52
- package/src/data/enrich-tree.ts +0 -74
- package/src/data/flatten-tree.ts +0 -17
- package/src/provider.tsx +0 -61
- package/src/reducer.ts +0 -161
- package/src/selection/range.ts +0 -41
- package/src/selection/selection-hook.ts +0 -24
- package/src/selection/selection.test.ts +0 -111
- package/src/selection/selection.ts +0 -186
- package/src/tree-api-hook.ts +0 -34
- package/src/tree-api.ts +0 -156
- package/src/types.ts +0 -155
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { IdObj } from "../types/utils";
|
|
2
|
+
import { NodeApi } from "../interfaces/node-api";
|
|
3
|
+
import { TreeApi } from "../interfaces/tree-api";
|
|
4
|
+
|
|
5
|
+
export const ROOT_ID = "__REACT_ARBORIST_INTERNAL_ROOT__";
|
|
6
|
+
|
|
7
|
+
export function createRoot<T extends IdObj>(tree: TreeApi<T>): NodeApi<T> {
|
|
8
|
+
function visitSelfAndChildren(
|
|
9
|
+
data: T,
|
|
10
|
+
level: number,
|
|
11
|
+
parent: NodeApi<T> | null
|
|
12
|
+
) {
|
|
13
|
+
const node = new NodeApi<T>({
|
|
14
|
+
tree,
|
|
15
|
+
data,
|
|
16
|
+
level,
|
|
17
|
+
parent,
|
|
18
|
+
id: data.id,
|
|
19
|
+
children: null,
|
|
20
|
+
isDraggable: tree.isDraggable(data),
|
|
21
|
+
isDroppable: tree.isDroppable(data),
|
|
22
|
+
rowIndex: null,
|
|
23
|
+
});
|
|
24
|
+
const children = tree.getChildren(data);
|
|
25
|
+
if (children) {
|
|
26
|
+
node.children = children.map((child: T) =>
|
|
27
|
+
visitSelfAndChildren(child, level + 1, node)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return node;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const root = new NodeApi<T>({
|
|
34
|
+
tree,
|
|
35
|
+
id: ROOT_ID,
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
data: { id: ROOT_ID },
|
|
38
|
+
level: -1,
|
|
39
|
+
parent: null,
|
|
40
|
+
children: null,
|
|
41
|
+
isDraggable: true,
|
|
42
|
+
isDroppable: true,
|
|
43
|
+
rowIndex: null,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const data: T[] = tree.props.data ?? [];
|
|
47
|
+
|
|
48
|
+
root.children = data.map((child) => {
|
|
49
|
+
return visitSelfAndChildren(child, 0, root);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return root;
|
|
53
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
type IdObj = { id: string; children?: IdObj[] };
|
|
2
|
+
|
|
3
|
+
export class SimpleTree<T extends IdObj> {
|
|
4
|
+
root: SimpleNode<T>;
|
|
5
|
+
constructor(data: T[]) {
|
|
6
|
+
this.root = createRoot<T>(data);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get data() {
|
|
10
|
+
return this.root.children?.map((node) => node.data) ?? [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
create(args: { parentId: string | null; index: number; data: T }) {
|
|
14
|
+
const parent = args.parentId ? this.find(args.parentId) : this.root;
|
|
15
|
+
if (!parent) return null;
|
|
16
|
+
parent.addChild(args.data, args.index);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
move(args: { id: string; parentId: string | null; index: number }) {
|
|
20
|
+
const src = this.find(args.id);
|
|
21
|
+
const parent = args.parentId ? this.find(args.parentId) : this.root;
|
|
22
|
+
if (!src || !parent) return;
|
|
23
|
+
parent.addChild(src.data, args.index);
|
|
24
|
+
src.drop();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
update(args: { id: string; changes: Partial<T> }) {
|
|
28
|
+
const node = this.find(args.id);
|
|
29
|
+
if (node) node.update(args.changes);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
drop(args: { id: string }) {
|
|
33
|
+
const node = this.find(args.id);
|
|
34
|
+
if (node) node.drop();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
find(id: string, node: SimpleNode<T> = this.root): SimpleNode<T> | null {
|
|
38
|
+
if (!node) return null;
|
|
39
|
+
if (node.id === id) return node as SimpleNode<T>;
|
|
40
|
+
if (node.children) {
|
|
41
|
+
for (let child of node.children) {
|
|
42
|
+
const found = this.find(id, child);
|
|
43
|
+
if (found) return found;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createRoot<T extends IdObj>(data: T[]) {
|
|
52
|
+
const root = new SimpleNode<T>({ id: "ROOT" } as T, null);
|
|
53
|
+
root.children = data.map((d) => createNode(d as T, root));
|
|
54
|
+
return root;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createNode<T extends IdObj>(data: T, parent: SimpleNode<T>) {
|
|
58
|
+
const node = new SimpleNode<T>(data, parent);
|
|
59
|
+
if (data.children)
|
|
60
|
+
node.children = data.children.map((d) => createNode<T>(d as T, node));
|
|
61
|
+
return node;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class SimpleNode<T extends IdObj> {
|
|
65
|
+
id: string;
|
|
66
|
+
children?: SimpleNode<T>[];
|
|
67
|
+
constructor(public data: T, public parent: SimpleNode<T> | null) {
|
|
68
|
+
this.id = data.id;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
hasParent(): this is this & { parent: SimpleNode<T> } {
|
|
72
|
+
return !!this.parent;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get childIndex(): number {
|
|
76
|
+
return this.hasParent() ? this.parent.children!.indexOf(this) : -1;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
addChild(data: T, index: number) {
|
|
80
|
+
const node = createNode(data, this);
|
|
81
|
+
this.children = this.children ?? [];
|
|
82
|
+
this.children.splice(index, 0, node);
|
|
83
|
+
this.data.children = this.data.children ?? [];
|
|
84
|
+
this.data.children.splice(index, 0, data);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
removeChild(index: number) {
|
|
88
|
+
this.children?.splice(index, 1);
|
|
89
|
+
this.data.children?.splice(index, 1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
update(changes: Partial<T>) {
|
|
93
|
+
if (this.hasParent()) {
|
|
94
|
+
const i = this.childIndex;
|
|
95
|
+
this.parent.addChild({ ...this.data, ...changes }, i);
|
|
96
|
+
this.drop();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
drop() {
|
|
101
|
+
if (this.hasParent()) this.parent.removeChild(this.childIndex);
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/dnd/compute-drop.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { XYCoord } from "react-dnd";
|
|
2
|
-
import {
|
|
3
|
-
import { bound, indexOf, isClosed,
|
|
2
|
+
import { NodeApi } from "../interfaces/node-api";
|
|
3
|
+
import { bound, indexOf, isClosed, isItem } from "../utils";
|
|
4
4
|
import { DropResult } from "./drop-hook";
|
|
5
5
|
|
|
6
6
|
function measureHover(el: HTMLElement, offset: XYCoord) {
|
|
@@ -20,17 +20,17 @@ function measureHover(el: HTMLElement, offset: XYCoord) {
|
|
|
20
20
|
type HoverData = ReturnType<typeof measureHover>;
|
|
21
21
|
|
|
22
22
|
function getNodesAroundCursor(
|
|
23
|
-
node:
|
|
24
|
-
prev:
|
|
25
|
-
next:
|
|
23
|
+
node: NodeApi | null,
|
|
24
|
+
prev: NodeApi | null,
|
|
25
|
+
next: NodeApi | null,
|
|
26
26
|
hover: HoverData
|
|
27
|
-
): [
|
|
27
|
+
): [NodeApi | null, NodeApi | null] {
|
|
28
28
|
if (!node) {
|
|
29
29
|
// We're hoving over the empty part of the list, not over an item,
|
|
30
30
|
// Put the cursor below the last item which is "prev"
|
|
31
31
|
return [prev, null];
|
|
32
32
|
}
|
|
33
|
-
if (
|
|
33
|
+
if (node.isInternal) {
|
|
34
34
|
if (hover.atTop) {
|
|
35
35
|
return [prev, node];
|
|
36
36
|
} else if (hover.inMiddle) {
|
|
@@ -51,15 +51,15 @@ type Args = {
|
|
|
51
51
|
element: HTMLElement;
|
|
52
52
|
offset: XYCoord;
|
|
53
53
|
indent: number;
|
|
54
|
-
node:
|
|
55
|
-
prevNode:
|
|
56
|
-
nextNode:
|
|
54
|
+
node: NodeApi | null;
|
|
55
|
+
prevNode: NodeApi | null;
|
|
56
|
+
nextNode: NodeApi | null;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
function getDropLevel(
|
|
60
60
|
hovering: HoverData,
|
|
61
|
-
aboveCursor:
|
|
62
|
-
belowCursor:
|
|
61
|
+
aboveCursor: NodeApi | null,
|
|
62
|
+
belowCursor: NodeApi | null,
|
|
63
63
|
indent: number
|
|
64
64
|
) {
|
|
65
65
|
const hoverLevel = Math.round(Math.max(0, hovering.x - indent) / indent);
|
|
@@ -78,12 +78,12 @@ function getDropLevel(
|
|
|
78
78
|
return bound(hoverLevel, min, max);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function canDrop(above:
|
|
81
|
+
function canDrop(above: NodeApi | null, below: NodeApi | null) {
|
|
82
82
|
if (!above) {
|
|
83
83
|
return true;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
let n:
|
|
86
|
+
let n: NodeApi | null = above;
|
|
87
87
|
if (isClosed(above) && above !== below) n = above.parent;
|
|
88
88
|
|
|
89
89
|
while (n) {
|
|
@@ -123,7 +123,7 @@ function highlightCursor(id: string) {
|
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
function walkUpFrom(node:
|
|
126
|
+
function walkUpFrom(node: NodeApi, level: number) {
|
|
127
127
|
let drop = node;
|
|
128
128
|
while (drop.parent && drop.level > level) {
|
|
129
129
|
drop = drop.parent;
|
|
@@ -152,7 +152,7 @@ export function computeDrop(args: Args): ComputedDrop {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
/* Hovering over the middle of a folder */
|
|
155
|
-
if (node &&
|
|
155
|
+
if (node && node.isInternal && hover.inMiddle) {
|
|
156
156
|
return {
|
|
157
157
|
drop: dropAt(node.id, 0),
|
|
158
158
|
cursor: highlightCursor(node.id),
|
package/src/dnd/drag-hook.ts
CHANGED
|
@@ -1,39 +1,44 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
2
|
import { ConnectDragSource, useDrag } from "react-dnd";
|
|
3
3
|
import { getEmptyImage } from "react-dnd-html5-backend";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { useTreeApi } from "../context";
|
|
5
|
+
import { NodeApi } from "../interfaces/node-api";
|
|
6
|
+
import { DragItem } from "../types/dnd";
|
|
7
|
+
import { IdObj } from "../types/utils";
|
|
6
8
|
import { DropResult } from "./drop-hook";
|
|
9
|
+
import { actions as dnd } from "../state/dnd-slice";
|
|
10
|
+
import { safeRun } from "../utils";
|
|
11
|
+
import { ROOT_ID } from "../data/create-root";
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const isSelected = useIsSelected();
|
|
15
|
-
const ids = useSelectedIds();
|
|
16
|
-
const [{ isDragging }, ref, preview] = useDrag<
|
|
17
|
-
DragItem,
|
|
18
|
-
DropResult,
|
|
19
|
-
CollectedProps
|
|
20
|
-
>(
|
|
13
|
+
export function useDragHook<T extends IdObj>(
|
|
14
|
+
node: NodeApi<T>
|
|
15
|
+
): ConnectDragSource {
|
|
16
|
+
const tree = useTreeApi();
|
|
17
|
+
const ids = tree.selectedIds;
|
|
18
|
+
const [_, ref, preview] = useDrag<DragItem, DropResult, void>(
|
|
21
19
|
() => ({
|
|
22
20
|
canDrag: () => node.isDraggable,
|
|
23
21
|
type: "NODE",
|
|
24
22
|
item: () => ({
|
|
25
23
|
id: node.id,
|
|
26
|
-
dragIds: isSelected(node.
|
|
27
|
-
}),
|
|
28
|
-
collect: (m) => ({
|
|
29
|
-
isDragging: m.isDragging(),
|
|
24
|
+
dragIds: tree.isSelected(node.id) ? Array.from(ids) : [node.id],
|
|
30
25
|
}),
|
|
26
|
+
start: () => {
|
|
27
|
+
tree.dispatch(dnd.dragStart(node.id));
|
|
28
|
+
},
|
|
31
29
|
end: (item, monitor) => {
|
|
32
|
-
tree.
|
|
30
|
+
tree.dispatch(dnd.dragEnd());
|
|
31
|
+
tree.hideCursor();
|
|
33
32
|
const drop = monitor.getDropResult();
|
|
33
|
+
// If they held down meta, we need to create a copy
|
|
34
|
+
// if (drop.dropEffect === "copy")
|
|
34
35
|
if (drop && drop.parentId) {
|
|
35
|
-
tree.onMove
|
|
36
|
-
|
|
36
|
+
safeRun(tree.props.onMove, {
|
|
37
|
+
dragIds: item.dragIds,
|
|
38
|
+
parentId: drop.parentId === ROOT_ID ? null : drop.parentId,
|
|
39
|
+
index: drop.index,
|
|
40
|
+
});
|
|
41
|
+
tree.open(drop.parentId);
|
|
37
42
|
}
|
|
38
43
|
},
|
|
39
44
|
}),
|
|
@@ -44,5 +49,5 @@ export function useDragHook(
|
|
|
44
49
|
preview(getEmptyImage());
|
|
45
50
|
}, [preview]);
|
|
46
51
|
|
|
47
|
-
return
|
|
52
|
+
return ref;
|
|
48
53
|
}
|
package/src/dnd/drop-hook.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { RefObject } from "react";
|
|
2
2
|
import { ConnectDropTarget, useDrop } from "react-dnd";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { useTreeApi } from "../context";
|
|
4
|
+
import { NodeApi } from "../interfaces/node-api";
|
|
5
|
+
import { DragItem } from "../types/dnd";
|
|
6
|
+
import { isDecendent } from "../utils";
|
|
6
7
|
import { computeDrop } from "./compute-drop";
|
|
7
8
|
|
|
8
9
|
export type DropResult = {
|
|
@@ -10,23 +11,34 @@ export type DropResult = {
|
|
|
10
11
|
index: number;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
export type CollectedProps = undefined;
|
|
14
|
-
|
|
15
14
|
export function useDropHook(
|
|
16
15
|
el: RefObject<HTMLElement | null>,
|
|
17
|
-
node:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const tree = useStaticContext();
|
|
22
|
-
return useDrop<DragItem, DropResult | null, CollectedProps>(
|
|
16
|
+
node: NodeApi<any>
|
|
17
|
+
): ConnectDropTarget {
|
|
18
|
+
const tree = useTreeApi();
|
|
19
|
+
const [_, dropRef] = useDrop<DragItem, DropResult | null, void>(
|
|
23
20
|
() => ({
|
|
24
21
|
accept: "NODE",
|
|
25
|
-
canDrop: (item) => {
|
|
22
|
+
canDrop: (item, m) => {
|
|
23
|
+
if (node.tree.isFiltered) return false;
|
|
24
|
+
const offset = m.getClientOffset();
|
|
25
|
+
if (!el.current || !offset) return false;
|
|
26
|
+
const { drop } = computeDrop({
|
|
27
|
+
element: el.current,
|
|
28
|
+
offset: offset,
|
|
29
|
+
indent: tree.indent,
|
|
30
|
+
node: node,
|
|
31
|
+
prevNode: node.prev,
|
|
32
|
+
nextNode: node.next,
|
|
33
|
+
});
|
|
34
|
+
if (!drop) return false;
|
|
35
|
+
const dropParent = tree.get(drop.parentId) ?? tree.root;
|
|
36
|
+
|
|
26
37
|
for (let id of item.dragIds) {
|
|
27
|
-
const drag = tree.
|
|
38
|
+
const drag = tree.get(id);
|
|
28
39
|
if (!drag) return false;
|
|
29
|
-
if (
|
|
40
|
+
if (!dropParent) return false;
|
|
41
|
+
if (drag.isInternal && isDecendent(dropParent, drag)) return false;
|
|
30
42
|
}
|
|
31
43
|
return true;
|
|
32
44
|
},
|
|
@@ -39,12 +51,12 @@ export function useDropHook(
|
|
|
39
51
|
offset: offset,
|
|
40
52
|
indent: tree.indent,
|
|
41
53
|
node: node,
|
|
42
|
-
prevNode: prev,
|
|
43
|
-
nextNode: next,
|
|
54
|
+
prevNode: node.prev,
|
|
55
|
+
nextNode: node.next,
|
|
44
56
|
});
|
|
45
|
-
if (cursor) tree.
|
|
57
|
+
if (cursor) tree.showCursor(cursor);
|
|
46
58
|
} else {
|
|
47
|
-
tree.
|
|
59
|
+
tree.hideCursor();
|
|
48
60
|
}
|
|
49
61
|
},
|
|
50
62
|
drop: (item, m): DropResult | undefined | null => {
|
|
@@ -55,12 +67,14 @@ export function useDropHook(
|
|
|
55
67
|
offset: offset,
|
|
56
68
|
indent: tree.indent,
|
|
57
69
|
node: node,
|
|
58
|
-
prevNode: prev,
|
|
59
|
-
nextNode: next,
|
|
70
|
+
prevNode: node.prev,
|
|
71
|
+
nextNode: node.next,
|
|
60
72
|
});
|
|
61
73
|
return drop;
|
|
62
74
|
},
|
|
63
75
|
}),
|
|
64
|
-
[node,
|
|
76
|
+
[node, el.current, tree.props]
|
|
65
77
|
);
|
|
78
|
+
|
|
79
|
+
return dropRef;
|
|
66
80
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useDrop } from "react-dnd";
|
|
2
|
-
import {
|
|
3
|
-
import { DragItem } from "../types";
|
|
2
|
+
import { useTreeApi } from "../context";
|
|
3
|
+
import { DragItem } from "../types/dnd";
|
|
4
4
|
import { computeDrop } from "./compute-drop";
|
|
5
5
|
import { DropResult } from "./drop-hook";
|
|
6
6
|
|
|
7
7
|
export function useOuterDrop() {
|
|
8
|
-
const tree =
|
|
8
|
+
const tree = useTreeApi();
|
|
9
9
|
|
|
10
10
|
// In case we drop an item at the bottom of the list
|
|
11
11
|
const [, drop] = useDrop<DragItem, DropResult | null, { isOver: boolean }>(
|
|
@@ -20,10 +20,10 @@ export function useOuterDrop() {
|
|
|
20
20
|
offset: offset,
|
|
21
21
|
indent: tree.indent,
|
|
22
22
|
node: null,
|
|
23
|
-
prevNode: tree.
|
|
23
|
+
prevNode: tree.visibleNodes[tree.visibleNodes.length - 1],
|
|
24
24
|
nextNode: null,
|
|
25
25
|
});
|
|
26
|
-
if (cursor) tree.
|
|
26
|
+
if (cursor) tree.showCursor(cursor);
|
|
27
27
|
},
|
|
28
28
|
canDrop: (item, m) => {
|
|
29
29
|
return m.isOver({ shallow: true });
|
|
@@ -37,7 +37,7 @@ export function useOuterDrop() {
|
|
|
37
37
|
offset: offset,
|
|
38
38
|
indent: tree.indent,
|
|
39
39
|
node: null,
|
|
40
|
-
prevNode: tree.
|
|
40
|
+
prevNode: tree.visibleNodes[tree.visibleNodes.length - 1],
|
|
41
41
|
nextNode: null,
|
|
42
42
|
});
|
|
43
43
|
return drop;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useTreeApi } from "../context";
|
|
3
|
+
import { IdObj } from "../types/utils";
|
|
4
|
+
|
|
5
|
+
export function useFreshNode<T extends IdObj>(index: number) {
|
|
6
|
+
const tree = useTreeApi<T>();
|
|
7
|
+
const original = tree.at(index);
|
|
8
|
+
if (!original) throw new Error(`Could not find node for index: ${index}`);
|
|
9
|
+
|
|
10
|
+
return useMemo(() => {
|
|
11
|
+
const fresh = original.clone();
|
|
12
|
+
tree.visibleNodes[index] = fresh; // sneaky
|
|
13
|
+
return fresh;
|
|
14
|
+
// Return a fresh instance if the state values change
|
|
15
|
+
}, [...Object.values(original.state), original]);
|
|
16
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
|
+
import { SimpleTree } from "../data/simple-tree";
|
|
3
|
+
import {
|
|
4
|
+
CreateHandler,
|
|
5
|
+
DeleteHandler,
|
|
6
|
+
MoveHandler,
|
|
7
|
+
RenameHandler,
|
|
8
|
+
} from "../types/handlers";
|
|
9
|
+
import { IdObj } from "../types/utils";
|
|
10
|
+
|
|
11
|
+
export type SimpleTreeData = {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
children?: SimpleTreeData[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
let nextId = 0;
|
|
18
|
+
|
|
19
|
+
export function useSimpleTree<T extends IdObj>(initialData: T[]) {
|
|
20
|
+
const [data, setData] = useState(initialData);
|
|
21
|
+
const tree = useMemo(() => new SimpleTree<T>(data), [data]);
|
|
22
|
+
|
|
23
|
+
const onMove: MoveHandler = (args: {
|
|
24
|
+
dragIds: string[];
|
|
25
|
+
parentId: null | string;
|
|
26
|
+
index: number;
|
|
27
|
+
}) => {
|
|
28
|
+
for (const id of args.dragIds) {
|
|
29
|
+
tree.move({ id, parentId: args.parentId, index: args.index });
|
|
30
|
+
}
|
|
31
|
+
setData(tree.data);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const onRename: RenameHandler = ({ name, id }) => {
|
|
35
|
+
tree.update({ id, changes: { name } as any });
|
|
36
|
+
setData(tree.data);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const onCreate: CreateHandler = ({ parentId, index, type }) => {
|
|
40
|
+
const data = { id: `simple-tree-id-${nextId++}`, name: "" } as any;
|
|
41
|
+
if (type === "internal") data.children = [];
|
|
42
|
+
tree.create({ parentId, index, data });
|
|
43
|
+
setData(tree.data);
|
|
44
|
+
return data;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const onDelete: DeleteHandler = (args: { ids: string[] }) => {
|
|
48
|
+
args.ids.forEach((id) => tree.drop({ id }));
|
|
49
|
+
setData(tree.data);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const controller = { onMove, onRename, onCreate, onDelete };
|
|
53
|
+
|
|
54
|
+
return [data, controller] as const;
|
|
55
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { TreeProps } from "../types/tree-props";
|
|
2
|
+
import { IdObj } from "../types/utils";
|
|
3
|
+
import { SimpleTreeData, useSimpleTree } from "./use-simple-tree";
|
|
4
|
+
|
|
5
|
+
export function useValidatedProps<T extends IdObj>(
|
|
6
|
+
props: TreeProps<T>
|
|
7
|
+
): TreeProps<T> {
|
|
8
|
+
if (props.initialData && props.data) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
`React Arborist Tree => Provide either a data or initialData prop, but not both.`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
if (
|
|
14
|
+
props.initialData &&
|
|
15
|
+
(props.onCreate || props.onDelete || props.onMove || props.onRename)
|
|
16
|
+
) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`React Arborist Tree => You passed the initialData prop along with a data handler.
|
|
19
|
+
Use the data prop if you want to provide your own handlers.`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
if (props.initialData) {
|
|
23
|
+
/**
|
|
24
|
+
* Let's break the rules of hooks here. If the initialData prop
|
|
25
|
+
* is provided, we will assume it will not change for the life of
|
|
26
|
+
* the component.
|
|
27
|
+
*
|
|
28
|
+
* We will provide the real data and the handlers to update it.
|
|
29
|
+
* */
|
|
30
|
+
const [data, controller] = useSimpleTree<T>(props.initialData);
|
|
31
|
+
return { ...props, ...controller, data };
|
|
32
|
+
} else {
|
|
33
|
+
return props;
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
1
|
+
/* The Public Api */
|
|
2
|
+
export { Tree } from "./components/tree";
|
|
3
|
+
export * from "./types/handlers";
|
|
4
|
+
export * from "./types/renderers";
|
|
5
|
+
export * from "./types/state";
|
|
6
|
+
export * from "./interfaces/node-api";
|
|
7
|
+
export * from "./interfaces/tree-api";
|
|
8
|
+
export * from "./data/simple-tree";
|
|
9
|
+
export * from "./hooks/use-simple-tree";
|