react-arborist 1.1.0 → 2.0.0-rc

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.
Files changed (122) hide show
  1. package/dist/components/{drop-cursor.d.ts → cursor.d.ts} +0 -0
  2. package/dist/components/default-container.d.ts +2 -0
  3. package/dist/components/default-cursor.d.ts +3 -0
  4. package/dist/components/default-drag-preview.d.ts +3 -0
  5. package/dist/components/default-node.d.ts +4 -0
  6. package/dist/components/default-row.d.ts +4 -0
  7. package/dist/components/drag-preview-container.d.ts +2 -0
  8. package/dist/components/list-inner-element.d.ts +2 -0
  9. package/dist/components/list-outer-element.d.ts +2 -0
  10. package/dist/components/outer-drop.d.ts +4 -0
  11. package/dist/components/provider.d.ts +11 -0
  12. package/dist/components/row-container.d.ts +8 -0
  13. package/dist/components/tree-container.d.ts +2 -0
  14. package/dist/components/tree.d.ts +5 -4
  15. package/dist/context.d.ts +23 -18
  16. package/dist/data/create-index.d.ts +5 -0
  17. package/dist/data/create-list.d.ts +4 -0
  18. package/dist/data/create-root.d.ts +5 -0
  19. package/dist/data/flatten-tree.d.ts +4 -2
  20. package/dist/data/simple-tree.d.ts +43 -0
  21. package/dist/dnd/compute-drop.d.ts +4 -4
  22. package/dist/dnd/drag-hook.d.ts +3 -4
  23. package/dist/dnd/drop-hook.d.ts +2 -3
  24. package/dist/hooks/use-fresh-node.d.ts +2 -0
  25. package/dist/hooks/use-simple-tree.d.ts +13 -0
  26. package/dist/hooks/use-uncontrolled-tree.d.ts +24 -0
  27. package/dist/hooks/use-validated-props.d.ts +3 -0
  28. package/dist/index.d.ts +8 -4
  29. package/dist/index.js +2093 -1184
  30. package/dist/index.js.map +1 -1
  31. package/dist/interfaces/node-api.d.ts +67 -0
  32. package/dist/interfaces/tree-api.d.ts +112 -0
  33. package/dist/module.js +2082 -1192
  34. package/dist/module.js.map +1 -1
  35. package/dist/state/dnd-slice.d.ts +20 -0
  36. package/dist/state/drag-slice.d.ts +7 -0
  37. package/dist/state/edit-slice.d.ts +8 -0
  38. package/dist/state/focus-slice.d.ts +12 -0
  39. package/dist/state/initial.d.ts +3 -0
  40. package/dist/state/open-slice.d.ts +30 -0
  41. package/dist/state/root-reducer.d.ts +13 -0
  42. package/dist/state/selection-slice.d.ts +36 -0
  43. package/dist/types/dnd.d.ts +9 -0
  44. package/dist/types/handlers.d.ts +24 -0
  45. package/dist/types/renderers.d.ts +30 -0
  46. package/dist/types/state.d.ts +2 -0
  47. package/dist/types/tree-props.d.ts +43 -0
  48. package/dist/types/utils.d.ts +21 -0
  49. package/dist/utils/props.d.ts +3 -0
  50. package/dist/utils.d.ts +15 -6
  51. package/package.json +10 -7
  52. package/src/components/cursor.tsx +15 -0
  53. package/src/components/default-container.tsx +229 -0
  54. package/src/components/{drop-cursor.tsx → default-cursor.tsx} +15 -20
  55. package/src/components/default-drag-preview.tsx +92 -0
  56. package/src/components/default-node.tsx +15 -0
  57. package/src/components/default-row.tsx +21 -0
  58. package/src/components/drag-preview-container.tsx +26 -0
  59. package/src/components/list-inner-element.tsx +22 -0
  60. package/src/components/list-outer-element.tsx +45 -0
  61. package/src/components/outer-drop.ts +7 -0
  62. package/src/components/provider.tsx +97 -0
  63. package/src/components/row-container.tsx +82 -0
  64. package/src/components/tree-container.tsx +13 -0
  65. package/src/components/tree.tsx +17 -126
  66. package/src/context.ts +36 -0
  67. package/src/data/create-index.ts +9 -0
  68. package/src/data/create-list.ts +56 -0
  69. package/src/data/create-root.ts +53 -0
  70. package/src/data/simple-tree.ts +103 -0
  71. package/src/dnd/compute-drop.ts +16 -16
  72. package/src/dnd/drag-hook.ts +28 -23
  73. package/src/dnd/drop-hook.ts +35 -21
  74. package/src/dnd/outer-drop-hook.ts +6 -6
  75. package/src/hooks/use-fresh-node.ts +16 -0
  76. package/src/hooks/use-simple-tree.ts +55 -0
  77. package/src/hooks/use-validated-props.ts +35 -0
  78. package/src/index.ts +9 -5
  79. package/src/interfaces/node-api.ts +187 -0
  80. package/src/interfaces/tree-api.ts +552 -0
  81. package/src/state/dnd-slice.ts +36 -0
  82. package/src/state/drag-slice.ts +31 -0
  83. package/src/state/edit-slice.ts +19 -0
  84. package/src/state/focus-slice.ts +28 -0
  85. package/src/state/initial.ts +14 -0
  86. package/src/state/open-slice.ts +53 -0
  87. package/src/state/root-reducer.ts +21 -0
  88. package/src/state/selection-slice.ts +75 -0
  89. package/src/types/dnd.ts +10 -0
  90. package/src/types/handlers.ts +24 -0
  91. package/src/types/renderers.ts +34 -0
  92. package/src/types/state.ts +3 -0
  93. package/src/types/tree-props.ts +63 -0
  94. package/src/types/utils.ts +26 -0
  95. package/src/utils/props.ts +8 -0
  96. package/src/utils.ts +125 -11
  97. package/README.md +0 -220
  98. package/dist/components/preview.d.ts +0 -2
  99. package/dist/components/row.d.ts +0 -7
  100. package/dist/data/enrich-tree.d.ts +0 -2
  101. package/dist/provider.d.ts +0 -3
  102. package/dist/reducer.d.ts +0 -46
  103. package/dist/selection/range.d.ts +0 -13
  104. package/dist/selection/selection-hook.d.ts +0 -3
  105. package/dist/selection/selection.d.ts +0 -33
  106. package/dist/tree-api-hook.d.ts +0 -6
  107. package/dist/tree-api.d.ts +0 -34
  108. package/dist/types.d.ts +0 -131
  109. package/src/components/preview.tsx +0 -108
  110. package/src/components/row.tsx +0 -114
  111. package/src/context.tsx +0 -52
  112. package/src/data/enrich-tree.ts +0 -74
  113. package/src/data/flatten-tree.ts +0 -17
  114. package/src/provider.tsx +0 -61
  115. package/src/reducer.ts +0 -161
  116. package/src/selection/range.ts +0 -41
  117. package/src/selection/selection-hook.ts +0 -24
  118. package/src/selection/selection.test.ts +0 -111
  119. package/src/selection/selection.ts +0 -186
  120. package/src/tree-api-hook.ts +0 -34
  121. package/src/tree-api.ts +0 -156
  122. package/src/types.ts +0 -155
