react-arborist 1.2.0 → 2.0.0-rc.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 +606 -144
- package/dist/components/{drop-cursor.d.ts → cursor.d.ts} +0 -0
- package/dist/components/default-container.d.ts +7 -0
- package/dist/components/default-cursor.d.ts +3 -0
- package/dist/components/default-drag-preview.d.ts +3 -0
- package/dist/components/default-node.d.ts +4 -0
- package/dist/components/default-row.d.ts +4 -0
- package/dist/components/drag-preview-container.d.ts +2 -0
- package/dist/components/list-inner-element.d.ts +2 -0
- package/dist/components/provider.d.ts +11 -0
- package/dist/components/row-container.d.ts +8 -0
- package/dist/components/tree-container.d.ts +2 -0
- package/dist/components/tree.d.ts +5 -4
- package/dist/context.d.ts +20 -2
- package/dist/data/create-index.d.ts +5 -0
- package/dist/data/create-list.d.ts +4 -0
- package/dist/data/create-root.d.ts +5 -0
- package/dist/data/simple-tree.d.ts +43 -0
- package/dist/dnd/compute-drop.d.ts +4 -4
- package/dist/dnd/drag-hook.d.ts +3 -4
- package/dist/dnd/drop-hook.d.ts +2 -3
- package/dist/hooks/use-fresh-node.d.ts +2 -0
- package/dist/hooks/use-simple-tree.d.ts +13 -0
- package/dist/hooks/use-validated-props.d.ts +3 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.js +1948 -973
- package/dist/index.js.map +1 -1
- package/dist/interfaces/node-api.d.ts +67 -0
- package/dist/interfaces/tree-api.d.ts +113 -0
- package/dist/module.js +1935 -979
- package/dist/module.js.map +1 -1
- package/dist/state/dnd-slice.d.ts +20 -0
- package/dist/state/drag-slice.d.ts +7 -0
- package/dist/state/edit-slice.d.ts +8 -0
- package/dist/state/focus-slice.d.ts +12 -0
- package/dist/state/initial.d.ts +3 -0
- package/dist/state/open-slice.d.ts +30 -0
- package/dist/state/root-reducer.d.ts +13 -0
- package/dist/state/selection-slice.d.ts +36 -0
- package/dist/types/dnd.d.ts +9 -0
- package/dist/types/handlers.d.ts +24 -0
- package/dist/types/renderers.d.ts +30 -0
- package/dist/types/state.d.ts +2 -0
- package/dist/types/tree-props.d.ts +44 -0
- package/dist/types/utils.d.ts +21 -0
- package/dist/utils.d.ts +15 -6
- package/package.json +11 -7
- package/src/components/cursor.tsx +15 -0
- package/src/components/default-container.tsx +238 -0
- package/src/components/{default-drop-cursor.tsx → default-cursor.tsx} +9 -8
- package/src/components/{preview.tsx → default-drag-preview.tsx} +25 -41
- package/src/components/default-node.tsx +50 -0
- package/src/components/default-row.tsx +21 -0
- package/src/components/drag-preview-container.tsx +26 -0
- package/src/components/list-inner-element.tsx +22 -0
- package/src/components/list-outer-element.tsx +26 -15
- package/src/components/provider.tsx +97 -0
- package/src/components/row-container.tsx +82 -0
- package/src/components/tree-container.tsx +13 -0
- package/src/components/tree.tsx +16 -44
- package/src/context.ts +36 -0
- package/src/data/create-index.ts +9 -0
- package/src/data/create-list.ts +56 -0
- package/src/data/create-root.ts +54 -0
- package/src/data/simple-tree.ts +103 -0
- package/src/dnd/compute-drop.ts +16 -16
- package/src/dnd/drag-hook.ts +25 -19
- package/src/dnd/drop-hook.ts +31 -17
- package/src/dnd/outer-drop-hook.ts +1 -1
- package/src/hooks/use-fresh-node.ts +16 -0
- package/src/hooks/use-simple-tree.ts +55 -0
- package/src/hooks/use-validated-props.ts +35 -0
- package/src/index.ts +9 -19
- package/src/interfaces/node-api.ts +187 -0
- package/src/interfaces/tree-api.ts +557 -0
- package/src/state/dnd-slice.ts +36 -0
- package/src/state/drag-slice.ts +31 -0
- package/src/state/edit-slice.ts +19 -0
- package/src/state/focus-slice.ts +28 -0
- package/src/state/initial.ts +14 -0
- package/src/state/open-slice.ts +53 -0
- package/src/state/root-reducer.ts +21 -0
- package/src/state/selection-slice.ts +75 -0
- package/src/types/dnd.ts +10 -0
- package/src/types/handlers.ts +24 -0
- package/src/types/renderers.ts +34 -0
- package/src/types/state.ts +3 -0
- package/src/types/tree-props.ts +64 -0
- package/src/types/utils.ts +26 -0
- package/src/utils.ts +125 -11
- package/tsconfig.json +1 -1
- package/dist/components/default-drop-cursor.d.ts +0 -3
- package/dist/components/list.d.ts +0 -4
- package/dist/components/preview.d.ts +0 -2
- package/dist/components/row.d.ts +0 -8
- package/dist/data/enrich-tree.d.ts +0 -2
- package/dist/data/flatten-tree.d.ts +0 -2
- package/dist/provider.d.ts +0 -3
- package/dist/reducer.d.ts +0 -46
- package/dist/selection/range.d.ts +0 -13
- package/dist/selection/selection-hook.d.ts +0 -4
- package/dist/selection/selection.d.ts +0 -33
- package/dist/tree-api.d.ts +0 -50
- package/dist/types.d.ts +0 -122
- package/src/components/drop-cursor.tsx +0 -12
- package/src/components/list.tsx +0 -25
- package/src/components/row.tsx +0 -112
- package/src/context.tsx +0 -13
- package/src/data/enrich-tree.ts +0 -74
- package/src/data/flatten-tree.ts +0 -17
- package/src/provider.tsx +0 -41
- package/src/reducer.ts +0 -161
- package/src/selection/range.ts +0 -41
- package/src/selection/selection-hook.ts +0 -25
- package/src/selection/selection.test.ts +0 -111
- package/src/selection/selection.ts +0 -186
- package/src/tree-api.ts +0 -230
- package/src/types.ts +0 -148
package/dist/types.d.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import React, { ComponentType, CSSProperties, MouseEvent, MouseEventHandler, ReactElement, Ref } from "react";
|
|
2
|
-
import { Cursor } from "./dnd/compute-drop";
|
|
3
|
-
import { SelectionData } from "./selection/selection";
|
|
4
|
-
import { TreeApi } from "./tree-api";
|
|
5
|
-
declare module "react" {
|
|
6
|
-
function forwardRef<T, P = {}>(render: (props: P, ref: React.Ref<T>) => React.ReactElement | null): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
|
|
7
|
-
}
|
|
8
|
-
export declare type Node<T = unknown> = {
|
|
9
|
-
id: string;
|
|
10
|
-
model: T;
|
|
11
|
-
level: number;
|
|
12
|
-
children: Node<T>[] | null;
|
|
13
|
-
parent: Node<T> | null;
|
|
14
|
-
isOpen: boolean;
|
|
15
|
-
isDraggable: boolean;
|
|
16
|
-
isDroppable: boolean;
|
|
17
|
-
rowIndex: number | null;
|
|
18
|
-
};
|
|
19
|
-
export declare type NodesById<T> = {
|
|
20
|
-
[id: string]: Node<T>;
|
|
21
|
-
};
|
|
22
|
-
export interface IdObj {
|
|
23
|
-
id: string;
|
|
24
|
-
}
|
|
25
|
-
export declare type NodeRendererProps<T extends IdObj> = {
|
|
26
|
-
innerRef: (el: HTMLDivElement | null) => void;
|
|
27
|
-
styles: {
|
|
28
|
-
row: CSSProperties;
|
|
29
|
-
indent: CSSProperties;
|
|
30
|
-
};
|
|
31
|
-
data: T;
|
|
32
|
-
state: NodeState;
|
|
33
|
-
handlers: NodeHandlers;
|
|
34
|
-
tree: TreeApi<T>;
|
|
35
|
-
preview: boolean;
|
|
36
|
-
};
|
|
37
|
-
export declare type NodeState = {
|
|
38
|
-
isOpen: boolean;
|
|
39
|
-
isSelected: boolean;
|
|
40
|
-
isHoveringOverChild: boolean;
|
|
41
|
-
isDragging: boolean;
|
|
42
|
-
isSelectedStart: boolean;
|
|
43
|
-
isSelectedEnd: boolean;
|
|
44
|
-
isEditing: boolean;
|
|
45
|
-
};
|
|
46
|
-
export declare type NodeHandlers = {
|
|
47
|
-
toggle: MouseEventHandler;
|
|
48
|
-
select: (e: MouseEvent, args: {
|
|
49
|
-
selectOnClick: boolean;
|
|
50
|
-
}) => void;
|
|
51
|
-
edit: () => Promise<EditResult>;
|
|
52
|
-
submit: (name: string) => void;
|
|
53
|
-
reset: () => void;
|
|
54
|
-
};
|
|
55
|
-
export declare type NodeRenderer<T extends IdObj> = ComponentType<NodeRendererProps<T>>;
|
|
56
|
-
export declare type MoveHandler = (dragIds: string[], parentId: string | null, index: number) => void;
|
|
57
|
-
export declare type ToggleHandler = (id: string, isOpen: boolean) => void;
|
|
58
|
-
export declare type EditHandler = (id: string, name: string) => void;
|
|
59
|
-
export declare type NodeClickHandler<T> = (e: MouseEvent, n: Node<T>) => void;
|
|
60
|
-
export declare type IndexClickHandler = (e: MouseEvent, index: number) => void;
|
|
61
|
-
export declare type SelectedCheck = (index: number) => boolean;
|
|
62
|
-
export declare type CursorLocation = {
|
|
63
|
-
index: number | null;
|
|
64
|
-
level: number | null;
|
|
65
|
-
parentId: string | null;
|
|
66
|
-
};
|
|
67
|
-
export declare type DragItem = {
|
|
68
|
-
dragIds: string[];
|
|
69
|
-
id: string;
|
|
70
|
-
};
|
|
71
|
-
export declare type SelectionState = {
|
|
72
|
-
data: SelectionData | null;
|
|
73
|
-
ids: string[];
|
|
74
|
-
};
|
|
75
|
-
export declare type StateContext = {
|
|
76
|
-
cursor: Cursor;
|
|
77
|
-
editingId: string | null;
|
|
78
|
-
selection: SelectionState;
|
|
79
|
-
visibleIds: string[];
|
|
80
|
-
};
|
|
81
|
-
declare type BoolFunc<T> = (data: T) => boolean;
|
|
82
|
-
export interface TreeProps<T extends IdObj> {
|
|
83
|
-
children: NodeRenderer<T>;
|
|
84
|
-
className?: string | undefined;
|
|
85
|
-
data: T;
|
|
86
|
-
disableDrag?: string | boolean | BoolFunc<T>;
|
|
87
|
-
disableDrop?: string | boolean | BoolFunc<T>;
|
|
88
|
-
dndRootElement?: globalThis.Node | null;
|
|
89
|
-
getChildren?: string | ((d: T) => T[]);
|
|
90
|
-
handle?: Ref<TreeApi<T>>;
|
|
91
|
-
height?: number;
|
|
92
|
-
hideRoot?: boolean;
|
|
93
|
-
indent?: number;
|
|
94
|
-
isOpen?: string | BoolFunc<T>;
|
|
95
|
-
onClick?: MouseEventHandler;
|
|
96
|
-
onContextMenu?: MouseEventHandler;
|
|
97
|
-
onEdit?: EditHandler;
|
|
98
|
-
onMove?: MoveHandler;
|
|
99
|
-
onToggle?: ToggleHandler;
|
|
100
|
-
openByDefault?: boolean;
|
|
101
|
-
rowHeight?: number;
|
|
102
|
-
width?: number;
|
|
103
|
-
dropCursor?: (props: DropCursorProps) => ReactElement;
|
|
104
|
-
}
|
|
105
|
-
export declare type TreeProviderProps<T extends IdObj> = {
|
|
106
|
-
treeProps: TreeProps<T>;
|
|
107
|
-
imperativeHandle: React.Ref<TreeApi<T>> | undefined;
|
|
108
|
-
children: ReactElement;
|
|
109
|
-
root: Node<T>;
|
|
110
|
-
};
|
|
111
|
-
export declare type EditResult = {
|
|
112
|
-
cancelled: true;
|
|
113
|
-
} | {
|
|
114
|
-
cancelled: false;
|
|
115
|
-
value: string;
|
|
116
|
-
};
|
|
117
|
-
export declare type DropCursorProps = {
|
|
118
|
-
top: number;
|
|
119
|
-
left: number;
|
|
120
|
-
indent: number;
|
|
121
|
-
};
|
|
122
|
-
export {};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { useTreeApi } from "../context";
|
|
2
|
-
|
|
3
|
-
export function DropCursor() {
|
|
4
|
-
const tree = useTreeApi();
|
|
5
|
-
const cursor = tree.state.cursor;
|
|
6
|
-
if (!cursor || cursor.type !== "line") return null;
|
|
7
|
-
const indent = tree.indent;
|
|
8
|
-
const top = tree.rowHeight * cursor.index;
|
|
9
|
-
const left = indent * cursor.level;
|
|
10
|
-
|
|
11
|
-
return tree.renderDropCursor({ top, left, indent });
|
|
12
|
-
}
|
package/src/components/list.tsx
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { FixedSizeList } from "react-window";
|
|
2
|
-
import { useTreeApi } from "../context";
|
|
3
|
-
import { ListOuterElement } from "./list-outer-element";
|
|
4
|
-
import { Row } from "./row";
|
|
5
|
-
|
|
6
|
-
export function List(props: { className?: string }) {
|
|
7
|
-
const tree = useTreeApi();
|
|
8
|
-
return (
|
|
9
|
-
<div style={{ height: tree.height, width: tree.width, overflow: "hidden" }}>
|
|
10
|
-
<FixedSizeList
|
|
11
|
-
className={props.className}
|
|
12
|
-
outerRef={tree.listEl}
|
|
13
|
-
itemCount={tree.visibleNodes.length}
|
|
14
|
-
height={tree.height}
|
|
15
|
-
width={tree.width}
|
|
16
|
-
itemSize={tree.rowHeight}
|
|
17
|
-
itemKey={(index) => tree.visibleNodes[index]?.id || index}
|
|
18
|
-
outerElementType={ListOuterElement}
|
|
19
|
-
ref={tree.list}
|
|
20
|
-
>
|
|
21
|
-
{Row}
|
|
22
|
-
</FixedSizeList>
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
}
|
package/src/components/row.tsx
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useMemo, useRef } from "react";
|
|
2
|
-
import { useTreeApi } from "../context";
|
|
3
|
-
import { useDragHook } from "../dnd/drag-hook";
|
|
4
|
-
import { useDropHook } from "../dnd/drop-hook";
|
|
5
|
-
import { IdObj } from "../types";
|
|
6
|
-
|
|
7
|
-
type Props = {
|
|
8
|
-
style: React.CSSProperties;
|
|
9
|
-
index: number;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export const Row = React.memo(function Row<T extends IdObj>({
|
|
13
|
-
index,
|
|
14
|
-
style,
|
|
15
|
-
}: Props) {
|
|
16
|
-
const realTree = useTreeApi<T>();
|
|
17
|
-
const tree = useMemo(() => realTree, []);
|
|
18
|
-
tree.sync(realTree);
|
|
19
|
-
|
|
20
|
-
const node = tree.visibleNodes[index];
|
|
21
|
-
const next = tree.visibleNodes[index + 1] || null;
|
|
22
|
-
const prev = tree.visibleNodes[index - 1] || null;
|
|
23
|
-
const el = useRef<HTMLDivElement | null>(null);
|
|
24
|
-
const [{ isDragging }, dragRef] = useDragHook(node);
|
|
25
|
-
const [, dropRef] = useDropHook(el, node, prev, next);
|
|
26
|
-
const isEditing = node.id === tree.editingId;
|
|
27
|
-
const isSelected = tree.isSelected(index);
|
|
28
|
-
const nextSelected = next && tree.isSelected(index + 1);
|
|
29
|
-
const prevSelected = prev && tree.isSelected(index - 1);
|
|
30
|
-
const isHoveringOverChild = node.id === tree.cursorParentId;
|
|
31
|
-
const isOverFolder = node.id === tree.cursorParentId && tree.cursorOverFolder;
|
|
32
|
-
const isOpen = node.isOpen;
|
|
33
|
-
const indent = tree.indent * node.level;
|
|
34
|
-
const state = useMemo(() => {
|
|
35
|
-
return {
|
|
36
|
-
isEditing,
|
|
37
|
-
isDragging,
|
|
38
|
-
isSelectedStart: isSelected && !prevSelected,
|
|
39
|
-
isSelectedEnd: isSelected && !nextSelected,
|
|
40
|
-
isSelected,
|
|
41
|
-
isHoveringOverChild,
|
|
42
|
-
isOpen,
|
|
43
|
-
isOverFolder,
|
|
44
|
-
};
|
|
45
|
-
}, [
|
|
46
|
-
isEditing,
|
|
47
|
-
isSelected,
|
|
48
|
-
prevSelected,
|
|
49
|
-
nextSelected,
|
|
50
|
-
isHoveringOverChild,
|
|
51
|
-
isOpen,
|
|
52
|
-
isDragging,
|
|
53
|
-
isOverFolder,
|
|
54
|
-
]);
|
|
55
|
-
|
|
56
|
-
const ref = useCallback(
|
|
57
|
-
(n: HTMLDivElement | null) => {
|
|
58
|
-
el.current = n;
|
|
59
|
-
dragRef(dropRef(n));
|
|
60
|
-
},
|
|
61
|
-
[dragRef, dropRef]
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const styles = useMemo(
|
|
65
|
-
() => ({
|
|
66
|
-
row: { ...style },
|
|
67
|
-
indent: { paddingLeft: indent },
|
|
68
|
-
}),
|
|
69
|
-
[indent, style]
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
const handlers = useMemo(() => {
|
|
73
|
-
return {
|
|
74
|
-
select: (
|
|
75
|
-
e: React.MouseEvent,
|
|
76
|
-
args: { selectOnClick: boolean } = { selectOnClick: true }
|
|
77
|
-
) => {
|
|
78
|
-
if (node.rowIndex === null) return;
|
|
79
|
-
if (args.selectOnClick || e.metaKey || e.shiftKey) {
|
|
80
|
-
tree.select(node.rowIndex, e.metaKey, e.shiftKey);
|
|
81
|
-
} else {
|
|
82
|
-
tree.select(null, false, false);
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
toggle: (e: React.MouseEvent) => {
|
|
86
|
-
e.stopPropagation();
|
|
87
|
-
tree.onToggle(node.id, !node.isOpen);
|
|
88
|
-
},
|
|
89
|
-
edit: () => tree.edit(node.id),
|
|
90
|
-
submit: (name: string) => {
|
|
91
|
-
name.trim() ? tree.submit(node.id, name) : tree.reset(node.id);
|
|
92
|
-
},
|
|
93
|
-
reset: () => tree.reset(node.id),
|
|
94
|
-
};
|
|
95
|
-
}, [node, tree]);
|
|
96
|
-
|
|
97
|
-
const Renderer = useMemo(() => {
|
|
98
|
-
return React.memo(tree.renderer);
|
|
99
|
-
}, [tree.renderer]);
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<Renderer
|
|
103
|
-
innerRef={ref}
|
|
104
|
-
data={node.model}
|
|
105
|
-
styles={styles}
|
|
106
|
-
state={state}
|
|
107
|
-
handlers={handlers}
|
|
108
|
-
preview={false}
|
|
109
|
-
tree={tree}
|
|
110
|
-
/>
|
|
111
|
-
);
|
|
112
|
-
});
|
package/src/context.tsx
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useContext, useMemo } from "react";
|
|
2
|
-
import { TreeApi } from "./tree-api";
|
|
3
|
-
import { IdObj } from "./types";
|
|
4
|
-
|
|
5
|
-
export const TreeApiContext = createContext<TreeApi<any> | null>(null);
|
|
6
|
-
|
|
7
|
-
export function useTreeApi<T extends IdObj>() {
|
|
8
|
-
const value = useContext<TreeApi<T> | null>(
|
|
9
|
-
TreeApiContext as unknown as React.Context<TreeApi<T> | null>
|
|
10
|
-
);
|
|
11
|
-
if (value === null) throw new Error("No Tree Api Provided");
|
|
12
|
-
return value;
|
|
13
|
-
}
|
package/src/data/enrich-tree.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { TreeProps, IdObj, Node } from "../types";
|
|
2
|
-
|
|
3
|
-
function createNode<T extends IdObj>(
|
|
4
|
-
model: T,
|
|
5
|
-
level: number,
|
|
6
|
-
parent: Node<T> | null,
|
|
7
|
-
children: Node<T>[] | null,
|
|
8
|
-
isOpen: boolean,
|
|
9
|
-
isDraggable: boolean,
|
|
10
|
-
isDroppable: boolean
|
|
11
|
-
): Node<T> {
|
|
12
|
-
return {
|
|
13
|
-
id: model.id,
|
|
14
|
-
level,
|
|
15
|
-
parent,
|
|
16
|
-
children,
|
|
17
|
-
isOpen,
|
|
18
|
-
isDraggable,
|
|
19
|
-
isDroppable,
|
|
20
|
-
model,
|
|
21
|
-
rowIndex: null,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function access(obj: any, accessor: string | boolean | Function) {
|
|
26
|
-
if (typeof accessor === "boolean") {
|
|
27
|
-
return accessor;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (typeof accessor === "string") {
|
|
31
|
-
return obj[accessor];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return accessor(obj);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function enrichTree<T extends IdObj>(
|
|
38
|
-
model: T,
|
|
39
|
-
hideRoot: boolean = false,
|
|
40
|
-
getChildren: TreeProps<T>["getChildren"] = "children",
|
|
41
|
-
isOpen: TreeProps<T>["isOpen"] = "isOpen",
|
|
42
|
-
disableDrag: TreeProps<T>["disableDrag"] = false,
|
|
43
|
-
disableDrop: TreeProps<T>["disableDrop"] = false,
|
|
44
|
-
openByDefault: boolean = true
|
|
45
|
-
): Node<T> {
|
|
46
|
-
function visitSelfAndChildren(
|
|
47
|
-
model: T,
|
|
48
|
-
level: number,
|
|
49
|
-
parent: Node<T> | null
|
|
50
|
-
) {
|
|
51
|
-
const open = access(model, isOpen) as boolean;
|
|
52
|
-
const draggable = !access(model, disableDrag) as boolean;
|
|
53
|
-
const droppable = !access(model, disableDrop) as boolean;
|
|
54
|
-
const node = createNode<T>(
|
|
55
|
-
model,
|
|
56
|
-
level,
|
|
57
|
-
parent,
|
|
58
|
-
null,
|
|
59
|
-
open === undefined ? openByDefault : open,
|
|
60
|
-
draggable,
|
|
61
|
-
droppable
|
|
62
|
-
);
|
|
63
|
-
const children = access(model, getChildren) as T[];
|
|
64
|
-
|
|
65
|
-
if (children) {
|
|
66
|
-
node.children = children.map((child: T) =>
|
|
67
|
-
visitSelfAndChildren(child, level + 1, node)
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
return node;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return visitSelfAndChildren(model, hideRoot ? -1 : 0, null);
|
|
74
|
-
}
|
package/src/data/flatten-tree.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Node } from "../types";
|
|
2
|
-
|
|
3
|
-
export function flattenTree<T>(root: Node<T>): Node<T>[] {
|
|
4
|
-
const list: Node<T>[] = [];
|
|
5
|
-
let index = 0;
|
|
6
|
-
function collect(node: Node<T>) {
|
|
7
|
-
if (node.level >= 0) {
|
|
8
|
-
node.rowIndex = index++;
|
|
9
|
-
list.push(node);
|
|
10
|
-
}
|
|
11
|
-
if (node.isOpen) {
|
|
12
|
-
node.children?.forEach(collect);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
collect(root);
|
|
16
|
-
return list;
|
|
17
|
-
}
|
package/src/provider.tsx
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
11
|
-
import { useSelectionKeys } from "./selection/selection-hook";
|
|
12
|
-
import { TreeApi } from "./tree-api";
|
|
13
|
-
import { IdObj, TreeProviderProps } from "./types";
|
|
14
|
-
|
|
15
|
-
export function TreeViewProvider<T extends IdObj>(props: TreeProviderProps<T>) {
|
|
16
|
-
const [state, dispatch] = useReducer(reducer, initState());
|
|
17
|
-
const list = useRef<FixedSizeList | null>(null);
|
|
18
|
-
const listEl = useRef<HTMLDivElement | null>(null);
|
|
19
|
-
|
|
20
|
-
const api = useMemo(
|
|
21
|
-
() => new TreeApi<T>(dispatch, state, props, list, listEl),
|
|
22
|
-
[dispatch, state, props, list, listEl]
|
|
23
|
-
);
|
|
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.visibleIds, api.idToIndex, props.root]);
|
|
32
|
-
|
|
33
|
-
useImperativeHandle(props.imperativeHandle, () => api);
|
|
34
|
-
useSelectionKeys(listEl, api);
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<TreeApiContext.Provider value={api}>
|
|
38
|
-
{props.children}
|
|
39
|
-
</TreeApiContext.Provider>
|
|
40
|
-
);
|
|
41
|
-
}
|
package/src/reducer.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { Cursor } from "./dnd/compute-drop";
|
|
2
|
-
import { Selection } from "./selection/selection";
|
|
3
|
-
import { StateContext } from "./types";
|
|
4
|
-
|
|
5
|
-
export const initState = (): StateContext => ({
|
|
6
|
-
visibleIds: [],
|
|
7
|
-
cursor: { type: "none" } as Cursor,
|
|
8
|
-
editingId: null,
|
|
9
|
-
selection: {
|
|
10
|
-
data: null,
|
|
11
|
-
ids: [],
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
export const actions = {
|
|
16
|
-
setCursorLocation: (cursor: Cursor) => ({
|
|
17
|
-
type: "SET_CURSOR_LOCATION" as "SET_CURSOR_LOCATION",
|
|
18
|
-
cursor,
|
|
19
|
-
}),
|
|
20
|
-
|
|
21
|
-
setVisibleIds: (
|
|
22
|
-
ids: string[], // index to id
|
|
23
|
-
idMap: { [id: string]: number } // id to index
|
|
24
|
-
) => ({
|
|
25
|
-
type: "SET_VISIBLE_IDS" as "SET_VISIBLE_IDS",
|
|
26
|
-
ids,
|
|
27
|
-
idMap,
|
|
28
|
-
}),
|
|
29
|
-
|
|
30
|
-
select: (index: number | null, meta: boolean, shift: boolean) => ({
|
|
31
|
-
type: "SELECT" as "SELECT",
|
|
32
|
-
index,
|
|
33
|
-
meta,
|
|
34
|
-
shift,
|
|
35
|
-
}),
|
|
36
|
-
|
|
37
|
-
selectId: (id: string) => ({
|
|
38
|
-
type: "SELECT_ID" as "SELECT_ID",
|
|
39
|
-
id,
|
|
40
|
-
}),
|
|
41
|
-
|
|
42
|
-
edit: (id: string | null) => ({
|
|
43
|
-
type: "EDIT" as "EDIT",
|
|
44
|
-
id,
|
|
45
|
-
}),
|
|
46
|
-
|
|
47
|
-
stepUp: (shift: boolean, ids: string[]) => ({
|
|
48
|
-
type: "STEP_UP" as "STEP_UP",
|
|
49
|
-
shift,
|
|
50
|
-
}),
|
|
51
|
-
|
|
52
|
-
stepDown: (shift: boolean, ids: string[]) => ({
|
|
53
|
-
type: "STEP_DOWN" as "STEP_DOWN",
|
|
54
|
-
shift,
|
|
55
|
-
}),
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
type ActionObj = {
|
|
59
|
-
[Prop in keyof typeof actions]: ReturnType<typeof actions[Prop]>;
|
|
60
|
-
};
|
|
61
|
-
export type Action = ActionObj[keyof ActionObj];
|
|
62
|
-
|
|
63
|
-
export function reducer(state: StateContext, action: Action): StateContext {
|
|
64
|
-
switch (action.type) {
|
|
65
|
-
case "EDIT":
|
|
66
|
-
return {
|
|
67
|
-
...state,
|
|
68
|
-
editingId: action.id,
|
|
69
|
-
};
|
|
70
|
-
case "SET_CURSOR_LOCATION":
|
|
71
|
-
if (equal(state.cursor, action.cursor)) {
|
|
72
|
-
return state;
|
|
73
|
-
} else {
|
|
74
|
-
return { ...state, cursor: action.cursor };
|
|
75
|
-
}
|
|
76
|
-
case "SELECT":
|
|
77
|
-
var s = Selection.parse(state.selection.data, state.visibleIds);
|
|
78
|
-
if (action.index === null) {
|
|
79
|
-
s.clear();
|
|
80
|
-
} else if (action.meta) {
|
|
81
|
-
if (s.contains(action.index)) {
|
|
82
|
-
s.deselect(action.index);
|
|
83
|
-
} else {
|
|
84
|
-
s.multiSelect(action.index);
|
|
85
|
-
}
|
|
86
|
-
} else if (action.shift) {
|
|
87
|
-
s.extend(action.index);
|
|
88
|
-
} else {
|
|
89
|
-
s.select(action.index);
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
...state,
|
|
93
|
-
selection: {
|
|
94
|
-
data: s.serialize(),
|
|
95
|
-
ids: s.getSelectedItems(),
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
case "SELECT_ID":
|
|
99
|
-
return {
|
|
100
|
-
...state,
|
|
101
|
-
selection: {
|
|
102
|
-
...state.selection,
|
|
103
|
-
ids: [action.id],
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
case "STEP_UP":
|
|
107
|
-
var s3 = Selection.parse(state.selection.data, state.visibleIds);
|
|
108
|
-
var f = s3.getFocus();
|
|
109
|
-
if (action.shift) {
|
|
110
|
-
s3.extend(f - 1);
|
|
111
|
-
} else {
|
|
112
|
-
s3.select(f - 1);
|
|
113
|
-
}
|
|
114
|
-
return {
|
|
115
|
-
...state,
|
|
116
|
-
selection: {
|
|
117
|
-
data: s3.serialize(),
|
|
118
|
-
ids: s3.getSelectedItems(),
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
case "STEP_DOWN":
|
|
122
|
-
var s6 = Selection.parse(state.selection.data, state.visibleIds);
|
|
123
|
-
var f2 = s6.getFocus();
|
|
124
|
-
if (action.shift) {
|
|
125
|
-
s6.extend(f2 + 1);
|
|
126
|
-
} else {
|
|
127
|
-
s6.select(f2 + 1);
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
...state,
|
|
131
|
-
selection: {
|
|
132
|
-
data: s6.serialize(),
|
|
133
|
-
ids: s6.getSelectedItems(),
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
case "SET_VISIBLE_IDS":
|
|
137
|
-
// The visible ids changed
|
|
138
|
-
var ids = state.selection.ids;
|
|
139
|
-
// Start with a blank selection
|
|
140
|
-
var s2 = new Selection([], null, "none", state.visibleIds);
|
|
141
|
-
// Add each of the old selected ids to this new selection
|
|
142
|
-
for (let id of ids) {
|
|
143
|
-
if (id in action.idMap) s2.multiSelect(action.idMap[id]);
|
|
144
|
-
}
|
|
145
|
-
return {
|
|
146
|
-
...state,
|
|
147
|
-
visibleIds: action.ids,
|
|
148
|
-
selection: {
|
|
149
|
-
ids,
|
|
150
|
-
data: s2.serialize(),
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
default:
|
|
154
|
-
return state;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function equal(a: Cursor | null, b: Cursor | null) {
|
|
159
|
-
if (a === null || b === null) return false;
|
|
160
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
161
|
-
}
|
package/src/selection/range.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export class Range {
|
|
2
|
-
constructor(public start: number, public end: number) {
|
|
3
|
-
if (this.start > this.end)
|
|
4
|
-
throw new Error("Invalid range: start larger than end");
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
serialize(): [number, number] {
|
|
8
|
-
return [this.start, this.end];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
contains(n: number) {
|
|
12
|
-
return n >= this.start && n <= this.end;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
overlaps(r: Range) {
|
|
16
|
-
return this.contains(r.start - 1) || this.contains(r.end + 1);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
combine(r: Range) {
|
|
20
|
-
this.start = Math.min(r.start, this.start);
|
|
21
|
-
this.end = Math.max(r.end, this.end);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
get size() {
|
|
25
|
-
return this.end - this.start + 1;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
clone() {
|
|
29
|
-
return new Range(this.start, this.end);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
map(fn: (index: any) => string): any {
|
|
33
|
-
let returns = [];
|
|
34
|
-
for (let i = this.start; i <= this.end; i++) returns.push(fn(i));
|
|
35
|
-
return returns;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
isEqual(other: Range) {
|
|
39
|
-
return this.start === other.start && this.end === other.end;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { MutableRefObject, useEffect } from "react";
|
|
2
|
-
import { TreeApi } from "../tree-api";
|
|
3
|
-
import { IdObj } from "../types";
|
|
4
|
-
|
|
5
|
-
export function useSelectionKeys<T extends IdObj>(
|
|
6
|
-
ref: MutableRefObject<HTMLDivElement | null>,
|
|
7
|
-
api: TreeApi<T>
|
|
8
|
-
) {
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
const el = ref.current;
|
|
11
|
-
const cb = (e: KeyboardEvent) => {
|
|
12
|
-
if (e.code === "ArrowDown") {
|
|
13
|
-
e.preventDefault();
|
|
14
|
-
api.selectDownwards(e.shiftKey);
|
|
15
|
-
} else if (e.code === "ArrowUp") {
|
|
16
|
-
e.preventDefault();
|
|
17
|
-
api.selectUpwards(e.shiftKey);
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
el?.addEventListener("keydown", cb);
|
|
21
|
-
return () => {
|
|
22
|
-
el?.removeEventListener("keydown", cb);
|
|
23
|
-
};
|
|
24
|
-
}, [ref, api]);
|
|
25
|
-
}
|