react-arborist 1.0.3 → 1.2.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 +108 -84
- package/dist/components/default-drop-cursor.d.ts +3 -0
- package/dist/components/list-outer-element.d.ts +2 -0
- package/dist/components/list.d.ts +4 -0
- package/dist/components/outer-drop.d.ts +4 -0
- package/dist/components/row.d.ts +2 -1
- package/dist/context.d.ts +5 -18
- package/dist/index.d.ts +2 -2
- package/dist/index.js +836 -853
- package/dist/index.js.map +1 -1
- package/dist/module.js +839 -856
- package/dist/module.js.map +1 -1
- package/dist/provider.d.ts +2 -2
- package/dist/selection/selection-hook.d.ts +2 -1
- package/dist/tree-api.d.ts +24 -8
- package/dist/types.d.ts +28 -34
- package/package.json +3 -2
- package/src/components/default-drop-cursor.tsx +41 -0
- package/src/components/drop-cursor.tsx +7 -42
- package/src/components/list-outer-element.tsx +34 -0
- package/src/components/list.tsx +25 -0
- package/src/components/outer-drop.ts +7 -0
- package/src/components/preview.tsx +6 -6
- package/src/components/row.tsx +26 -28
- package/src/components/tree.tsx +10 -76
- package/src/context.tsx +9 -48
- package/src/dnd/drag-hook.ts +5 -6
- package/src/dnd/drop-hook.ts +5 -5
- package/src/dnd/outer-drop-hook.ts +5 -5
- package/src/index.ts +16 -2
- package/src/provider.tsx +29 -49
- package/src/selection/selection-hook.ts +2 -1
- package/src/tree-api.ts +95 -21
- package/src/types.ts +30 -33
- package/dist/tree-api-hook.d.ts +0 -6
- package/src/tree-api-hook.ts +0 -34
package/src/provider.tsx
CHANGED
|
@@ -1,61 +1,41 @@
|
|
|
1
|
-
import { useImperativeHandle, useMemo, useReducer, useRef } from "react";
|
|
2
|
-
import { FixedSizeList } from "react-window";
|
|
3
1
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from "
|
|
11
|
-
import {
|
|
12
|
-
import { initState, reducer } from "./reducer";
|
|
2
|
+
useImperativeHandle,
|
|
3
|
+
useLayoutEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useReducer,
|
|
6
|
+
useRef,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { FixedSizeList } from "react-window";
|
|
9
|
+
import { TreeApiContext } from "./context";
|
|
10
|
+
import { actions, initState, reducer } from "./reducer";
|
|
13
11
|
import { useSelectionKeys } from "./selection/selection-hook";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
12
|
+
import { TreeApi } from "./tree-api";
|
|
13
|
+
import { IdObj, TreeProviderProps } from "./types";
|
|
16
14
|
|
|
17
|
-
export function TreeViewProvider<T>(props: TreeProviderProps<T>) {
|
|
15
|
+
export function TreeViewProvider<T extends IdObj>(props: TreeProviderProps<T>) {
|
|
18
16
|
const [state, dispatch] = useReducer(reducer, initState());
|
|
19
|
-
const list = useRef<FixedSizeList>();
|
|
20
|
-
const
|
|
17
|
+
const list = useRef<FixedSizeList | null>(null);
|
|
18
|
+
const listEl = useRef<HTMLDivElement | null>(null);
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
() => ({ ...props, api, list }),
|
|
26
|
-
[props, api, list]
|
|
20
|
+
const api = useMemo(
|
|
21
|
+
() => new TreeApi<T>(dispatch, state, props, list, listEl),
|
|
22
|
+
[dispatch, state, props, list, listEl]
|
|
27
23
|
);
|
|
28
24
|
|
|
29
25
|
/**
|
|
30
|
-
* This
|
|
26
|
+
* This ensures that the selection remains correct even
|
|
27
|
+
* after opening and closing a folders
|
|
31
28
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<EditingIdContext.Provider value={state.editingId}>
|
|
36
|
-
<SelectionContext.Provider value={state.selection}>
|
|
37
|
-
<CursorParentId.Provider value={getParentId(state.cursor)}>
|
|
38
|
-
<IsCursorOverFolder.Provider value={isOverFolder(state)}>
|
|
39
|
-
<CursorLocationContext.Provider value={state.cursor}>
|
|
40
|
-
{props.children}
|
|
41
|
-
</CursorLocationContext.Provider>
|
|
42
|
-
</IsCursorOverFolder.Provider>
|
|
43
|
-
</CursorParentId.Provider>
|
|
44
|
-
</SelectionContext.Provider>
|
|
45
|
-
</EditingIdContext.Provider>
|
|
46
|
-
</Static.Provider>
|
|
47
|
-
);
|
|
48
|
-
}
|
|
29
|
+
useLayoutEffect(() => {
|
|
30
|
+
dispatch(actions.setVisibleIds(api.visibleIds, api.idToIndex));
|
|
31
|
+
}, [dispatch, api.visibleIds, api.idToIndex, props.root]);
|
|
49
32
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
case "highlight":
|
|
53
|
-
return cursor.id;
|
|
54
|
-
default:
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
33
|
+
useImperativeHandle(props.imperativeHandle, () => api);
|
|
34
|
+
useSelectionKeys(listEl, api);
|
|
58
35
|
|
|
59
|
-
|
|
60
|
-
|
|
36
|
+
return (
|
|
37
|
+
<TreeApiContext.Provider value={api}>
|
|
38
|
+
{props.children}
|
|
39
|
+
</TreeApiContext.Provider>
|
|
40
|
+
);
|
|
61
41
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { MutableRefObject, useEffect } from "react";
|
|
2
2
|
import { TreeApi } from "../tree-api";
|
|
3
|
+
import { IdObj } from "../types";
|
|
3
4
|
|
|
4
|
-
export function useSelectionKeys<T>(
|
|
5
|
+
export function useSelectionKeys<T extends IdObj>(
|
|
5
6
|
ref: MutableRefObject<HTMLDivElement | null>,
|
|
6
7
|
api: TreeApi<T>
|
|
7
8
|
) {
|
package/src/tree-api.ts
CHANGED
|
@@ -1,35 +1,41 @@
|
|
|
1
1
|
import memoizeOne from "memoize-one";
|
|
2
|
-
import { Dispatch } from "react";
|
|
2
|
+
import { Dispatch, MutableRefObject } from "react";
|
|
3
3
|
import { FixedSizeList } from "react-window";
|
|
4
4
|
import { flattenTree } from "./data/flatten-tree";
|
|
5
5
|
import { Cursor } from "./dnd/compute-drop";
|
|
6
6
|
import { Action, actions } from "./reducer";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
Node,
|
|
9
|
+
StateContext,
|
|
10
|
+
TreeProviderProps,
|
|
11
|
+
EditResult,
|
|
12
|
+
IdObj,
|
|
13
|
+
DropCursorProps,
|
|
14
|
+
} from "./types";
|
|
8
15
|
import ReactDOM from "react-dom";
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
import { noop } from "./utils";
|
|
17
|
+
import { Selection } from "./selection/selection";
|
|
18
|
+
import { defaultDropCursor } from "./components/default-drop-cursor";
|
|
19
|
+
export class TreeApi<T extends IdObj> {
|
|
11
20
|
private edits = new Map<string, (args: EditResult) => void>();
|
|
12
21
|
|
|
13
22
|
constructor(
|
|
14
23
|
public dispatch: Dispatch<Action>,
|
|
15
24
|
public state: StateContext,
|
|
16
25
|
public props: TreeProviderProps<T>,
|
|
17
|
-
public list: FixedSizeList |
|
|
26
|
+
public list: MutableRefObject<FixedSizeList | null>,
|
|
27
|
+
public listEl: MutableRefObject<HTMLDivElement | null>
|
|
18
28
|
) {}
|
|
19
29
|
|
|
20
|
-
|
|
21
|
-
dispatch
|
|
22
|
-
state
|
|
23
|
-
props
|
|
24
|
-
list
|
|
25
|
-
|
|
26
|
-
this.dispatch = dispatch;
|
|
27
|
-
this.state = state;
|
|
28
|
-
this.props = props;
|
|
29
|
-
this.list = list;
|
|
30
|
+
sync(other: TreeApi<T>) {
|
|
31
|
+
this.dispatch = other.dispatch;
|
|
32
|
+
this.state = other.state;
|
|
33
|
+
this.props = other.props;
|
|
34
|
+
this.list = other.list;
|
|
35
|
+
this.listEl = other.listEl;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
|
-
getNode(id: string): Node<
|
|
38
|
+
getNode(id: string): Node<T> | null {
|
|
33
39
|
if (id in this.idToIndex)
|
|
34
40
|
return this.visibleNodes[this.idToIndex[id]] || null;
|
|
35
41
|
else return null;
|
|
@@ -49,7 +55,7 @@ export class TreeApi<T = unknown> {
|
|
|
49
55
|
|
|
50
56
|
submit(id: string | number, value: string) {
|
|
51
57
|
const sid = id.toString();
|
|
52
|
-
this.
|
|
58
|
+
this.onEdit(sid, value);
|
|
53
59
|
this.dispatch(actions.edit(null));
|
|
54
60
|
this.resolveEdit(sid, { cancelled: false, value });
|
|
55
61
|
}
|
|
@@ -95,20 +101,20 @@ export class TreeApi<T = unknown> {
|
|
|
95
101
|
if (!this.list) return;
|
|
96
102
|
const index = this.idToIndex[id];
|
|
97
103
|
if (index) {
|
|
98
|
-
this.list.scrollToItem(index
|
|
104
|
+
this.list.current?.scrollToItem(index);
|
|
99
105
|
} else {
|
|
100
106
|
this.openParents(id);
|
|
101
107
|
ReactDOM.flushSync(() => {
|
|
102
108
|
const index = this.idToIndex[id];
|
|
103
109
|
if (index) {
|
|
104
|
-
this.list?.scrollToItem(index
|
|
110
|
+
this.list.current?.scrollToItem(index);
|
|
105
111
|
}
|
|
106
112
|
});
|
|
107
113
|
}
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
open(id: string) {
|
|
111
|
-
this.
|
|
117
|
+
this.onToggle(id, true);
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
openParents(id: string) {
|
|
@@ -130,7 +136,75 @@ export class TreeApi<T = unknown> {
|
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
get visibleNodes() {
|
|
133
|
-
return createList(this.props.root);
|
|
139
|
+
return createList(this.props.root) as Node<T>[];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
get width() {
|
|
143
|
+
return this.props.treeProps.width || 300;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get height() {
|
|
147
|
+
return this.props.treeProps.height || 500;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
get indent() {
|
|
151
|
+
return this.props.treeProps.indent || 24;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
get renderer() {
|
|
155
|
+
return this.props.treeProps.children;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
get onToggle() {
|
|
159
|
+
return this.props.treeProps.onToggle || noop;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
get rowHeight() {
|
|
163
|
+
return this.props.treeProps.rowHeight || 24;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
get onClick() {
|
|
167
|
+
return this.props.treeProps.onClick || noop;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
get onContextMenu() {
|
|
171
|
+
return this.props.treeProps.onContextMenu || noop;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
get onMove() {
|
|
175
|
+
return this.props.treeProps.onMove || noop;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
get onEdit() {
|
|
179
|
+
return this.props.treeProps.onEdit || noop;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
get cursorParentId() {
|
|
183
|
+
const { cursor } = this.state;
|
|
184
|
+
switch (cursor.type) {
|
|
185
|
+
case "highlight":
|
|
186
|
+
return cursor.id;
|
|
187
|
+
default:
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
get cursorOverFolder() {
|
|
193
|
+
return this.state.cursor.type === "highlight";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
get editingId() {
|
|
197
|
+
return this.state.editingId;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
isSelected(index: number | null) {
|
|
201
|
+
const selection = Selection.parse(this.state.selection.data, []);
|
|
202
|
+
return selection.contains(index);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
renderDropCursor(props: DropCursorProps) {
|
|
206
|
+
const render = this.props.treeProps.dropCursor || defaultDropCursor;
|
|
207
|
+
return render(props);
|
|
134
208
|
}
|
|
135
209
|
}
|
|
136
210
|
|
package/src/types.ts
CHANGED
|
@@ -5,6 +5,7 @@ import React, {
|
|
|
5
5
|
MouseEventHandler,
|
|
6
6
|
MutableRefObject,
|
|
7
7
|
ReactElement,
|
|
8
|
+
ReactNode,
|
|
8
9
|
Ref,
|
|
9
10
|
} from "react";
|
|
10
11
|
import { FixedSizeList } from "react-window";
|
|
@@ -38,7 +39,7 @@ export interface IdObj {
|
|
|
38
39
|
id: string;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
export type NodeRendererProps<T> = {
|
|
42
|
+
export type NodeRendererProps<T extends IdObj> = {
|
|
42
43
|
innerRef: (el: HTMLDivElement | null) => void;
|
|
43
44
|
styles: { row: CSSProperties; indent: CSSProperties };
|
|
44
45
|
data: T;
|
|
@@ -66,7 +67,7 @@ export type NodeHandlers = {
|
|
|
66
67
|
reset: () => void;
|
|
67
68
|
};
|
|
68
69
|
|
|
69
|
-
export type NodeRenderer<T> = ComponentType<NodeRendererProps<T>>;
|
|
70
|
+
export type NodeRenderer<T extends IdObj> = ComponentType<NodeRendererProps<T>>;
|
|
70
71
|
|
|
71
72
|
export type MoveHandler = (
|
|
72
73
|
dragIds: string[],
|
|
@@ -102,50 +103,46 @@ export type StateContext = {
|
|
|
102
103
|
selection: SelectionState;
|
|
103
104
|
visibleIds: string[];
|
|
104
105
|
};
|
|
105
|
-
|
|
106
|
+
|
|
107
|
+
type BoolFunc<T> = (data: T) => boolean;
|
|
108
|
+
|
|
109
|
+
export interface TreeProps<T extends IdObj> {
|
|
106
110
|
children: NodeRenderer<T>;
|
|
111
|
+
className?: string | undefined;
|
|
107
112
|
data: T;
|
|
113
|
+
disableDrag?: string | boolean | BoolFunc<T>;
|
|
114
|
+
disableDrop?: string | boolean | BoolFunc<T>;
|
|
115
|
+
dndRootElement?: globalThis.Node | null;
|
|
116
|
+
getChildren?: string | ((d: T) => T[]);
|
|
117
|
+
handle?: Ref<TreeApi<T>>; // Deprecated
|
|
108
118
|
height?: number;
|
|
109
|
-
width?: number;
|
|
110
|
-
rowHeight?: number;
|
|
111
|
-
indent?: number;
|
|
112
119
|
hideRoot?: boolean;
|
|
113
|
-
|
|
114
|
-
|
|
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>>;
|
|
120
|
+
indent?: number;
|
|
121
|
+
isOpen?: string | BoolFunc<T>;
|
|
123
122
|
onClick?: MouseEventHandler;
|
|
124
123
|
onContextMenu?: MouseEventHandler;
|
|
124
|
+
onEdit?: EditHandler;
|
|
125
|
+
onMove?: MoveHandler;
|
|
126
|
+
onToggle?: ToggleHandler;
|
|
127
|
+
openByDefault?: boolean;
|
|
128
|
+
rowHeight?: number;
|
|
129
|
+
width?: number;
|
|
130
|
+
dropCursor?: (props: DropCursorProps) => ReactElement;
|
|
125
131
|
}
|
|
126
132
|
|
|
127
|
-
export type TreeProviderProps<T> = {
|
|
133
|
+
export type TreeProviderProps<T extends IdObj> = {
|
|
134
|
+
treeProps: TreeProps<T>;
|
|
128
135
|
imperativeHandle: React.Ref<TreeApi<T>> | undefined;
|
|
129
136
|
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
137
|
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
138
|
};
|
|
148
139
|
|
|
149
140
|
export type EditResult =
|
|
150
141
|
| { cancelled: true }
|
|
151
142
|
| { cancelled: false; value: string };
|
|
143
|
+
|
|
144
|
+
export type DropCursorProps = {
|
|
145
|
+
top: number;
|
|
146
|
+
left: number;
|
|
147
|
+
indent: number;
|
|
148
|
+
};
|
package/dist/tree-api-hook.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { Dispatch } from "react";
|
|
2
|
-
import { FixedSizeList } from "react-window";
|
|
3
|
-
import { Action } from "./reducer";
|
|
4
|
-
import { TreeApi } from "./tree-api";
|
|
5
|
-
import { StateContext, TreeProviderProps } from "./types";
|
|
6
|
-
export declare function useTreeApi<T>(state: StateContext, dispatch: Dispatch<Action>, props: TreeProviderProps<T>, list: FixedSizeList | undefined): TreeApi<T>;
|
package/src/tree-api-hook.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { Dispatch, useLayoutEffect, useMemo } from "react";
|
|
2
|
-
import { FixedSizeList } from "react-window";
|
|
3
|
-
import { Action, actions } from "./reducer";
|
|
4
|
-
import { TreeApi } from "./tree-api";
|
|
5
|
-
import { StateContext, TreeProviderProps } from "./types";
|
|
6
|
-
|
|
7
|
-
export function useTreeApi<T>(
|
|
8
|
-
state: StateContext,
|
|
9
|
-
dispatch: Dispatch<Action>,
|
|
10
|
-
props: TreeProviderProps<T>,
|
|
11
|
-
list: FixedSizeList | undefined
|
|
12
|
-
) {
|
|
13
|
-
/**
|
|
14
|
-
* We only ever want one instance of the api object
|
|
15
|
-
* It will get updated as the props change, but the
|
|
16
|
-
* reference will not.
|
|
17
|
-
*/
|
|
18
|
-
const api = useMemo(
|
|
19
|
-
() => new TreeApi<T>(dispatch, state, props, list),
|
|
20
|
-
// eslint-disable-next-line
|
|
21
|
-
[]
|
|
22
|
-
);
|
|
23
|
-
api.assign(dispatch, state, props, list);
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* This ensures that the selection remains correct even
|
|
27
|
-
* after opening and closing a folders
|
|
28
|
-
*/
|
|
29
|
-
useLayoutEffect(() => {
|
|
30
|
-
dispatch(actions.setVisibleIds(api.visibleIds, api.idToIndex));
|
|
31
|
-
}, [dispatch, api, props.root]);
|
|
32
|
-
|
|
33
|
-
return api;
|
|
34
|
-
}
|