react-arborist 0.1.14 → 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.
- package/dist/{lib/components → components}/drop-cursor.d.ts +1 -0
- package/dist/{lib/components → components}/preview.d.ts +1 -0
- package/dist/{lib/components → components}/row.d.ts +0 -0
- package/dist/{lib/components → components}/tree.d.ts +0 -0
- package/dist/{lib/context.d.ts → context.d.ts} +0 -0
- package/dist/{lib/data → data}/enrich-tree.d.ts +0 -0
- package/dist/{lib/data → data}/flatten-tree.d.ts +0 -0
- package/dist/{lib/dnd → dnd}/compute-drop.d.ts +0 -0
- package/dist/{lib/dnd → dnd}/drag-hook.d.ts +0 -0
- package/dist/{lib/dnd → dnd}/drop-hook.d.ts +0 -0
- package/dist/{lib/dnd → dnd}/outer-drop-hook.d.ts +0 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1354 -0
- package/dist/index.js.map +1 -0
- package/dist/module.js +1346 -0
- package/dist/module.js.map +1 -0
- package/dist/{lib/provider.d.ts → provider.d.ts} +1 -0
- package/dist/{lib/reducer.d.ts → reducer.d.ts} +0 -0
- package/dist/{lib/selection → selection}/range.d.ts +0 -0
- package/dist/{lib/selection → selection}/selection-hook.d.ts +0 -0
- package/dist/{lib/selection → selection}/selection.d.ts +0 -0
- package/dist/{lib/tree-api-hook.d.ts → tree-api-hook.d.ts} +0 -0
- package/dist/{lib/tree-api.d.ts → tree-api.d.ts} +1 -1
- package/dist/{lib/types.d.ts → types.d.ts} +4 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/{lib/utils.d.ts → utils.d.ts} +0 -0
- package/package.json +16 -44
- package/src/components/drop-cursor.tsx +47 -0
- package/src/components/preview.tsx +108 -0
- package/src/components/row.tsx +119 -0
- package/src/components/tree.tsx +118 -0
- package/src/context.tsx +52 -0
- package/src/data/enrich-tree.ts +74 -0
- package/src/data/flatten-tree.ts +17 -0
- package/src/data/make-tree.ts +37 -0
- package/src/dnd/compute-drop.ts +184 -0
- package/src/dnd/drag-hook.ts +48 -0
- package/src/dnd/drop-hook.ts +66 -0
- package/src/dnd/measure-hover.ts +26 -0
- package/src/dnd/outer-drop-hook.ts +50 -0
- package/src/index.ts +5 -0
- package/src/provider.tsx +61 -0
- package/src/reducer.ts +161 -0
- package/src/selection/range.ts +41 -0
- package/src/selection/selection-hook.ts +24 -0
- package/src/selection/selection.test.ts +111 -0
- package/src/selection/selection.ts +186 -0
- package/src/tree-api-hook.ts +34 -0
- package/src/tree-api.ts +129 -0
- package/src/types.ts +147 -0
- package/src/utils.ts +35 -0
- package/tsconfig.json +28 -0
- package/README.md +0 -197
- package/dist/lib/components/drop-cursor.js +0 -53
- package/dist/lib/components/preview.js +0 -91
- package/dist/lib/components/row.js +0 -122
- package/dist/lib/components/tree.js +0 -76
- package/dist/lib/context.js +0 -57
- package/dist/lib/data/enrich-tree.js +0 -48
- package/dist/lib/data/flatten-tree.js +0 -20
- package/dist/lib/data/make-tree.d.ts +0 -5
- package/dist/lib/data/make-tree.js +0 -40
- package/dist/lib/data/visible-nodes-hook.d.ts +0 -2
- package/dist/lib/data/visible-nodes-hook.js +0 -19
- package/dist/lib/dnd/compute-drop.js +0 -146
- package/dist/lib/dnd/drag-hook.js +0 -36
- package/dist/lib/dnd/drop-hook.js +0 -59
- package/dist/lib/dnd/measure-hover.d.ts +0 -8
- package/dist/lib/dnd/measure-hover.js +0 -21
- package/dist/lib/dnd/outer-drop-hook.js +0 -51
- package/dist/lib/index.d.ts +0 -3
- package/dist/lib/index.js +0 -7
- package/dist/lib/provider.js +0 -46
- package/dist/lib/reducer.js +0 -147
- package/dist/lib/selection/range.js +0 -45
- package/dist/lib/selection/selection-hook.js +0 -24
- package/dist/lib/selection/selection.js +0 -192
- package/dist/lib/selection/selection.test.d.ts +0 -1
- package/dist/lib/selection/selection.test.js +0 -102
- package/dist/lib/tree-api-hook.js +0 -26
- package/dist/lib/tree-api.js +0 -130
- package/dist/lib/tree-monitor.d.ts +0 -15
- package/dist/lib/tree-monitor.js +0 -32
- package/dist/lib/types.js +0 -2
- package/dist/lib/utils.js +0 -39
package/src/tree-api.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import memoizeOne from "memoize-one";
|
|
2
|
+
import { Dispatch } from "react";
|
|
3
|
+
import { FixedSizeList } from "react-window";
|
|
4
|
+
import { flattenTree } from "./data/flatten-tree";
|
|
5
|
+
import { Cursor } from "./dnd/compute-drop";
|
|
6
|
+
import { Action, actions } from "./reducer";
|
|
7
|
+
import { Node, StateContext, TreeProviderProps } from "./types";
|
|
8
|
+
|
|
9
|
+
export class TreeApi<T = unknown> {
|
|
10
|
+
constructor(
|
|
11
|
+
public dispatch: Dispatch<Action>,
|
|
12
|
+
public state: StateContext,
|
|
13
|
+
public props: TreeProviderProps<T>,
|
|
14
|
+
public list: FixedSizeList | undefined
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
assign(
|
|
18
|
+
dispatch: Dispatch<Action>,
|
|
19
|
+
state: StateContext,
|
|
20
|
+
props: TreeProviderProps<T>,
|
|
21
|
+
list: FixedSizeList | undefined
|
|
22
|
+
) {
|
|
23
|
+
this.dispatch = dispatch;
|
|
24
|
+
this.state = state;
|
|
25
|
+
this.props = props;
|
|
26
|
+
this.list = list;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getNode(id: string): Node<unknown> | null {
|
|
30
|
+
if (id in this.idToIndex)
|
|
31
|
+
return this.visibleNodes[this.idToIndex[id]] || null;
|
|
32
|
+
else return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getSelectedIds() {
|
|
36
|
+
return this.state.selection.ids;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
edit(id: string | number | null) {
|
|
40
|
+
this.dispatch(actions.edit(id ? id.toString() : null));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
select(index: number | null, meta: boolean, shift: boolean) {
|
|
44
|
+
this.dispatch(actions.select(index, meta, shift));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
selectUpwards(shiftKey: boolean) {
|
|
48
|
+
this.dispatch(actions.stepUp(shiftKey, this.visibleIds));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
selectDownwards(shiftKey: boolean) {
|
|
52
|
+
this.dispatch(actions.stepDown(shiftKey, this.visibleIds));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
hideCursor() {
|
|
56
|
+
this.dispatch(actions.setCursorLocation({ type: "none" }));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
showCursor(cursor: Cursor) {
|
|
60
|
+
this.dispatch(actions.setCursorLocation(cursor));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
scrollToId(id: string) {
|
|
64
|
+
if (!this.list) return;
|
|
65
|
+
const index = this.idToIndex[id];
|
|
66
|
+
if (index) {
|
|
67
|
+
this.list.scrollToItem(index, "start");
|
|
68
|
+
} else {
|
|
69
|
+
this.openParents(id);
|
|
70
|
+
// This appears to be synchronous
|
|
71
|
+
// But I've only tested it in the console and
|
|
72
|
+
// not in an event handler which will be batched...
|
|
73
|
+
// We may need to wrap this in a timeout or trigger an effect somehow
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
const index = this.idToIndex[id];
|
|
76
|
+
if (index) {
|
|
77
|
+
this.list?.scrollToItem(index, "start");
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
open(id: string) {
|
|
84
|
+
this.props.onToggle(id, true);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
openParents(id: string) {
|
|
88
|
+
const node = dfs(this.props.root, id);
|
|
89
|
+
let parent = node?.parent;
|
|
90
|
+
|
|
91
|
+
while (parent) {
|
|
92
|
+
this.open(parent.id);
|
|
93
|
+
parent = parent.parent;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get visibleIds() {
|
|
98
|
+
return getIds(this.visibleNodes);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get idToIndex() {
|
|
102
|
+
return createIndex(this.visibleNodes);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get visibleNodes() {
|
|
106
|
+
return createList(this.props.root);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const getIds = memoizeOne((nodes: Node[]) => nodes.map((n) => n.id));
|
|
111
|
+
const createIndex = memoizeOne((nodes: Node[]) => {
|
|
112
|
+
return nodes.reduce<{ [id: string]: number }>((map, node, index) => {
|
|
113
|
+
map[node.id] = index;
|
|
114
|
+
return map;
|
|
115
|
+
}, {});
|
|
116
|
+
});
|
|
117
|
+
const createList = memoizeOne(flattenTree);
|
|
118
|
+
|
|
119
|
+
function dfs(node: Node<unknown>, id: string): Node<unknown> | null {
|
|
120
|
+
if (!node) return null;
|
|
121
|
+
if (node.id === id) return node;
|
|
122
|
+
if (node.children) {
|
|
123
|
+
for (let child of node.children) {
|
|
124
|
+
const result = dfs(child, id);
|
|
125
|
+
if (result) return result;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
ComponentType,
|
|
3
|
+
CSSProperties,
|
|
4
|
+
MouseEvent,
|
|
5
|
+
MouseEventHandler,
|
|
6
|
+
MutableRefObject,
|
|
7
|
+
ReactElement,
|
|
8
|
+
Ref,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { FixedSizeList } from "react-window";
|
|
11
|
+
import { Cursor } from "./dnd/compute-drop";
|
|
12
|
+
import { SelectionData } from "./selection/selection";
|
|
13
|
+
import { TreeApi } from "./tree-api";
|
|
14
|
+
|
|
15
|
+
// Forward ref can't forward generics without this little re-declare
|
|
16
|
+
// https://fettblog.eu/typescript-react-generic-forward-refs/
|
|
17
|
+
declare module "react" {
|
|
18
|
+
function forwardRef<T, P = {}>(
|
|
19
|
+
render: (props: P, ref: React.Ref<T>) => React.ReactElement | null
|
|
20
|
+
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type Node<T = unknown> = {
|
|
24
|
+
id: string;
|
|
25
|
+
model: T;
|
|
26
|
+
level: number;
|
|
27
|
+
children: Node<T>[] | null;
|
|
28
|
+
parent: Node<T> | null;
|
|
29
|
+
isOpen: boolean;
|
|
30
|
+
isDraggable: boolean;
|
|
31
|
+
isDroppable: boolean;
|
|
32
|
+
rowIndex: number | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type NodesById<T> = { [id: string]: Node<T> };
|
|
36
|
+
|
|
37
|
+
export interface IdObj {
|
|
38
|
+
id: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type NodeRendererProps<T> = {
|
|
42
|
+
innerRef: (el: HTMLDivElement | null) => void;
|
|
43
|
+
styles: { row: CSSProperties; indent: CSSProperties };
|
|
44
|
+
data: T;
|
|
45
|
+
state: NodeState;
|
|
46
|
+
handlers: NodeHandlers;
|
|
47
|
+
tree: TreeApi<T>;
|
|
48
|
+
preview: boolean;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type NodeState = {
|
|
52
|
+
isOpen: boolean;
|
|
53
|
+
isSelected: boolean;
|
|
54
|
+
isHoveringOverChild: boolean;
|
|
55
|
+
isDragging: boolean;
|
|
56
|
+
isFirstOfSelected: boolean;
|
|
57
|
+
isLastOfSelected: boolean;
|
|
58
|
+
isEditing: boolean;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type NodeHandlers = {
|
|
62
|
+
toggle: MouseEventHandler;
|
|
63
|
+
select: (e: MouseEvent, selectOnClick?: boolean) => void;
|
|
64
|
+
edit: () => void;
|
|
65
|
+
submit: (name: string) => void;
|
|
66
|
+
reset: () => void;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type NodeRenderer<T> = ComponentType<NodeRendererProps<T>>;
|
|
70
|
+
|
|
71
|
+
export type MoveHandler = (
|
|
72
|
+
dragIds: string[],
|
|
73
|
+
parentId: string | null,
|
|
74
|
+
index: number
|
|
75
|
+
) => void;
|
|
76
|
+
|
|
77
|
+
export type ToggleHandler = (id: string, isOpen: boolean) => void;
|
|
78
|
+
export type EditHandler = (id: string, name: string) => void;
|
|
79
|
+
export type NodeClickHandler<T> = (e: MouseEvent, n: Node<T>) => void;
|
|
80
|
+
export type IndexClickHandler = (e: MouseEvent, index: number) => void;
|
|
81
|
+
export type SelectedCheck = (index: number) => boolean;
|
|
82
|
+
|
|
83
|
+
export type CursorLocation = {
|
|
84
|
+
index: number | null;
|
|
85
|
+
level: number | null;
|
|
86
|
+
parentId: string | null;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type DragItem = {
|
|
90
|
+
dragIds: string[];
|
|
91
|
+
id: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type SelectionState = {
|
|
95
|
+
data: SelectionData | null;
|
|
96
|
+
ids: string[];
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type StateContext = {
|
|
100
|
+
cursor: Cursor;
|
|
101
|
+
editingId: string | null;
|
|
102
|
+
selection: SelectionState;
|
|
103
|
+
visibleIds: string[];
|
|
104
|
+
};
|
|
105
|
+
export interface TreeProps<T> {
|
|
106
|
+
children: NodeRenderer<T>;
|
|
107
|
+
data: T;
|
|
108
|
+
height?: number;
|
|
109
|
+
width?: number;
|
|
110
|
+
rowHeight?: number;
|
|
111
|
+
indent?: number;
|
|
112
|
+
hideRoot?: boolean;
|
|
113
|
+
onToggle?: ToggleHandler;
|
|
114
|
+
onMove?: MoveHandler;
|
|
115
|
+
onEdit?: EditHandler;
|
|
116
|
+
getChildren?: string | ((d: T) => T[]);
|
|
117
|
+
isOpen?: string | ((d: T) => boolean);
|
|
118
|
+
disableDrag?: string | boolean | ((d: T) => boolean);
|
|
119
|
+
disableDrop?: string | boolean | ((d: T) => boolean);
|
|
120
|
+
openByDefault?: boolean;
|
|
121
|
+
className?: string | undefined;
|
|
122
|
+
handle?: Ref<TreeApi<T>>;
|
|
123
|
+
onClick?: MouseEventHandler;
|
|
124
|
+
onContextMenu?: MouseEventHandler;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type TreeProviderProps<T> = {
|
|
128
|
+
imperativeHandle: React.Ref<TreeApi<T>> | undefined;
|
|
129
|
+
children: ReactElement;
|
|
130
|
+
height: number;
|
|
131
|
+
indent: number;
|
|
132
|
+
listEl: MutableRefObject<HTMLDivElement | null>;
|
|
133
|
+
onToggle: ToggleHandler;
|
|
134
|
+
onMove: MoveHandler;
|
|
135
|
+
onEdit: EditHandler;
|
|
136
|
+
onClick?: MouseEventHandler;
|
|
137
|
+
onContextMenu?: MouseEventHandler;
|
|
138
|
+
renderer: NodeRenderer<any>;
|
|
139
|
+
rowHeight: number;
|
|
140
|
+
root: Node<T>;
|
|
141
|
+
width: number;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export type StaticContext<T> = TreeProviderProps<T> & {
|
|
145
|
+
api: TreeApi<T>;
|
|
146
|
+
list: MutableRefObject<FixedSizeList | undefined>;
|
|
147
|
+
};
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Node } from "./types";
|
|
2
|
+
|
|
3
|
+
export function bound(n: number, min: number, max: number) {
|
|
4
|
+
return Math.max(Math.min(n, max), min);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const isFolder = (node: Node<any>) => !!node.children;
|
|
8
|
+
|
|
9
|
+
export function isItem(node: Node | null) {
|
|
10
|
+
return node && !isFolder(node);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isClosed(node: Node | null) {
|
|
14
|
+
return node && isFolder(node) && !node.isOpen;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Is first param a decendent of the second param
|
|
19
|
+
*/
|
|
20
|
+
export const isDecendent = (a: Node, b: Node) => {
|
|
21
|
+
let n: Node | null = a;
|
|
22
|
+
while (n) {
|
|
23
|
+
if (n.id === b.id) return true;
|
|
24
|
+
n = n.parent;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const indexOf = (node: Node) => {
|
|
30
|
+
// This should probably not throw an error, but instead return null
|
|
31
|
+
if (!node.parent) throw Error("Node does not have a parent");
|
|
32
|
+
return node.parent.children!.findIndex((c) => c.id === node.id);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function noop() {}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"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": "preserve", /* 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
|
+
}
|
package/README.md
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
# React Arborist
|
|
2
|
-
|
|
3
|
-
A full-featured tree component for React.
|
|
4
|
-
|
|
5
|
-
The tree UI is ubiquitous in software applications. There are many tree component libraries for React, but none were full-featured enough to stand on their own.
|
|
6
|
-
|
|
7
|
-
This library provides all the common features expected in a tree. You can select one or many nodes to drag and drop into new positions, open and close folders, render an inline form for renaming, efficiently show thousands of items, and provide your own node renderer to control the style.
|
|
8
|
-
|
|
9
|
-

|
|
10
|
-
|
|
11
|
-
## Installation
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
yarn add react-arborist
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
npm install react-arborist
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Example
|
|
22
|
-
|
|
23
|
-
Render the tree data structure.
|
|
24
|
-
|
|
25
|
-
```jsx
|
|
26
|
-
const data = {
|
|
27
|
-
id: "The Root",
|
|
28
|
-
children: [{id: "Node A"}, {id: "Node B"}]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function App() {
|
|
32
|
-
return (
|
|
33
|
-
<Tree data={data}>
|
|
34
|
-
{Node}
|
|
35
|
-
</Tree>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function Node({ ref, styles, data}) {
|
|
40
|
-
return (
|
|
41
|
-
<div ref={ref} style={styles.row}>
|
|
42
|
-
<div style={styles.indent}>
|
|
43
|
-
{data.name}
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
#### Contents
|
|
51
|
-
* [Expected Data Structure](#expected-data-structure)
|
|
52
|
-
* [Tree Component API](#tree-component)
|
|
53
|
-
* [Node Renderer API](#node-renderer-component)
|
|
54
|
-
* [Styles Prop](#styles-prop)
|
|
55
|
-
* [State Prop](#state-prop)
|
|
56
|
-
* [Handlers Prop](#handlers-prop)
|
|
57
|
-
* [Tree Prop](#tree-prop)
|
|
58
|
-
|
|
59
|
-
## Expected Data Structure
|
|
60
|
-
|
|
61
|
-
The Tree component expects the data prop to be a tree-like data structure with the following type:
|
|
62
|
-
|
|
63
|
-
```ts
|
|
64
|
-
type Data = {
|
|
65
|
-
id: string, /* Required */
|
|
66
|
-
children?: Data[]
|
|
67
|
-
isOpen?: boolean
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
If your data does not look like this, you can provide a `childrenAccessor` prop. You can also provide `isOpenAccessor`. The value can be a string or a function.
|
|
72
|
-
|
|
73
|
-
```ts
|
|
74
|
-
<Tree childrenAccessor="items" ...
|
|
75
|
-
// Or
|
|
76
|
-
<Tree childrenAccessor={(node) => node.items} ...
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Tree Component
|
|
80
|
-
|
|
81
|
-
Unlike other Tree Components, react-arborist is designed as a [controlled component](https://reactjs.org/docs/forms.html#controlled-components). This means the consumer will provide the tree data and the handlers to update it. The only state managed internally is for drag and drop, selection, and editing.
|
|
82
|
-
|
|
83
|
-
| Prop | Default | Description |
|
|
84
|
-
| --- | --- | --- |
|
|
85
|
-
| data | (required) | The tree data structure to render as described above. |
|
|
86
|
-
| width | 300 | The width of the tree.
|
|
87
|
-
| height | 500 | The height of the tree. To dynamically fill it's container, use a [hook](https://github.com/ZeeCoder/use-resize-observer) or [component](https://github.com/bvaughn/react-virtualized-auto-sizer) to gather the width and height of the Tree's parent.
|
|
88
|
-
| rowHeight | 24 | The height of each row.
|
|
89
|
-
| indent | 24 | The number of pixels to indent child nodes.
|
|
90
|
-
| hideRoot | false | Hide the root node so that the first set of children appear as the roots.
|
|
91
|
-
| onToggle | noop | Handler called when a node is opened or closed. This and the subsequent functions should update the `data` prop for the tree to re-render.
|
|
92
|
-
| onMove | noop | Handler called when a user moves one or more nodes by dragging and dropping.
|
|
93
|
-
| onEdit | noop | Handler called when a user performs an inline edit of the node.
|
|
94
|
-
| childrenAccessor | "children" | Used to get a node's children if they exist on a property other than "children".
|
|
95
|
-
| isOpenAccessor | "isOpen" | Used to get a node's openness state if it exists on a property other than "isOpen".
|
|
96
|
-
| openByDefault | true | Choose if the node should be open or closed when it has an undefined openness state.
|
|
97
|
-
| className | undefined | Adds a class to the containing div.
|
|
98
|
-
|
|
99
|
-
The only child of the Tree Component must be a NodeRenderer function as described below.
|
|
100
|
-
|
|
101
|
-
```jsx
|
|
102
|
-
const NodeRenderer = ({
|
|
103
|
-
innerRef, data, styles, handlers, state, tree
|
|
104
|
-
}) => ...
|
|
105
|
-
|
|
106
|
-
const MyApp = () =>
|
|
107
|
-
<Tree>
|
|
108
|
-
{NodeRenderer}
|
|
109
|
-
</Tree>
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## Node Renderer Component
|
|
113
|
-
|
|
114
|
-
The Node Renderer is where you get to make the tree your own. You completely own the style and functionality. The props passed to it should enable you to do whatever you need.
|
|
115
|
-
|
|
116
|
-
The most basic node renderer will look like this:
|
|
117
|
-
|
|
118
|
-
```jsx
|
|
119
|
-
function NodeRenderer({
|
|
120
|
-
innerRef,
|
|
121
|
-
styles,
|
|
122
|
-
data,
|
|
123
|
-
state,
|
|
124
|
-
handlers,
|
|
125
|
-
tree
|
|
126
|
-
}) {
|
|
127
|
-
return (
|
|
128
|
-
<div ref={innerRef} style={styles.row}>
|
|
129
|
-
<div style={styles.indent}>{data.id}</div>
|
|
130
|
-
</div>
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
The function above is passed data for this individual node, the DOM ref used for drag and drop, the styles to position the row in the virtualized list and the styles to indent the current node according to its level in the tree.
|
|
136
|
-
|
|
137
|
-
| Prop | Type | Description |
|
|
138
|
-
| ---- | ---- | ----------- |
|
|
139
|
-
| data | Node | A single node from the tree data structure provided. |
|
|
140
|
-
| innerRef | Ref | Must be attached to the root element returned by the NodeRenderer. This is needed for drag and drop to work.
|
|
141
|
-
| [styles](#styles-prop) | object | This is an object that contains styles for the position of the row, and the level of indentation. Each key is described below.
|
|
142
|
-
| [state](#state-prop) | object | An handful of boolean values that indicate the current state of this node. See below for details.
|
|
143
|
-
| [handlers](#handlers-prop) | object | A collection of handlers to attach to the DOM, that provide selectable and toggle-able behaviors. Each handler is described below.
|
|
144
|
-
| [tree](#tree-prop) | TreeMonitor | This object can be used to get at the internal state of the whole tree component. For example, `tree.getSelectedNodes()`. All the methods are listed below in the Tree Prop section.
|
|
145
|
-
|
|
146
|
-
### Styles Prop
|
|
147
|
-
|
|
148
|
-
These are the properties on the styles object passed to the NodeRenderer.
|
|
149
|
-
|
|
150
|
-
| Name | Type | Description |
|
|
151
|
-
| ---- | ---- | ----------- |
|
|
152
|
-
| row | CSSProperties | Since the tree only renders the rows that are currently visible, all the rows are absolutely positioned with a fixed top and left. Those styles are in this property.
|
|
153
|
-
| indent | CSSProperties | This is simply a left padding set to the level of the tree multiplied by the tree indent prop.
|
|
154
|
-
|
|
155
|
-
### State Prop
|
|
156
|
-
|
|
157
|
-
These are the properties on the state object passed to the NodeRenderer.
|
|
158
|
-
|
|
159
|
-
| Name | Type | Description |
|
|
160
|
-
| ---- | ---- | ----------- |
|
|
161
|
-
| isOpen | boolean | True if this node has children and the children are visible. Use this to display some type a open or closed icon.
|
|
162
|
-
| isSelected | boolean | True if this node is selected. Use this to show a "selected" state. Maybe a different background?
|
|
163
|
-
| isHoveringOverChild | boolean | True if the user is dragging n node, and the node is hovering over one of this node's direct children. This can be used to indicate which folder the user is dragging an item into.
|
|
164
|
-
| isDragging | boolean | True if this node is being dragged.
|
|
165
|
-
| isFirstOfSelected | boolean | True if this is the first of a contiguous group of selected rows. This can be used to tastefully style a group of selected items. Maybe a different border radius on the first and last rows?
|
|
166
|
-
| isLastOfSelected | boolean | True if this is the last of a contiguous group of selected rows.
|
|
167
|
-
| isEditing | boolean | True if this row is being edited. When true, the renderer should return some type of form input.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
### Handlers Prop
|
|
171
|
-
|
|
172
|
-
These are the properties on the handlers object passed to the NodeRenderer.
|
|
173
|
-
|
|
174
|
-
| Name | Type | Description |
|
|
175
|
-
| ---- | ---- | ----------- |
|
|
176
|
-
| select | MouseEventHandler | Attach this to the element that tiggers selection. Maybe you want to add it to the outermost div. <br /> `<div onClick={handlers.select}>`
|
|
177
|
-
| toggle | MouseEventHandler | Attach this to the element that opens and closes the node. Maybe you want to add it to the `+`/`-` icon. <br />`<icon onClick={handlers.toggle}>`
|
|
178
|
-
| edit | `() => void` | Makes this node editable. This will re-render the Node with the `state.isEditing` prop set to `true`.
|
|
179
|
-
| submit | `(update: string) => void` | Sends the update to the `onEdit` handler in the Tree component, and sets the `state.isEditing` prop to `false`.
|
|
180
|
-
| reset | `() => void` | Re-renders with the `state.isEditing` prop set to `false`.
|
|
181
|
-
|
|
182
|
-
### Tree Prop
|
|
183
|
-
|
|
184
|
-
The tree monitor provides methods to get the tree's internal state. A use case might be in a right click menu.
|
|
185
|
-
|
|
186
|
-
```jsx
|
|
187
|
-
// In your node renderer
|
|
188
|
-
onContextMenu={() => {
|
|
189
|
-
// Do something with all the selected nodes
|
|
190
|
-
tree.getSelectedIds()
|
|
191
|
-
}}
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
| Methods | Returns | Description |
|
|
195
|
-
| ---- | ---- | ----------- |
|
|
196
|
-
| `getSelectedIds()` | `string[]` | Get the the ids of all currently selected nodes.
|
|
197
|
-
| `edit(id: string)` | `void` | Edit a node programatically.
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __assign = (this && this.__assign) || function () {
|
|
3
|
-
__assign = Object.assign || function(t) {
|
|
4
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
-
s = arguments[i];
|
|
6
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
-
t[p] = s[p];
|
|
8
|
-
}
|
|
9
|
-
return t;
|
|
10
|
-
};
|
|
11
|
-
return __assign.apply(this, arguments);
|
|
12
|
-
};
|
|
13
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.DropCursor = void 0;
|
|
15
|
-
var jsx_runtime_1 = require("react/jsx-runtime");
|
|
16
|
-
var context_1 = require("../context");
|
|
17
|
-
function DropCursor() {
|
|
18
|
-
var treeView = (0, context_1.useStaticContext)();
|
|
19
|
-
var cursor = (0, context_1.useCursorLocation)();
|
|
20
|
-
if (!cursor || cursor.type !== "line")
|
|
21
|
-
return null;
|
|
22
|
-
var top = treeView.rowHeight * cursor.index;
|
|
23
|
-
var left = treeView.indent * cursor.level;
|
|
24
|
-
var style = {
|
|
25
|
-
position: "absolute",
|
|
26
|
-
pointerEvents: "none",
|
|
27
|
-
top: top - 2 + "px",
|
|
28
|
-
left: treeView.indent + left + "px",
|
|
29
|
-
right: treeView.indent + "px",
|
|
30
|
-
};
|
|
31
|
-
return (0, jsx_runtime_1.jsx)(DefaultCursor, { style: style }, void 0);
|
|
32
|
-
}
|
|
33
|
-
exports.DropCursor = DropCursor;
|
|
34
|
-
var placeholderStyle = {
|
|
35
|
-
display: "flex",
|
|
36
|
-
alignItems: "center",
|
|
37
|
-
};
|
|
38
|
-
var lineStyle = {
|
|
39
|
-
flex: 1,
|
|
40
|
-
height: "2px",
|
|
41
|
-
background: "#4B91E2",
|
|
42
|
-
borderRadius: "1px",
|
|
43
|
-
};
|
|
44
|
-
var circleStyle = {
|
|
45
|
-
width: "4px",
|
|
46
|
-
height: "4px",
|
|
47
|
-
boxShadow: "0 0 0 3px #4B91E2",
|
|
48
|
-
borderRadius: "50%",
|
|
49
|
-
};
|
|
50
|
-
function DefaultCursor(_a) {
|
|
51
|
-
var style = _a.style;
|
|
52
|
-
return ((0, jsx_runtime_1.jsxs)("div", __assign({ style: __assign(__assign({}, placeholderStyle), style) }, { children: [(0, jsx_runtime_1.jsx)("div", { style: __assign({}, circleStyle) }, void 0), (0, jsx_runtime_1.jsx)("div", { style: __assign({}, lineStyle) }, void 0)] }), void 0));
|
|
53
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __assign = (this && this.__assign) || function () {
|
|
3
|
-
__assign = Object.assign || function(t) {
|
|
4
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
-
s = arguments[i];
|
|
6
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
-
t[p] = s[p];
|
|
8
|
-
}
|
|
9
|
-
return t;
|
|
10
|
-
};
|
|
11
|
-
return __assign.apply(this, arguments);
|
|
12
|
-
};
|
|
13
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.Preview = void 0;
|
|
15
|
-
var jsx_runtime_1 = require("react/jsx-runtime");
|
|
16
|
-
var react_1 = require("react");
|
|
17
|
-
var react_dnd_1 = require("react-dnd");
|
|
18
|
-
var context_1 = require("../context");
|
|
19
|
-
var layerStyles = {
|
|
20
|
-
position: "fixed",
|
|
21
|
-
pointerEvents: "none",
|
|
22
|
-
zIndex: 100,
|
|
23
|
-
left: 0,
|
|
24
|
-
top: 0,
|
|
25
|
-
width: "100%",
|
|
26
|
-
height: "100%",
|
|
27
|
-
};
|
|
28
|
-
var getStyle = function (offset) {
|
|
29
|
-
if (!offset)
|
|
30
|
-
return { display: "none" };
|
|
31
|
-
var x = offset.x, y = offset.y;
|
|
32
|
-
return { transform: "translate(" + x + "px, " + y + "px)" };
|
|
33
|
-
};
|
|
34
|
-
var getCountStyle = function (offset) {
|
|
35
|
-
if (!offset)
|
|
36
|
-
return { display: "none" };
|
|
37
|
-
var x = offset.x, y = offset.y;
|
|
38
|
-
return { transform: "translate(" + (x + 10) + "px, " + (y + 10) + "px)" };
|
|
39
|
-
};
|
|
40
|
-
function Preview() {
|
|
41
|
-
var _a = (0, react_dnd_1.useDragLayer)(function (m) { return ({
|
|
42
|
-
offset: m.getSourceClientOffset(),
|
|
43
|
-
mouse: m.getClientOffset(),
|
|
44
|
-
item: m.getItem(),
|
|
45
|
-
isDragging: m.isDragging(),
|
|
46
|
-
}); }), offset = _a.offset, mouse = _a.mouse, item = _a.item, isDragging = _a.isDragging;
|
|
47
|
-
return ((0, jsx_runtime_1.jsxs)(Overlay, __assign({ isDragging: isDragging }, { children: [(0, jsx_runtime_1.jsx)(Position, __assign({ offset: offset }, { children: (0, jsx_runtime_1.jsx)(PreviewNode, { item: item }, void 0) }), void 0), (0, jsx_runtime_1.jsx)(Count, { mouse: mouse, item: item }, void 0)] }), void 0));
|
|
48
|
-
}
|
|
49
|
-
exports.Preview = Preview;
|
|
50
|
-
var Overlay = (0, react_1.memo)(function Overlay(props) {
|
|
51
|
-
if (!props.isDragging)
|
|
52
|
-
return null;
|
|
53
|
-
return (0, jsx_runtime_1.jsx)("div", __assign({ style: layerStyles }, { children: props.children }), void 0);
|
|
54
|
-
});
|
|
55
|
-
function Position(props) {
|
|
56
|
-
return ((0, jsx_runtime_1.jsx)("div", __assign({ className: "row preview", style: getStyle(props.offset) }, { children: props.children }), void 0));
|
|
57
|
-
}
|
|
58
|
-
function Count(props) {
|
|
59
|
-
var _a;
|
|
60
|
-
var item = props.item, mouse = props.mouse;
|
|
61
|
-
if (((_a = item === null || item === void 0 ? void 0 : item.dragIds) === null || _a === void 0 ? void 0 : _a.length) > 1)
|
|
62
|
-
return ((0, jsx_runtime_1.jsx)("div", __assign({ className: "selected-count", style: getCountStyle(mouse) }, { children: item.dragIds.length }), void 0));
|
|
63
|
-
else
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
var PreviewNode = (0, react_1.memo)(function PreviewNode(props) {
|
|
67
|
-
var tree = (0, context_1.useStaticContext)();
|
|
68
|
-
if (!props.item)
|
|
69
|
-
return null;
|
|
70
|
-
var node = tree.api.getNode(props.item.id);
|
|
71
|
-
if (!node)
|
|
72
|
-
return null;
|
|
73
|
-
return ((0, jsx_runtime_1.jsx)(tree.renderer, { preview: true, innerRef: function () { }, data: node.model, styles: {
|
|
74
|
-
row: {},
|
|
75
|
-
indent: { paddingLeft: node.level * tree.indent },
|
|
76
|
-
}, tree: tree.api, state: {
|
|
77
|
-
isDragging: false,
|
|
78
|
-
isEditing: false,
|
|
79
|
-
isSelected: false,
|
|
80
|
-
isFirstOfSelected: false,
|
|
81
|
-
isLastOfSelected: false,
|
|
82
|
-
isHoveringOverChild: false,
|
|
83
|
-
isOpen: node.isOpen,
|
|
84
|
-
}, handlers: {
|
|
85
|
-
edit: function () { },
|
|
86
|
-
select: function () { },
|
|
87
|
-
toggle: function () { },
|
|
88
|
-
submit: function () { },
|
|
89
|
-
reset: function () { },
|
|
90
|
-
} }, void 0));
|
|
91
|
-
});
|