react-arborist 2.3.0 → 3.0.1
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 +21 -16
- 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 +5 -1
- package/dist/index.js +107 -141
- package/dist/index.js.map +1 -1
- package/dist/interfaces/node-api.d.ts +1 -2
- package/dist/interfaces/tree-api.d.ts +3 -1
- package/dist/module.js +107 -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 +8 -3
- package/dist/types/utils.d.ts +0 -4
- package/package.json +5 -2
- package/src/components/default-container.tsx +4 -2
- package/src/components/tree.tsx +6 -2
- 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 +39 -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 +12 -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
|
@@ -25,13 +25,18 @@ export interface TreeProps<T> {
|
|
|
25
25
|
paddingTop?: number;
|
|
26
26
|
paddingBottom?: number;
|
|
27
27
|
padding?: number;
|
|
28
|
+
childrenAccessor?: string | ((d: T) => T[] | null);
|
|
29
|
+
idAccessor?: string | ((d: T) => string);
|
|
28
30
|
openByDefault?: boolean;
|
|
29
31
|
selectionFollowsFocus?: boolean;
|
|
30
32
|
disableMultiSelection?: boolean;
|
|
33
|
+
disableEdit?: string | boolean | BoolFunc<T>;
|
|
31
34
|
disableDrag?: string | boolean | BoolFunc<T>;
|
|
32
|
-
disableDrop?: string | boolean |
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
disableDrop?: string | boolean | ((args: {
|
|
36
|
+
parentNode: NodeApi<T>;
|
|
37
|
+
dragNodes: NodeApi<T>[];
|
|
38
|
+
index: number;
|
|
39
|
+
}) => boolean);
|
|
35
40
|
onActivate?: (node: NodeApi<T>) => void;
|
|
36
41
|
onSelect?: (nodes: NodeApi<T>[]) => void;
|
|
37
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
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-arborist",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"source": "src/index.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/module.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
|
-
"repository":
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/brimdata/react-arborist.git"
|
|
12
|
+
},
|
|
10
13
|
"homepage": "https://react-arborist.netlify.app",
|
|
11
14
|
"bugs": "https://github.com/brimdata/react-arborist/issues",
|
|
12
15
|
"keywords": [
|
|
@@ -160,9 +160,11 @@ export function DefaultContainer() {
|
|
|
160
160
|
return;
|
|
161
161
|
}
|
|
162
162
|
if (e.key === "Enter") {
|
|
163
|
-
|
|
163
|
+
const node = tree.focusedNode;
|
|
164
|
+
if (!node) return;
|
|
165
|
+
if (!node.isEditable || !tree.props.onRename) return;
|
|
164
166
|
setTimeout(() => {
|
|
165
|
-
if (
|
|
167
|
+
if (node) tree.edit(node);
|
|
166
168
|
});
|
|
167
169
|
return;
|
|
168
170
|
}
|
package/src/components/tree.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { TreeProps } from "../types/tree-props";
|
|
|
8
8
|
import { IdObj } from "../types/utils";
|
|
9
9
|
import { useValidatedProps } from "../hooks/use-validated-props";
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
function TreeComponent<T>(
|
|
12
12
|
props: TreeProps<T>,
|
|
13
13
|
ref: React.Ref<TreeApi<T> | undefined>
|
|
14
14
|
) {
|
|
@@ -21,4 +21,8 @@ export const Tree = forwardRef(function Tree<T>(
|
|
|
21
21
|
<DragPreviewContainer />
|
|
22
22
|
</TreeProvider>
|
|
23
23
|
);
|
|
24
|
-
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const Tree = forwardRef(TreeComponent) as <T>(
|
|
27
|
+
props: TreeProps<T> & { ref?: React.ForwardedRef<TreeApi<T> | undefined> }
|
|
28
|
+
) => ReturnType<typeof TreeComponent>;
|
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
|
}
|
|
@@ -398,6 +398,41 @@ export class TreeApi<T> {
|
|
|
398
398
|
return this.state.dnd.cursor.type === "highlight";
|
|
399
399
|
}
|
|
400
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
|
+
|
|
401
436
|
hideCursor() {
|
|
402
437
|
this.dispatch(dnd.cursor({ type: "none" }));
|
|
403
438
|
}
|
|
@@ -529,13 +564,13 @@ export class TreeApi<T> {
|
|
|
529
564
|
}
|
|
530
565
|
}
|
|
531
566
|
|
|
532
|
-
|
|
533
|
-
const check = this.props.
|
|
567
|
+
isEditable(data: T) {
|
|
568
|
+
const check = this.props.disableEdit || (() => false);
|
|
534
569
|
return !utils.access(data, check) ?? true;
|
|
535
570
|
}
|
|
536
571
|
|
|
537
|
-
|
|
538
|
-
const check = this.props.
|
|
572
|
+
isDraggable(data: T) {
|
|
573
|
+
const check = this.props.disableDrag || (() => false);
|
|
539
574
|
return !utils.access(data, check) ?? true;
|
|
540
575
|
}
|
|
541
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 */
|
|
@@ -35,13 +35,21 @@ export interface TreeProps<T> {
|
|
|
35
35
|
padding?: number;
|
|
36
36
|
|
|
37
37
|
/* Config */
|
|
38
|
+
childrenAccessor?: string | ((d: T) => T[] | null);
|
|
39
|
+
idAccessor?: string | ((d: T) => string);
|
|
38
40
|
openByDefault?: boolean;
|
|
39
41
|
selectionFollowsFocus?: boolean;
|
|
40
42
|
disableMultiSelection?: boolean;
|
|
43
|
+
disableEdit?: string | boolean | BoolFunc<T>;
|
|
41
44
|
disableDrag?: string | boolean | BoolFunc<T>;
|
|
42
|
-
disableDrop?:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
disableDrop?:
|
|
46
|
+
| string
|
|
47
|
+
| boolean
|
|
48
|
+
| ((args: {
|
|
49
|
+
parentNode: NodeApi<T>;
|
|
50
|
+
dragNodes: NodeApi<T>[];
|
|
51
|
+
index: number;
|
|
52
|
+
}) => boolean);
|
|
45
53
|
|
|
46
54
|
/* Event Handlers */
|
|
47
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
|
}
|