@@ -1,186 +0,0 @@
1
- import { Range } from "./range";
2
-
3
- type SelectionDirection = "forward" | "backward" | "none";
4
-
5
- export type SelectionData = {
6
- ranges: [number, number][];
7
- currentIndex: number | null;
8
- direction: SelectionDirection;
9
- };
10
-
11
- export class Selection {
12
- ranges: Range[] = [];
13
- currentIndex: number | null;
14
- direction: SelectionDirection = "none";
15
- items: any[];
16
-
17
- static parse(data: SelectionData | null, items: any[]) {
18
- if (data) {
19
- return new Selection(
20
- data.ranges,
21
- data.currentIndex,
22
- data.direction,
23
- items
24
- );
25
- } else {
26
- return new Selection();
27
- }
28
- }
29
-
30
- constructor(
31
- ranges: [number, number][] = [],
32
- currentIndex: number | null = ranges.length ? ranges.length - 1 : null,
33
- direction: SelectionDirection = "none",
34
- items: any[] = []
35
- ) {
36
- ranges.forEach(([s, e]) => this.addRange(s, e));
37
- this.currentIndex = currentIndex;
38
- this.direction = direction;
39
- this.items = items;
40
- }
41
-
42
- get current() {
43
- if (this.currentIndex === null) return null;
44
- const range = this.ranges[this.currentIndex];
45
- if (!range) {
46
- return null;
47
- } else {
48
- return range;
49
- }
50
- }
51
-
52
- select(n: number) {
53
- if (n < 0 || n >= this.items.length) return;
54
- this.clear();
55
- this.currentIndex = this.addRange(n, n);
56
- }
57
-
58
- multiSelect(n: number) {
59
- if (n < 0 || n >= this.items.length) return;
60
- if (this.contains(n)) return;
61
- this.currentIndex = this.addRange(n, n);
62
- this.compact(n);
63
- }
64
-
65
- deselect(n: number) {
66
- if (n < 0 || n >= this.items.length) return;
67
- const r = this.ranges.find((r) => r.contains(n));
68
- if (!r) return;
69
- else if (r.size === 1) this.removeRange(r);
70
- else if (r.start === n) r.start++;
71
- else if (r.end === n) r.end--;
72
- else {
73
- this.removeRange(r);
74
- this.addRange(r.start, n - 1);
75
- this.currentIndex = this.addRange(n + 1, r.end);
76
- }
77
- }
78
-
79
- getSelectedItems<T>(): T[] {
80
- return this.ranges.flatMap((range) =>
81
- range.map((index) => this.items[index])
82
- );
83
- }
84
-
85
- extend(n: number) {
86
- if (n < 0 || n >= this.items.length) return;
87
- if (this.isEmpty()) {
88
- this.select(n);
89
- } else {
90
- const anchor = this.getAnchor();
91
- if (anchor !== null && this.current) {
92
- const [start, end] = [n, anchor].sort((a, b) => a - b);
93
- this.current.start = start;
94
- this.current.end = end;
95
- this.compact(n);
96
- }
97
- }
98
- }
99
-
100
- contains(n: number | null) {
101
- if (n === null) return false;
102
- return this.ranges.some((r) => r.contains(n));
103
- }
104
-
105
- getRanges() {
106
- return this.ranges.map((r) => r.serialize());
107
- }
108
-
109
- clear() {
110
- this.ranges = [];
111
- this.currentIndex = null;
112
- this.direction = "none";
113
- }
114
-
115
- serialize(): SelectionData {
116
- return {
117
- ranges: this.getRanges(),
118
- currentIndex: this.currentIndex,
119
- direction: this.direction,
120
- };
121
- }
122
-
123
- isEqual(other: Selection) {
124
- if (other.ranges.length !== this.ranges.length) return false;
125
-
126
- for (let i = 0; i < this.ranges.length; ++i) {
127
- if (!this.ranges[i].isEqual(other.ranges[i])) return false;
128
- }
129
- return true;
130
- }
131
-
132
- private addRange(start: number, end: number) {
133
- const r = new Range(start, end);
134
- // Keep ranges sorted by start
135
- const index = this.ranges.findIndex((r) => r.start >= start);
136
- if (index === -1) this.ranges.push(r);
137
- else this.ranges.splice(index, 0, r);
138
- return index === -1 ? this.ranges.length - 1 : index;
139
- }
140
-
141
- private removeRange(r: Range) {
142
- const index = this.ranges.indexOf(r);
143
- this.ranges.splice(index, 1);
144
- if (this.isEmpty()) {
145
- this.currentIndex = null;
146
- } else if (index === this.currentIndex) {
147
- this.currentIndex = this.ranges.length - 1;
148
- }
149
- }
150
-
151
- private isEmpty() {
152
- return this.ranges.length === 0;
153
- }
154
-
155
- getAnchor() {
156
- if (!this.current) return null;
157
- return this.direction === "backward"
158
- ? this.current.end
159
- : this.current.start;
160
- }
161
-
162
- getFocus() {
163
- if (!this.current) return -1;
164
- return this.direction === "backward"
165
- ? this.current.start
166
- : this.current.end;
167
- }
168
-
169
- private compact(focus: number) {
170
- const removals = [];
171
- const current = this.current;
172
- for (let r of this.ranges) {
173
- if (!this.current || r === this.current) continue;
174
- if (this.current.overlaps(r)) {
175
- this.current.combine(r);
176
- removals.push(r);
177
- }
178
- }
179
- removals.forEach((r) => this.removeRange(r));
180
- if (current) this.currentIndex = this.ranges.indexOf(current);
181
- if (!this.current) return;
182
- if (this.current.start < focus) this.direction = "forward";
183
- else if (this.current.end > focus) this.direction = "backward";
184
- else this.direction = "none";
185
- }
186
- }
@@ -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
- }
package/src/tree-api.ts DELETED
@@ -1,156 +0,0 @@
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, EditResult } from "./types";
8
- import ReactDOM from "react-dom";
9
-
10
- export class TreeApi<T = unknown> {
11
- private edits = new Map<string, (args: EditResult) => void>();
12
-
13
- constructor(
14
- public dispatch: Dispatch<Action>,
15
- public state: StateContext,
16
- public props: TreeProviderProps<T>,
17
- public list: FixedSizeList | undefined
18
- ) {}
19
-
20
- assign(
21
- dispatch: Dispatch<Action>,
22
- state: StateContext,
23
- props: TreeProviderProps<T>,
24
- list: FixedSizeList | undefined
25
- ) {
26
- this.dispatch = dispatch;
27
- this.state = state;
28
- this.props = props;
29
- this.list = list;
30
- }
31
-
32
- getNode(id: string): Node<unknown> | null {
33
- if (id in this.idToIndex)
34
- return this.visibleNodes[this.idToIndex[id]] || null;
35
- else return null;
36
- }
37
-
38
- getSelectedIds() {
39
- return this.state.selection.ids;
40
- }
41
-
42
- edit(id: string | number): Promise<EditResult> {
43
- const sid = id.toString();
44
- this.resolveEdit(sid, { cancelled: true });
45
- this.scrollToId(sid);
46
- this.dispatch(actions.edit(sid));
47
- return new Promise((resolve) => this.edits.set(sid, resolve));
48
- }
49
-
50
- submit(id: string | number, value: string) {
51
- const sid = id.toString();
52
- this.props.onEdit(sid, value);
53
- this.dispatch(actions.edit(null));
54
- this.resolveEdit(sid, { cancelled: false, value });
55
- }
56
-
57
- reset(id: string | number) {
58
- const sid = id.toString();
59
- this.dispatch(actions.edit(null));
60
- this.resolveEdit(sid, { cancelled: true });
61
- }
62
-
63
- private resolveEdit(id: string, value: EditResult) {
64
- const resolve = this.edits.get(id.toString());
65
- if (resolve) resolve(value);
66
- this.edits.delete(id);
67
- }
68
-
69
- select(index: number | null, meta = false, shift = false) {
70
- this.dispatch(actions.select(index, meta, shift));
71
- }
72
-
73
- selectById(id: string | number, meta = false, shift = false) {
74
- const index = this.idToIndex[id];
75
- this.select(index, meta, shift);
76
- }
77
-
78
- selectUpwards(shiftKey: boolean) {
79
- this.dispatch(actions.stepUp(shiftKey, this.visibleIds));
80
- }
81
-
82
- selectDownwards(shiftKey: boolean) {
83
- this.dispatch(actions.stepDown(shiftKey, this.visibleIds));
84
- }
85
-
86
- hideCursor() {
87
- this.dispatch(actions.setCursorLocation({ type: "none" }));
88
- }
89
-
90
- showCursor(cursor: Cursor) {
91
- this.dispatch(actions.setCursorLocation(cursor));
92
- }
93
-
94
- scrollToId(id: string) {
95
- if (!this.list) return;
96
- const index = this.idToIndex[id];
97
- if (index) {
98
- this.list.scrollToItem(index);
99
- } else {
100
- this.openParents(id);
101
- ReactDOM.flushSync(() => {
102
- const index = this.idToIndex[id];
103
- if (index) {
104
- this.list?.scrollToItem(index);
105
- }
106
- });
107
- }
108
- }
109
-
110
- open(id: string) {
111
- this.props.onToggle(id, true);
112
- }
113
-
114
- openParents(id: string) {
115
- const node = dfs(this.props.root, id);
116
- let parent = node?.parent;
117
-
118
- while (parent) {
119
- this.open(parent.id);
120
- parent = parent.parent;
121
- }
122
- }
123
-
124
- get visibleIds() {
125
- return getIds(this.visibleNodes);
126
- }
127
-
128
- get idToIndex() {
129
- return createIndex(this.visibleNodes);
130
- }
131
-
132
- get visibleNodes() {
133
- return createList(this.props.root);
134
- }
135
- }
136
-
137
- const getIds = memoizeOne((nodes: Node[]) => nodes.map((n) => n.id));
138
- const createIndex = memoizeOne((nodes: Node[]) => {
139
- return nodes.reduce<{ [id: string]: number }>((map, node, index) => {
140
- map[node.id] = index;
141
- return map;
142
- }, {});
143
- });
144
- const createList = memoizeOne(flattenTree);
145
-
146
- function dfs(node: Node<unknown>, id: string): Node<unknown> | null {
147
- if (!node) return null;
148
- if (node.id === id) return node;
149
- if (node.children) {
150
- for (let child of node.children) {
151
- const result = dfs(child, id);
152
- if (result) return result;
153
- }
154
- }
155
- return null;
156
- }
package/src/types.ts DELETED
@@ -1,155 +0,0 @@
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
- isSelectedStart: boolean;
57
- isSelectedEnd: boolean;
58
- isEditing: boolean;
59
- };
60
-
61
- export type NodeHandlers = {
62
- toggle: MouseEventHandler;
63
- select: (e: MouseEvent, args: { selectOnClick: boolean }) => void;
64
- edit: () => Promise<EditResult>;
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
-
106
- type BoolFunc<T> = (data: T) => boolean;
107
-
108
- export interface TreeProps<T> {
109
- children: NodeRenderer<T>;
110
- className?: string | undefined;
111
- data: T;
112
- disableDrag?: string | boolean | BoolFunc<T>;
113
- disableDrop?: string | boolean | BoolFunc<T>;
114
- dndRootElement?: globalThis.Node | null;
115
- getChildren?: string | ((d: T) => T[]);
116
- handle?: Ref<TreeApi<T>>; // Deprecated
117
- height?: number;
118
- hideRoot?: boolean;
119
- indent?: number;
120
- isOpen?: string | BoolFunc<T>;
121
- onClick?: MouseEventHandler;
122
- onContextMenu?: MouseEventHandler;
123
- onEdit?: EditHandler;
124
- onMove?: MoveHandler;
125
- onToggle?: ToggleHandler;
126
- openByDefault?: boolean;
127
- rowHeight?: number;
128
- width?: number;
129
- }
130
-
131
- export type TreeProviderProps<T> = {
132
- imperativeHandle: React.Ref<TreeApi<T>> | undefined;
133
- children: ReactElement;
134
- height: number;
135
- indent: number;
136
- listEl: MutableRefObject<HTMLDivElement | null>;
137
- onToggle: ToggleHandler;
138
- onMove: MoveHandler;
139
- onEdit: EditHandler;
140
- onClick?: MouseEventHandler;
141
- onContextMenu?: MouseEventHandler;
142
- renderer: NodeRenderer<any>;
143
- rowHeight: number;
144
- root: Node<T>;
145
- width: number;
146
- };
147
-
148
- export type StaticContext<T> = TreeProviderProps<T> & {
149
- api: TreeApi<T>;
150
- list: MutableRefObject<FixedSizeList | undefined>;
151
- };
152
-
153
- export type EditResult =
154
- | { cancelled: true }
155
- | { cancelled: false; value: string };