react-arborist 2.2.0 → 3.0.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.
- package/README.md +22 -10
- package/dist/components/list-inner-element.d.ts +1 -1
- package/dist/components/list-outer-element.d.ts +1 -1
- package/dist/components/tree.d.ts +1 -1
- package/dist/index.js +109 -141
- package/dist/index.js.map +1 -1
- package/dist/interfaces/node-api.d.ts +1 -2
- package/dist/interfaces/tree-api.d.ts +4 -1
- package/dist/module.js +109 -141
- package/dist/module.js.map +1 -1
- package/dist/state/dnd-slice.d.ts +10 -1
- package/dist/types/dnd.d.ts +0 -1
- package/dist/types/tree-props.d.ts +9 -3
- package/dist/types/utils.d.ts +0 -4
- package/package.json +1 -1
- package/src/components/default-container.tsx +6 -4
- package/src/data/create-root.ts +0 -2
- package/src/dnd/compute-drop.ts +0 -19
- package/src/dnd/drag-hook.ts +15 -18
- package/src/dnd/drop-hook.ts +8 -38
- package/src/dnd/outer-drop-hook.ts +13 -45
- package/src/interfaces/node-api.ts +4 -3
- package/src/interfaces/tree-api.ts +43 -4
- package/src/state/dnd-slice.ts +17 -6
- package/src/state/initial.ts +7 -1
- package/src/types/dnd.ts +0 -1
- package/src/types/tree-props.ts +13 -4
- package/src/types/utils.ts +0 -8
- package/src/utils.ts +1 -1
- package/tsconfig.json +1 -25
|
@@ -3,18 +3,27 @@ import { ActionTypes } from "../types/utils";
|
|
|
3
3
|
export declare type DndState = {
|
|
4
4
|
dragId: null | string;
|
|
5
5
|
cursor: Cursor;
|
|
6
|
+
dragIds: string[];
|
|
7
|
+
parentId: null | string;
|
|
8
|
+
index: number;
|
|
6
9
|
};
|
|
7
10
|
export declare const actions: {
|
|
8
11
|
cursor(cursor: Cursor): {
|
|
9
12
|
type: "DND_CURSOR";
|
|
10
13
|
cursor: Cursor;
|
|
11
14
|
};
|
|
12
|
-
dragStart(id: string): {
|
|
15
|
+
dragStart(id: string, dragIds: string[]): {
|
|
13
16
|
type: "DND_DRAG_START";
|
|
14
17
|
id: string;
|
|
18
|
+
dragIds: string[];
|
|
15
19
|
};
|
|
16
20
|
dragEnd(): {
|
|
17
21
|
type: "DND_DRAG_END";
|
|
18
22
|
};
|
|
23
|
+
hovering(parentId: string | null, index: number): {
|
|
24
|
+
type: "DND_HOVERING";
|
|
25
|
+
parentId: string | null;
|
|
26
|
+
index: number;
|
|
27
|
+
};
|
|
19
28
|
};
|
|
20
29
|
export declare function reducer(state: DndState | undefined, action: ActionTypes<typeof actions>): DndState;
|
package/dist/types/dnd.d.ts
CHANGED
|
@@ -18,19 +18,25 @@ export interface TreeProps<T> {
|
|
|
18
18
|
renderCursor?: ElementType<renderers.CursorProps>;
|
|
19
19
|
renderContainer?: ElementType<{}>;
|
|
20
20
|
rowHeight?: number;
|
|
21
|
+
overscanCount?: number;
|
|
21
22
|
width?: number | string;
|
|
22
23
|
height?: number;
|
|
23
24
|
indent?: number;
|
|
24
25
|
paddingTop?: number;
|
|
25
26
|
paddingBottom?: number;
|
|
26
27
|
padding?: number;
|
|
28
|
+
childrenAccessor?: string | ((d: T) => T[] | null);
|
|
29
|
+
idAccessor?: string | ((d: T) => string);
|
|
27
30
|
openByDefault?: boolean;
|
|
28
31
|
selectionFollowsFocus?: boolean;
|
|
29
32
|
disableMultiSelection?: boolean;
|
|
33
|
+
disableEdit?: string | boolean | BoolFunc<T>;
|
|
30
34
|
disableDrag?: string | boolean | BoolFunc<T>;
|
|
31
|
-
disableDrop?: string | boolean |
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
disableDrop?: string | boolean | ((args: {
|
|
36
|
+
parentNode: NodeApi<T>;
|
|
37
|
+
dragNodes: NodeApi<T>[];
|
|
38
|
+
index: number;
|
|
39
|
+
}) => boolean);
|
|
34
40
|
onActivate?: (node: NodeApi<T>) => void;
|
|
35
41
|
onSelect?: (nodes: NodeApi<T>[]) => void;
|
|
36
42
|
onScroll?: (props: ListOnScrollProps) => void;
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
1
|
import { AnyAction } from "redux";
|
|
3
2
|
import { NodeApi } from "../interfaces/node-api";
|
|
4
3
|
export interface IdObj {
|
|
5
4
|
id: string;
|
|
6
5
|
}
|
|
7
6
|
export declare type Identity = string | IdObj | null;
|
|
8
|
-
declare module "react" {
|
|
9
|
-
function forwardRef<T, P = {}>(render: (props: P, ref: React.Ref<T>) => React.ReactElement | null): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
|
|
10
|
-
}
|
|
11
7
|
export declare type BoolFunc<T> = (data: T) => boolean;
|
|
12
8
|
export declare type ActionTypes<Actions extends {
|
|
13
9
|
[name: string]: (...args: any[]) => AnyAction;
|
package/package.json
CHANGED
|
@@ -137,8 +137,7 @@ export function DefaultContainer() {
|
|
|
137
137
|
tree.selectAll();
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
|
-
if (e.key === "a" && !e.metaKey) {
|
|
141
|
-
if (!tree.props.onCreate) return;
|
|
140
|
+
if (e.key === "a" && !e.metaKey && tree.props.onCreate) {
|
|
142
141
|
tree.createLeaf();
|
|
143
142
|
return;
|
|
144
143
|
}
|
|
@@ -161,9 +160,11 @@ export function DefaultContainer() {
|
|
|
161
160
|
return;
|
|
162
161
|
}
|
|
163
162
|
if (e.key === "Enter") {
|
|
164
|
-
|
|
163
|
+
const node = tree.focusedNode;
|
|
164
|
+
if (!node) return;
|
|
165
|
+
if (!node.isEditable || !tree.props.onRename) return;
|
|
165
166
|
setTimeout(() => {
|
|
166
|
-
if (
|
|
167
|
+
if (node) tree.edit(node);
|
|
167
168
|
});
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
@@ -221,6 +222,7 @@ export function DefaultContainer() {
|
|
|
221
222
|
height={tree.height}
|
|
222
223
|
width={tree.width}
|
|
223
224
|
itemSize={tree.rowHeight}
|
|
225
|
+
overscanCount={tree.overscanCount}
|
|
224
226
|
itemKey={(index) => tree.visibleNodes[index]?.id || index}
|
|
225
227
|
outerElementType={ListOuterElement}
|
|
226
228
|
innerElementType={ListInnerElement}
|
package/src/data/create-root.ts
CHANGED
|
@@ -19,7 +19,6 @@ export function createRoot<T>(tree: TreeApi<T>): NodeApi<T> {
|
|
|
19
19
|
id,
|
|
20
20
|
children: null,
|
|
21
21
|
isDraggable: tree.isDraggable(data),
|
|
22
|
-
isDroppable: tree.isDroppable(data),
|
|
23
22
|
rowIndex: null,
|
|
24
23
|
});
|
|
25
24
|
const children = tree.accessChildren(data);
|
|
@@ -40,7 +39,6 @@ export function createRoot<T>(tree: TreeApi<T>): NodeApi<T> {
|
|
|
40
39
|
parent: null,
|
|
41
40
|
children: null,
|
|
42
41
|
isDraggable: true,
|
|
43
|
-
isDroppable: true,
|
|
44
42
|
rowIndex: null,
|
|
45
43
|
});
|
|
46
44
|
|
package/src/dnd/compute-drop.ts
CHANGED
|
@@ -78,21 +78,6 @@ function getDropLevel(
|
|
|
78
78
|
return bound(hoverLevel, min, max);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function canDrop(above: NodeApi | null, below: NodeApi | null) {
|
|
82
|
-
if (!above) {
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
let n: NodeApi | 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
81
|
export type ComputedDrop = {
|
|
97
82
|
drop: DropResult | null;
|
|
98
83
|
cursor: Cursor | null;
|
|
@@ -147,10 +132,6 @@ export function computeDrop(args: Args): ComputedDrop {
|
|
|
147
132
|
const { node, nextNode, prevNode } = args;
|
|
148
133
|
const [above, below] = getNodesAroundCursor(node, prevNode, nextNode, hover);
|
|
149
134
|
|
|
150
|
-
if (!canDrop(above, below)) {
|
|
151
|
-
return { drop: null, cursor: noCursor() };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
135
|
/* Hovering over the middle of a folder */
|
|
155
136
|
if (node && node.isInternal && hover.inMiddle) {
|
|
156
137
|
return {
|
package/src/dnd/drag-hook.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { getEmptyImage } from "react-dnd-html5-backend";
|
|
|
4
4
|
import { useTreeApi } from "../context";
|
|
5
5
|
import { NodeApi } from "../interfaces/node-api";
|
|
6
6
|
import { DragItem } from "../types/dnd";
|
|
7
|
-
import { IdObj } from "../types/utils";
|
|
8
7
|
import { DropResult } from "./drop-hook";
|
|
9
8
|
import { actions as dnd } from "../state/dnd-slice";
|
|
10
9
|
import { safeRun } from "../utils";
|
|
@@ -17,30 +16,28 @@ export function useDragHook<T>(node: NodeApi<T>): ConnectDragSource {
|
|
|
17
16
|
() => ({
|
|
18
17
|
canDrag: () => node.isDraggable,
|
|
19
18
|
type: "NODE",
|
|
20
|
-
item: () =>
|
|
21
|
-
|
|
22
|
-
dragIds
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
tree.dispatch(dnd.dragStart(node.id));
|
|
19
|
+
item: () => {
|
|
20
|
+
// This is fired once at the begging of a drag operation
|
|
21
|
+
const dragIds = tree.isSelected(node.id) ? Array.from(ids) : [node.id];
|
|
22
|
+
tree.dispatch(dnd.dragStart(node.id, dragIds));
|
|
23
|
+
return { id: node.id };
|
|
26
24
|
},
|
|
27
|
-
end: (
|
|
28
|
-
tree.dispatch(dnd.dragEnd());
|
|
25
|
+
end: () => {
|
|
29
26
|
tree.hideCursor();
|
|
30
|
-
|
|
27
|
+
let { parentId, index, dragIds } = tree.state.dnd;
|
|
31
28
|
// If they held down meta, we need to create a copy
|
|
32
29
|
// if (drop.dropEffect === "copy")
|
|
33
|
-
if (
|
|
34
|
-
const parentId = drop.parentId === ROOT_ID ? null : drop.parentId;
|
|
30
|
+
if (tree.canDrop()) {
|
|
35
31
|
safeRun(tree.props.onMove, {
|
|
36
|
-
dragIds
|
|
37
|
-
parentId,
|
|
38
|
-
index
|
|
39
|
-
dragNodes:
|
|
40
|
-
parentNode: tree.get(
|
|
32
|
+
dragIds,
|
|
33
|
+
parentId: parentId === ROOT_ID ? null : parentId,
|
|
34
|
+
index,
|
|
35
|
+
dragNodes: tree.dragNodes,
|
|
36
|
+
parentNode: tree.get(parentId),
|
|
41
37
|
});
|
|
42
|
-
tree.open(
|
|
38
|
+
tree.open(parentId);
|
|
43
39
|
}
|
|
40
|
+
tree.dispatch(dnd.dragEnd());
|
|
44
41
|
},
|
|
45
42
|
}),
|
|
46
43
|
[ids, node]
|
package/src/dnd/drop-hook.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { ConnectDropTarget, useDrop } from "react-dnd";
|
|
|
3
3
|
import { useTreeApi } from "../context";
|
|
4
4
|
import { NodeApi } from "../interfaces/node-api";
|
|
5
5
|
import { DragItem } from "../types/dnd";
|
|
6
|
-
import { isDecendent } from "../utils";
|
|
7
6
|
import { computeDrop } from "./compute-drop";
|
|
7
|
+
import { actions as dnd } from "../state/dnd-slice";
|
|
8
8
|
|
|
9
9
|
export type DropResult = {
|
|
10
10
|
parentId: string | null;
|
|
@@ -19,11 +19,11 @@ export function useDropHook(
|
|
|
19
19
|
const [_, dropRef] = useDrop<DragItem, DropResult | null, void>(
|
|
20
20
|
() => ({
|
|
21
21
|
accept: "NODE",
|
|
22
|
-
canDrop: (
|
|
23
|
-
|
|
22
|
+
canDrop: () => tree.canDrop(),
|
|
23
|
+
hover: (_item, m) => {
|
|
24
24
|
const offset = m.getClientOffset();
|
|
25
|
-
if (!el.current || !offset) return
|
|
26
|
-
const { drop } = computeDrop({
|
|
25
|
+
if (!el.current || !offset) return;
|
|
26
|
+
const { cursor, drop } = computeDrop({
|
|
27
27
|
element: el.current,
|
|
28
28
|
offset: offset,
|
|
29
29
|
indent: tree.indent,
|
|
@@ -31,46 +31,16 @@ export function useDropHook(
|
|
|
31
31
|
prevNode: node.prev,
|
|
32
32
|
nextNode: node.next,
|
|
33
33
|
});
|
|
34
|
-
if (
|
|
35
|
-
const dropParent = tree.get(drop.parentId) ?? tree.root;
|
|
34
|
+
if (drop) tree.dispatch(dnd.hovering(drop.parentId, drop.index));
|
|
36
35
|
|
|
37
|
-
for (let id of item.dragIds) {
|
|
38
|
-
const drag = tree.get(id);
|
|
39
|
-
if (!drag) return false;
|
|
40
|
-
if (!dropParent) return false;
|
|
41
|
-
if (drag.isInternal && isDecendent(dropParent, drag)) return false;
|
|
42
|
-
}
|
|
43
|
-
return true;
|
|
44
|
-
},
|
|
45
|
-
hover: (item, m) => {
|
|
46
36
|
if (m.canDrop()) {
|
|
47
|
-
const offset = m.getClientOffset();
|
|
48
|
-
if (!el.current || !offset) return;
|
|
49
|
-
const { cursor } = computeDrop({
|
|
50
|
-
element: el.current,
|
|
51
|
-
offset: offset,
|
|
52
|
-
indent: tree.indent,
|
|
53
|
-
node: node,
|
|
54
|
-
prevNode: node.prev,
|
|
55
|
-
nextNode: node.next,
|
|
56
|
-
});
|
|
57
37
|
if (cursor) tree.showCursor(cursor);
|
|
58
38
|
} else {
|
|
59
39
|
tree.hideCursor();
|
|
60
40
|
}
|
|
61
41
|
},
|
|
62
|
-
drop: (
|
|
63
|
-
|
|
64
|
-
if (!el.current || !offset) return;
|
|
65
|
-
const { drop } = computeDrop({
|
|
66
|
-
element: el.current,
|
|
67
|
-
offset: offset,
|
|
68
|
-
indent: tree.indent,
|
|
69
|
-
node: node,
|
|
70
|
-
prevNode: node.prev,
|
|
71
|
-
nextNode: node.next,
|
|
72
|
-
});
|
|
73
|
-
return drop;
|
|
42
|
+
drop: (_, m) => {
|
|
43
|
+
if (!m.canDrop()) return null;
|
|
74
44
|
},
|
|
75
45
|
}),
|
|
76
46
|
[node, el.current, tree.props]
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useDrop } from "react-dnd";
|
|
2
2
|
import { useTreeApi } from "../context";
|
|
3
3
|
import { DragItem } from "../types/dnd";
|
|
4
|
-
import { isDecendent } from "../utils";
|
|
5
4
|
import { computeDrop } from "./compute-drop";
|
|
6
5
|
import { DropResult } from "./drop-hook";
|
|
6
|
+
import { actions as dnd } from "../state/dnd-slice";
|
|
7
7
|
|
|
8
8
|
export function useOuterDrop() {
|
|
9
9
|
const tree = useTreeApi();
|
|
@@ -12,53 +12,15 @@ export function useOuterDrop() {
|
|
|
12
12
|
const [, drop] = useDrop<DragItem, DropResult | null, { isOver: boolean }>(
|
|
13
13
|
() => ({
|
|
14
14
|
accept: "NODE",
|
|
15
|
-
|
|
16
|
-
if (!m.isOver({ shallow: true })) return;
|
|
17
|
-
if (m.canDrop()) {
|
|
18
|
-
const offset = m.getClientOffset();
|
|
19
|
-
if (!tree.listEl.current || !offset) return;
|
|
20
|
-
const { cursor } = computeDrop({
|
|
21
|
-
element: tree.listEl.current,
|
|
22
|
-
offset: offset,
|
|
23
|
-
indent: tree.indent,
|
|
24
|
-
node: null,
|
|
25
|
-
prevNode: tree.visibleNodes[tree.visibleNodes.length - 1],
|
|
26
|
-
nextNode: null,
|
|
27
|
-
});
|
|
28
|
-
if (cursor) tree.showCursor(cursor);
|
|
29
|
-
} else {
|
|
30
|
-
tree.hideCursor();
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
canDrop: (item, m) => {
|
|
15
|
+
canDrop: (_item, m) => {
|
|
34
16
|
if (!m.isOver({ shallow: true })) return false;
|
|
35
|
-
|
|
36
|
-
const offset = m.getClientOffset();
|
|
37
|
-
if (!tree.listEl.current || !offset) return false;
|
|
38
|
-
const { drop } = computeDrop({
|
|
39
|
-
element: tree.listEl.current,
|
|
40
|
-
offset: offset,
|
|
41
|
-
indent: tree.indent,
|
|
42
|
-
node: null,
|
|
43
|
-
prevNode: tree.visibleNodes[tree.visibleNodes.length - 1],
|
|
44
|
-
nextNode: null,
|
|
45
|
-
});
|
|
46
|
-
if (!drop) return false;
|
|
47
|
-
const dropParent = tree.get(drop.parentId) ?? tree.root;
|
|
48
|
-
|
|
49
|
-
for (let id of item.dragIds) {
|
|
50
|
-
const drag = tree.get(id);
|
|
51
|
-
if (!drag) return false;
|
|
52
|
-
if (!dropParent) return false;
|
|
53
|
-
if (drag.isInternal && isDecendent(dropParent, drag)) return false;
|
|
54
|
-
}
|
|
55
|
-
return true;
|
|
17
|
+
return tree.canDrop();
|
|
56
18
|
},
|
|
57
|
-
|
|
58
|
-
if (m.
|
|
19
|
+
hover: (_item, m) => {
|
|
20
|
+
if (!m.isOver({ shallow: true })) return;
|
|
59
21
|
const offset = m.getClientOffset();
|
|
60
22
|
if (!tree.listEl.current || !offset) return;
|
|
61
|
-
const { drop } = computeDrop({
|
|
23
|
+
const { cursor, drop } = computeDrop({
|
|
62
24
|
element: tree.listEl.current,
|
|
63
25
|
offset: offset,
|
|
64
26
|
indent: tree.indent,
|
|
@@ -66,7 +28,13 @@ export function useOuterDrop() {
|
|
|
66
28
|
prevNode: tree.visibleNodes[tree.visibleNodes.length - 1],
|
|
67
29
|
nextNode: null,
|
|
68
30
|
});
|
|
69
|
-
|
|
31
|
+
if (drop) tree.dispatch(dnd.hovering(drop.parentId, drop.index));
|
|
32
|
+
|
|
33
|
+
if (m.canDrop()) {
|
|
34
|
+
if (cursor) tree.showCursor(cursor);
|
|
35
|
+
} else {
|
|
36
|
+
tree.hideCursor();
|
|
37
|
+
}
|
|
70
38
|
},
|
|
71
39
|
}),
|
|
72
40
|
[tree]
|
|
@@ -10,7 +10,6 @@ type Params<T> = {
|
|
|
10
10
|
children: NodeApi<T>[] | null;
|
|
11
11
|
parent: NodeApi<T> | null;
|
|
12
12
|
isDraggable: boolean;
|
|
13
|
-
isDroppable: boolean;
|
|
14
13
|
rowIndex: number | null;
|
|
15
14
|
tree: TreeApi<T>;
|
|
16
15
|
};
|
|
@@ -23,7 +22,6 @@ export class NodeApi<T = any> {
|
|
|
23
22
|
children: NodeApi<T>[] | null;
|
|
24
23
|
parent: NodeApi<T> | null;
|
|
25
24
|
isDraggable: boolean;
|
|
26
|
-
isDroppable: boolean;
|
|
27
25
|
rowIndex: number | null;
|
|
28
26
|
|
|
29
27
|
constructor(params: Params<T>) {
|
|
@@ -34,7 +32,6 @@ export class NodeApi<T = any> {
|
|
|
34
32
|
this.children = params.children;
|
|
35
33
|
this.parent = params.parent;
|
|
36
34
|
this.isDraggable = params.isDraggable;
|
|
37
|
-
this.isDroppable = params.isDroppable;
|
|
38
35
|
this.rowIndex = params.rowIndex;
|
|
39
36
|
}
|
|
40
37
|
|
|
@@ -58,6 +55,10 @@ export class NodeApi<T = any> {
|
|
|
58
55
|
return this.isLeaf ? false : !this.tree.isOpen(this.id);
|
|
59
56
|
}
|
|
60
57
|
|
|
58
|
+
get isEditable() {
|
|
59
|
+
return this.tree.isEditable(this.data);
|
|
60
|
+
}
|
|
61
|
+
|
|
61
62
|
get isEditing() {
|
|
62
63
|
return this.tree.editingId === this.id;
|
|
63
64
|
}
|
|
@@ -83,6 +83,10 @@ export class TreeApi<T> {
|
|
|
83
83
|
return this.props.rowHeight ?? 24;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
get overscanCount() {
|
|
87
|
+
return this.props.overscanCount ?? 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
86
90
|
get searchTerm() {
|
|
87
91
|
return (this.props.searchTerm || "").trim();
|
|
88
92
|
}
|
|
@@ -394,6 +398,41 @@ export class TreeApi<T> {
|
|
|
394
398
|
return this.state.dnd.cursor.type === "highlight";
|
|
395
399
|
}
|
|
396
400
|
|
|
401
|
+
get dragNodes() {
|
|
402
|
+
return this.state.dnd.dragIds
|
|
403
|
+
.map((id) => this.get(id))
|
|
404
|
+
.filter((n) => !!n) as NodeApi<T>[];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
canDrop() {
|
|
408
|
+
if (this.isFiltered) return false;
|
|
409
|
+
const parentNode = this.get(this.state.dnd.parentId) ?? this.root;
|
|
410
|
+
const dragNodes = this.dragNodes;
|
|
411
|
+
const check = this.props.disableDrop;
|
|
412
|
+
|
|
413
|
+
for (const drag of dragNodes) {
|
|
414
|
+
if (!drag) return false;
|
|
415
|
+
if (!parentNode) return false;
|
|
416
|
+
if (drag.isInternal && utils.isDecendent(parentNode, drag)) return false;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Allow the user to insert their own logic
|
|
420
|
+
if (typeof check == "function") {
|
|
421
|
+
return check({
|
|
422
|
+
parentNode,
|
|
423
|
+
dragNodes: this.dragNodes,
|
|
424
|
+
index: this.state.dnd.index,
|
|
425
|
+
});
|
|
426
|
+
} else if (typeof check == "string") {
|
|
427
|
+
// @ts-ignore
|
|
428
|
+
return !!parentNode.data[check];
|
|
429
|
+
} else if (typeof check === "boolean") {
|
|
430
|
+
return check;
|
|
431
|
+
} else {
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
397
436
|
hideCursor() {
|
|
398
437
|
this.dispatch(dnd.cursor({ type: "none" }));
|
|
399
438
|
}
|
|
@@ -525,13 +564,13 @@ export class TreeApi<T> {
|
|
|
525
564
|
}
|
|
526
565
|
}
|
|
527
566
|
|
|
528
|
-
|
|
529
|
-
const check = this.props.
|
|
567
|
+
isEditable(data: T) {
|
|
568
|
+
const check = this.props.disableEdit || (() => false);
|
|
530
569
|
return !utils.access(data, check) ?? true;
|
|
531
570
|
}
|
|
532
571
|
|
|
533
|
-
|
|
534
|
-
const check = this.props.
|
|
572
|
+
isDraggable(data: T) {
|
|
573
|
+
const check = this.props.disableDrag || (() => false);
|
|
535
574
|
return !utils.access(data, check) ?? true;
|
|
536
575
|
}
|
|
537
576
|
|
package/src/state/dnd-slice.ts
CHANGED
|
@@ -3,33 +3,44 @@ import { ActionTypes } from "../types/utils";
|
|
|
3
3
|
import { initialState } from "./initial";
|
|
4
4
|
|
|
5
5
|
/* Types */
|
|
6
|
-
export type DndState = {
|
|
6
|
+
export type DndState = {
|
|
7
|
+
dragId: null | string;
|
|
8
|
+
cursor: Cursor;
|
|
9
|
+
dragIds: string[];
|
|
10
|
+
parentId: null | string;
|
|
11
|
+
index: number;
|
|
12
|
+
};
|
|
7
13
|
|
|
8
14
|
/* Actions */
|
|
9
15
|
export const actions = {
|
|
10
16
|
cursor(cursor: Cursor) {
|
|
11
17
|
return { type: "DND_CURSOR" as const, cursor };
|
|
12
18
|
},
|
|
13
|
-
dragStart(id: string) {
|
|
14
|
-
return { type: "DND_DRAG_START" as const, id };
|
|
19
|
+
dragStart(id: string, dragIds: string[]) {
|
|
20
|
+
return { type: "DND_DRAG_START" as const, id, dragIds };
|
|
15
21
|
},
|
|
16
22
|
dragEnd() {
|
|
17
23
|
return { type: "DND_DRAG_END" as const };
|
|
18
24
|
},
|
|
25
|
+
hovering(parentId: string | null, index: number) {
|
|
26
|
+
return { type: "DND_HOVERING" as const, parentId, index };
|
|
27
|
+
},
|
|
19
28
|
};
|
|
20
29
|
|
|
21
30
|
/* Reducer */
|
|
22
31
|
export function reducer(
|
|
23
32
|
state: DndState = initialState()["dnd"],
|
|
24
33
|
action: ActionTypes<typeof actions>
|
|
25
|
-
) {
|
|
34
|
+
): DndState {
|
|
26
35
|
switch (action.type) {
|
|
27
36
|
case "DND_CURSOR":
|
|
28
37
|
return { ...state, cursor: action.cursor };
|
|
29
38
|
case "DND_DRAG_START":
|
|
30
|
-
return { ...state, dragId: action.id };
|
|
39
|
+
return { ...state, dragId: action.id, dragIds: action.dragIds };
|
|
31
40
|
case "DND_DRAG_END":
|
|
32
|
-
return
|
|
41
|
+
return initialState()["dnd"];
|
|
42
|
+
case "DND_HOVERING":
|
|
43
|
+
return { ...state, parentId: action.parentId, index: action.index };
|
|
33
44
|
default:
|
|
34
45
|
return state;
|
|
35
46
|
}
|
package/src/state/initial.ts
CHANGED
|
@@ -10,5 +10,11 @@ export const initialState = (props?: TreeProps<any>): RootState => ({
|
|
|
10
10
|
drag: { id: null, idWillReceiveDrop: null },
|
|
11
11
|
selection: { ids: new Set(), anchor: null, mostRecent: null },
|
|
12
12
|
},
|
|
13
|
-
dnd: {
|
|
13
|
+
dnd: {
|
|
14
|
+
cursor: { type: "none" },
|
|
15
|
+
dragId: null,
|
|
16
|
+
dragIds: [],
|
|
17
|
+
parentId: null,
|
|
18
|
+
index: -1,
|
|
19
|
+
},
|
|
14
20
|
});
|
package/src/types/dnd.ts
CHANGED
package/src/types/tree-props.ts
CHANGED
|
@@ -4,7 +4,7 @@ import * as renderers from "./renderers";
|
|
|
4
4
|
import { ElementType, MouseEventHandler } from "react";
|
|
5
5
|
import { ListOnScrollProps } from "react-window";
|
|
6
6
|
import { NodeApi } from "../interfaces/node-api";
|
|
7
|
-
import { OpenMap
|
|
7
|
+
import { OpenMap } from "../state/open-slice";
|
|
8
8
|
|
|
9
9
|
export interface TreeProps<T> {
|
|
10
10
|
/* Data Options */
|
|
@@ -26,6 +26,7 @@ export interface TreeProps<T> {
|
|
|
26
26
|
|
|
27
27
|
/* Sizes */
|
|
28
28
|
rowHeight?: number;
|
|
29
|
+
overscanCount?: number;
|
|
29
30
|
width?: number | string;
|
|
30
31
|
height?: number;
|
|
31
32
|
indent?: number;
|
|
@@ -34,13 +35,21 @@ export interface TreeProps<T> {
|
|
|
34
35
|
padding?: number;
|
|
35
36
|
|
|
36
37
|
/* Config */
|
|
38
|
+
childrenAccessor?: string | ((d: T) => T[] | null);
|
|
39
|
+
idAccessor?: string | ((d: T) => string);
|
|
37
40
|
openByDefault?: boolean;
|
|
38
41
|
selectionFollowsFocus?: boolean;
|
|
39
42
|
disableMultiSelection?: boolean;
|
|
43
|
+
disableEdit?: string | boolean | BoolFunc<T>;
|
|
40
44
|
disableDrag?: string | boolean | BoolFunc<T>;
|
|
41
|
-
disableDrop?:
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
disableDrop?:
|
|
46
|
+
| string
|
|
47
|
+
| boolean
|
|
48
|
+
| ((args: {
|
|
49
|
+
parentNode: NodeApi<T>;
|
|
50
|
+
dragNodes: NodeApi<T>[];
|
|
51
|
+
index: number;
|
|
52
|
+
}) => boolean);
|
|
44
53
|
|
|
45
54
|
/* Event Handlers */
|
|
46
55
|
onActivate?: (node: NodeApi<T>) => void;
|
package/src/types/utils.ts
CHANGED
|
@@ -7,14 +7,6 @@ export interface IdObj {
|
|
|
7
7
|
|
|
8
8
|
export type Identity = string | IdObj | null;
|
|
9
9
|
|
|
10
|
-
// Forward ref can't forward generics without this little re-declare
|
|
11
|
-
// https://fettblog.eu/typescript-react-generic-forward-refs/
|
|
12
|
-
declare module "react" {
|
|
13
|
-
function forwardRef<T, P = {}>(
|
|
14
|
-
render: (props: P, ref: React.Ref<T>) => React.ReactElement | null
|
|
15
|
-
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
10
|
export type BoolFunc<T> = (data: T) => boolean;
|
|
19
11
|
|
|
20
12
|
export type ActionTypes<
|
package/src/utils.ts
CHANGED
|
@@ -173,6 +173,6 @@ export function getInsertParentId(tree: TreeApi<any>) {
|
|
|
173
173
|
const focus = tree.focusedNode;
|
|
174
174
|
if (!focus) return null;
|
|
175
175
|
if (focus.isOpen) return focus.id;
|
|
176
|
-
if (focus.parent) return focus.parent.id;
|
|
176
|
+
if (focus.parent && !focus.parent.isRoot) return focus.parent.id;
|
|
177
177
|
return null;
|
|
178
178
|
}
|
package/tsconfig.json
CHANGED
|
@@ -1,28 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
2
3
|
"include": ["src/index.ts"],
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
|
5
|
-
|
|
6
|
-
/* Language and Environment */
|
|
7
|
-
"target": "ES5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
|
8
|
-
"jsx": "react-jsx", /* Specify what JSX code is generated. */
|
|
9
|
-
|
|
10
|
-
/* Modules */
|
|
11
|
-
"module": "CommonJS", /* Specify what module code is generated. */
|
|
12
|
-
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
|
13
|
-
|
|
14
|
-
/* Emit */
|
|
15
|
-
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
|
16
|
-
"emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
|
17
|
-
|
|
18
|
-
/* Interop Constraints */
|
|
19
|
-
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
|
|
20
|
-
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
|
21
|
-
|
|
22
|
-
/* Type Checking */
|
|
23
|
-
"strict": true, /* Enable all strict type-checking options. */
|
|
24
|
-
|
|
25
|
-
/* Completeness */
|
|
26
|
-
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
|
27
|
-
}
|
|
28
4
|
}
|