react-arborist 3.6.1 → 3.8.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/main/components/cursor.js +1 -2
- package/dist/main/components/default-container.js +31 -1
- package/dist/main/components/default-cursor.js +1 -1
- package/dist/main/components/default-drag-preview.d.ts +1 -1
- package/dist/main/components/default-drag-preview.js +1 -1
- package/dist/main/components/default-row.d.ts +1 -1
- package/dist/main/components/default-row.js +1 -1
- package/dist/main/components/list-outer-element.d.ts +1 -0
- package/dist/main/components/list-outer-element.js +4 -3
- package/dist/main/components/provider.d.ts +1 -1
- package/dist/main/components/provider.js +2 -2
- package/dist/main/components/provider.test.js +70 -0
- package/dist/main/components/row-container.d.ts +1 -1
- package/dist/main/components/row-container.js +2 -3
- package/dist/main/dnd/drag-hook.js +1 -0
- package/dist/main/hooks/use-validated-props.js +1 -2
- package/dist/main/index.d.ts +3 -0
- package/dist/main/index.js +7 -1
- package/dist/main/interfaces/node-api.js +4 -1
- package/dist/main/interfaces/tree-api.d.ts +27 -5
- package/dist/main/interfaces/tree-api.js +98 -14
- package/dist/main/interfaces/tree-api.test.js +31 -0
- package/dist/main/state/drag-slice.js +1 -2
- package/dist/main/types/state.d.ts +1 -1
- package/dist/main/types/tree-props.d.ts +6 -2
- package/dist/module/components/cursor.js +1 -2
- package/dist/module/components/default-container.js +32 -2
- package/dist/module/components/default-cursor.js +1 -1
- package/dist/module/components/default-drag-preview.d.ts +1 -1
- package/dist/module/components/default-drag-preview.js +1 -1
- package/dist/module/components/default-row.d.ts +1 -1
- package/dist/module/components/default-row.js +1 -1
- package/dist/module/components/list-outer-element.d.ts +1 -0
- package/dist/module/components/list-outer-element.js +2 -2
- package/dist/module/components/provider.d.ts +1 -1
- package/dist/module/components/provider.js +4 -4
- package/dist/module/components/provider.test.js +71 -1
- package/dist/module/components/row-container.d.ts +1 -1
- package/dist/module/components/row-container.js +2 -3
- package/dist/module/dnd/compute-drop.js +1 -1
- package/dist/module/dnd/drag-hook.js +1 -0
- package/dist/module/hooks/use-validated-props.js +1 -2
- package/dist/module/index.d.ts +3 -0
- package/dist/module/index.js +3 -0
- package/dist/module/interfaces/node-api.js +4 -1
- package/dist/module/interfaces/tree-api.d.ts +27 -5
- package/dist/module/interfaces/tree-api.js +98 -14
- package/dist/module/interfaces/tree-api.test.js +31 -0
- package/dist/module/state/drag-slice.js +1 -2
- package/dist/module/types/state.d.ts +1 -1
- package/dist/module/types/tree-props.d.ts +6 -2
- package/package.json +27 -27
- package/src/components/cursor.tsx +1 -2
- package/src/components/default-container.tsx +40 -19
- package/src/components/default-cursor.tsx +1 -5
- package/src/components/default-drag-preview.tsx +3 -16
- package/src/components/default-node.tsx +0 -1
- package/src/components/default-row.tsx +2 -13
- package/src/components/drag-preview-container.tsx +1 -1
- package/src/components/list-inner-element.tsx +1 -1
- package/src/components/list-outer-element.tsx +3 -4
- package/src/components/provider.test.tsx +85 -9
- package/src/components/provider.tsx +8 -23
- package/src/components/row-container.tsx +4 -9
- package/src/components/tree.tsx +2 -6
- package/src/context.ts +2 -3
- package/src/data/create-index.ts +0 -1
- package/src/data/create-list.ts +1 -2
- package/src/data/create-root.ts +2 -9
- package/src/data/simple-tree.ts +5 -3
- package/src/dnd/compute-drop.ts +6 -15
- package/src/dnd/drag-hook.ts +1 -0
- package/src/dnd/measure-hover.ts +2 -6
- package/src/dnd/outer-drop-hook.ts +1 -1
- package/src/hooks/use-fresh-node.ts +0 -1
- package/src/hooks/use-simple-tree.ts +2 -8
- package/src/hooks/use-validated-props.ts +4 -8
- package/src/index.ts +3 -0
- package/src/interfaces/node-api.ts +2 -2
- package/src/interfaces/tree-api.test.ts +35 -0
- package/src/interfaces/tree-api.ts +103 -36
- package/src/state/dnd-slice.ts +1 -1
- package/src/state/drag-slice.ts +2 -5
- package/src/state/edit-slice.ts +1 -4
- package/src/state/focus-slice.ts +1 -1
- package/src/state/open-slice.ts +2 -5
- package/src/state/selection-slice.ts +2 -6
- package/src/types/handlers.ts +1 -3
- package/src/types/renderers.ts +0 -1
- package/src/types/state.ts +1 -1
- package/src/types/tree-props.ts +11 -11
- package/src/types/utils.ts +2 -3
- package/src/utils.ts +5 -14
|
@@ -30,6 +30,46 @@ export class TreeApi {
|
|
|
30
30
|
this.listEl = listEl;
|
|
31
31
|
this.visibleStartIndex = 0;
|
|
32
32
|
this.visibleStopIndex = 0;
|
|
33
|
+
/* Memoized prefix-sum of row heights; only used for variable heights. */
|
|
34
|
+
this.rowOffsets = null;
|
|
35
|
+
/**
|
|
36
|
+
* The height of the row at `index`, evaluating the `rowHeight` function if
|
|
37
|
+
* given. Falls back to the default height for an out-of-range index so this
|
|
38
|
+
* never feeds an invalid `0` to react-window's `itemSize`.
|
|
39
|
+
*/
|
|
40
|
+
this.rowHeightAt = (index) => {
|
|
41
|
+
const rowHeight = this.props.rowHeight;
|
|
42
|
+
if (typeof rowHeight === "function") {
|
|
43
|
+
const node = this.at(index);
|
|
44
|
+
return node ? rowHeight(node) : this.rowHeight;
|
|
45
|
+
}
|
|
46
|
+
return rowHeight !== null && rowHeight !== void 0 ? rowHeight : 24;
|
|
47
|
+
};
|
|
48
|
+
/** The pixel offset of the top of the row at `index` from the top of the list. */
|
|
49
|
+
this.rowTopPosition = (index) => {
|
|
50
|
+
/* Fixed heights: O(1). */
|
|
51
|
+
if (typeof this.props.rowHeight !== "function") {
|
|
52
|
+
return index * this.rowHeight;
|
|
53
|
+
}
|
|
54
|
+
/* Variable heights: O(1) amortized via a memoized prefix sum. */
|
|
55
|
+
const offsets = this.getRowOffsets();
|
|
56
|
+
const clamped = Math.max(0, Math.min(index, offsets.length - 1));
|
|
57
|
+
return offsets[clamped];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Tell the underlying virtualized list to recompute row heights at and after
|
|
61
|
+
* `index`. Call this if a `rowHeight` function's output changes for reasons
|
|
62
|
+
* the tree can't observe (e.g. external state).
|
|
63
|
+
*/
|
|
64
|
+
this.redrawList = (afterIndex = 0) => {
|
|
65
|
+
this.rowOffsets = null;
|
|
66
|
+
/* Only the VariableSizeList (function rowHeight) caches measurements; a
|
|
67
|
+
FixedSizeList has constant heights and nothing to recompute. */
|
|
68
|
+
const list = this.list.current;
|
|
69
|
+
if (list && "resetAfterIndex" in list) {
|
|
70
|
+
list.resetAfterIndex(Math.max(0, afterIndex));
|
|
71
|
+
}
|
|
72
|
+
};
|
|
33
73
|
/* Changes here must also be made in update() */
|
|
34
74
|
this.root = createRoot(this);
|
|
35
75
|
this.visibleNodes = createList(this);
|
|
@@ -41,6 +81,18 @@ export class TreeApi {
|
|
|
41
81
|
this.root = createRoot(this);
|
|
42
82
|
this.visibleNodes = createList(this);
|
|
43
83
|
this.idToIndex = createIndex(this.visibleNodes);
|
|
84
|
+
this.rowOffsets = null;
|
|
85
|
+
/* Variable-height mode renders a VariableSizeList, which caches item
|
|
86
|
+
measurements by index and never invalidates them on its own. When the
|
|
87
|
+
visible nodes change (insert/remove/reorder), those cached sizes belong
|
|
88
|
+
to the wrong rows, so drop them. Fixed-height mode renders a
|
|
89
|
+
FixedSizeList (no cache, nothing to reset). update() runs during render,
|
|
90
|
+
so pass shouldForceUpdate=false: the in-progress render repaints the list
|
|
91
|
+
and a forceUpdate here would warn about setting state mid-render. */
|
|
92
|
+
const list = this.list.current;
|
|
93
|
+
if (list && "resetAfterIndex" in list) {
|
|
94
|
+
list.resetAfterIndex(0, false);
|
|
95
|
+
}
|
|
44
96
|
}
|
|
45
97
|
/* Store helpers */
|
|
46
98
|
dispatch(action) {
|
|
@@ -65,9 +117,24 @@ export class TreeApi {
|
|
|
65
117
|
var _a;
|
|
66
118
|
return (_a = this.props.indent) !== null && _a !== void 0 ? _a : 24;
|
|
67
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* The fixed row height. When a `rowHeight` function is supplied for variable
|
|
122
|
+
* heights, this returns the default (24); use `rowHeightAt(index)` to get the
|
|
123
|
+
* height of a specific row.
|
|
124
|
+
*/
|
|
68
125
|
get rowHeight() {
|
|
69
|
-
|
|
70
|
-
|
|
126
|
+
return typeof this.props.rowHeight === "number" ? this.props.rowHeight : 24;
|
|
127
|
+
}
|
|
128
|
+
/** Lazily-built prefix sum where offsets[i] is the top of row i. */
|
|
129
|
+
getRowOffsets() {
|
|
130
|
+
if (this.rowOffsets)
|
|
131
|
+
return this.rowOffsets;
|
|
132
|
+
const offsets = [0];
|
|
133
|
+
for (let i = 0; i < this.visibleNodes.length; i++) {
|
|
134
|
+
offsets.push(offsets[i] + this.rowHeightAt(i));
|
|
135
|
+
}
|
|
136
|
+
this.rowOffsets = offsets;
|
|
137
|
+
return offsets;
|
|
71
138
|
}
|
|
72
139
|
get overscanCount() {
|
|
73
140
|
var _a;
|
|
@@ -169,9 +236,7 @@ export class TreeApi {
|
|
|
169
236
|
create() {
|
|
170
237
|
return __awaiter(this, arguments, void 0, function* (opts = {}) {
|
|
171
238
|
var _a, _b;
|
|
172
|
-
const parentId = opts.parentId === undefined
|
|
173
|
-
? utils.getInsertParentId(this)
|
|
174
|
-
: opts.parentId;
|
|
239
|
+
const parentId = opts.parentId === undefined ? utils.getInsertParentId(this) : opts.parentId;
|
|
175
240
|
const index = (_a = opts.index) !== null && _a !== void 0 ? _a : utils.getInsertIndex(this);
|
|
176
241
|
const type = (_b = opts.type) !== null && _b !== void 0 ? _b : "leaf";
|
|
177
242
|
const data = yield safeRun(this.props.onCreate, {
|
|
@@ -198,20 +263,26 @@ export class TreeApi {
|
|
|
198
263
|
const idents = Array.isArray(node) ? node : [node];
|
|
199
264
|
const ids = idents.map(identify);
|
|
200
265
|
const nodes = ids.map((id) => this.get(id)).filter((n) => !!n);
|
|
266
|
+
/* Guard against Math.min(...[]) === Infinity when no ids resolve to nodes. */
|
|
267
|
+
const fromIndex = nodes.length ? Math.min(...nodes.map((n) => { var _a; return (_a = n.rowIndex) !== null && _a !== void 0 ? _a : 0; })) : 0;
|
|
201
268
|
yield safeRun(this.props.onDelete, { nodes, ids });
|
|
269
|
+
this.redrawList(fromIndex);
|
|
202
270
|
});
|
|
203
271
|
}
|
|
204
272
|
edit(node) {
|
|
273
|
+
var _a, _b;
|
|
205
274
|
const id = identify(node);
|
|
206
275
|
this.resolveEdit({ cancelled: true });
|
|
207
276
|
this.scrollTo(id);
|
|
208
277
|
this.dispatch(edit(id));
|
|
278
|
+
this.redrawList((_b = (_a = this.get(id)) === null || _a === void 0 ? void 0 : _a.rowIndex) !== null && _b !== void 0 ? _b : 0);
|
|
209
279
|
return new Promise((resolve) => {
|
|
210
280
|
TreeApi.editPromise = resolve;
|
|
211
281
|
});
|
|
212
282
|
}
|
|
213
283
|
submit(identity, value) {
|
|
214
284
|
return __awaiter(this, void 0, void 0, function* () {
|
|
285
|
+
var _a, _b;
|
|
215
286
|
if (!identity)
|
|
216
287
|
return;
|
|
217
288
|
const id = identify(identity);
|
|
@@ -222,12 +293,14 @@ export class TreeApi {
|
|
|
222
293
|
});
|
|
223
294
|
this.dispatch(edit(null));
|
|
224
295
|
this.resolveEdit({ cancelled: false, value });
|
|
296
|
+
this.redrawList((_b = (_a = this.get(id)) === null || _a === void 0 ? void 0 : _a.rowIndex) !== null && _b !== void 0 ? _b : 0);
|
|
225
297
|
setTimeout(() => this.onFocus()); // Return focus to element;
|
|
226
298
|
});
|
|
227
299
|
}
|
|
228
300
|
reset() {
|
|
229
301
|
this.dispatch(edit(null));
|
|
230
302
|
this.resolveEdit({ cancelled: true });
|
|
303
|
+
this.redrawList();
|
|
231
304
|
setTimeout(() => this.onFocus()); // Return focus to element;
|
|
232
305
|
}
|
|
233
306
|
activate(id) {
|
|
@@ -407,9 +480,7 @@ export class TreeApi {
|
|
|
407
480
|
return this.state.dnd.cursor.type === "highlight";
|
|
408
481
|
}
|
|
409
482
|
get dragNodes() {
|
|
410
|
-
return this.state.dnd.dragIds
|
|
411
|
-
.map((id) => this.get(id))
|
|
412
|
-
.filter((n) => !!n);
|
|
483
|
+
return this.state.dnd.dragIds.map((id) => this.get(id)).filter((n) => !!n);
|
|
413
484
|
}
|
|
414
485
|
get dragNode() {
|
|
415
486
|
return this.get(this.state.nodes.drag.id);
|
|
@@ -461,22 +532,28 @@ export class TreeApi {
|
|
|
461
532
|
this.dispatch(dnd.cursor(cursor));
|
|
462
533
|
}
|
|
463
534
|
/* Visibility */
|
|
464
|
-
open(identity) {
|
|
535
|
+
open(identity, redraw = true) {
|
|
536
|
+
var _a, _b;
|
|
465
537
|
const id = identifyNull(identity);
|
|
466
538
|
if (!id)
|
|
467
539
|
return;
|
|
468
540
|
if (this.isOpen(id))
|
|
469
541
|
return;
|
|
470
542
|
this.dispatch(visibility.open(id, this.isFiltered));
|
|
543
|
+
if (redraw)
|
|
544
|
+
this.redrawList((_b = (_a = this.get(id)) === null || _a === void 0 ? void 0 : _a.rowIndex) !== null && _b !== void 0 ? _b : 0);
|
|
471
545
|
safeRun(this.props.onToggle, id);
|
|
472
546
|
}
|
|
473
|
-
close(identity) {
|
|
547
|
+
close(identity, redraw = true) {
|
|
548
|
+
var _a, _b;
|
|
474
549
|
const id = identifyNull(identity);
|
|
475
550
|
if (!id)
|
|
476
551
|
return;
|
|
477
552
|
if (!this.isOpen(id))
|
|
478
553
|
return;
|
|
479
554
|
this.dispatch(visibility.close(id, this.isFiltered));
|
|
555
|
+
if (redraw)
|
|
556
|
+
this.redrawList((_b = (_a = this.get(id)) === null || _a === void 0 ? void 0 : _a.rowIndex) !== null && _b !== void 0 ? _b : 0);
|
|
480
557
|
safeRun(this.props.onToggle, id);
|
|
481
558
|
}
|
|
482
559
|
toggle(identity) {
|
|
@@ -492,9 +569,10 @@ export class TreeApi {
|
|
|
492
569
|
const node = utils.dfs(this.root, id);
|
|
493
570
|
let parent = node === null || node === void 0 ? void 0 : node.parent;
|
|
494
571
|
while (parent) {
|
|
495
|
-
this.open(parent.id);
|
|
572
|
+
this.open(parent.id, false);
|
|
496
573
|
parent = parent.parent;
|
|
497
574
|
}
|
|
575
|
+
this.redrawList();
|
|
498
576
|
}
|
|
499
577
|
openSiblings(node) {
|
|
500
578
|
const parent = node.parent;
|
|
@@ -505,23 +583,29 @@ export class TreeApi {
|
|
|
505
583
|
const isOpen = node.isOpen;
|
|
506
584
|
for (let sibling of parent.children) {
|
|
507
585
|
if (sibling.isInternal) {
|
|
508
|
-
|
|
586
|
+
if (isOpen)
|
|
587
|
+
this.close(sibling.id, false);
|
|
588
|
+
else
|
|
589
|
+
this.open(sibling.id, false);
|
|
509
590
|
}
|
|
510
591
|
}
|
|
592
|
+
this.redrawList();
|
|
511
593
|
this.scrollTo(this.focusedNode);
|
|
512
594
|
}
|
|
513
595
|
}
|
|
514
596
|
openAll() {
|
|
515
597
|
utils.walk(this.root, (node) => {
|
|
516
598
|
if (node.isInternal)
|
|
517
|
-
|
|
599
|
+
this.open(node.id, false);
|
|
518
600
|
});
|
|
601
|
+
this.redrawList();
|
|
519
602
|
}
|
|
520
603
|
closeAll() {
|
|
521
604
|
utils.walk(this.root, (node) => {
|
|
522
605
|
if (node.isInternal)
|
|
523
|
-
|
|
606
|
+
this.close(node.id, false);
|
|
524
607
|
});
|
|
608
|
+
this.redrawList();
|
|
525
609
|
}
|
|
526
610
|
/* Scrolling */
|
|
527
611
|
scrollTo(identity, align = "smart") {
|
|
@@ -10,3 +10,34 @@ test("tree.canDrop()", () => {
|
|
|
10
10
|
expect(setupApi({ disableDrop: () => false }).canDrop()).toBe(true);
|
|
11
11
|
expect(setupApi({ disableDrop: false }).canDrop()).toBe(true);
|
|
12
12
|
});
|
|
13
|
+
const rowData = [{ id: "a" }, { id: "b" }, { id: "c" }];
|
|
14
|
+
test("rowHeight defaults to 24", () => {
|
|
15
|
+
const api = setupApi({});
|
|
16
|
+
expect(api.rowHeight).toBe(24);
|
|
17
|
+
expect(api.rowHeightAt(0)).toBe(24);
|
|
18
|
+
});
|
|
19
|
+
test("fixed numeric rowHeight", () => {
|
|
20
|
+
const api = setupApi({ data: rowData, rowHeight: 30 });
|
|
21
|
+
expect(api.rowHeight).toBe(30);
|
|
22
|
+
expect(api.rowHeightAt(0)).toBe(30);
|
|
23
|
+
expect(api.rowTopPosition(0)).toBe(0);
|
|
24
|
+
expect(api.rowTopPosition(2)).toBe(60);
|
|
25
|
+
expect(api.rowTopPosition(3)).toBe(90); // total list height
|
|
26
|
+
});
|
|
27
|
+
test("variable rowHeight function", () => {
|
|
28
|
+
const heights = { a: 10, b: 20, c: 40 };
|
|
29
|
+
const api = setupApi({
|
|
30
|
+
data: rowData,
|
|
31
|
+
rowHeight: (node) => heights[node.id],
|
|
32
|
+
});
|
|
33
|
+
// The back-compat getter falls back to the default for variable heights.
|
|
34
|
+
expect(api.rowHeight).toBe(24);
|
|
35
|
+
expect(api.rowHeightAt(0)).toBe(10);
|
|
36
|
+
expect(api.rowHeightAt(1)).toBe(20);
|
|
37
|
+
expect(api.rowTopPosition(0)).toBe(0);
|
|
38
|
+
expect(api.rowTopPosition(1)).toBe(10);
|
|
39
|
+
expect(api.rowTopPosition(2)).toBe(30);
|
|
40
|
+
expect(api.rowTopPosition(3)).toBe(70); // total list height
|
|
41
|
+
// Out-of-range index falls back to the default height, never an invalid 0.
|
|
42
|
+
expect(api.rowHeightAt(99)).toBe(24);
|
|
43
|
+
});
|
|
@@ -7,8 +7,7 @@ export function reducer(state = initialState().nodes.drag, action) {
|
|
|
7
7
|
case "DND_DRAG_END":
|
|
8
8
|
return Object.assign(Object.assign({}, state), { id: null, destinationParentId: null, destinationIndex: null, selectedIds: [] });
|
|
9
9
|
case "DND_HOVERING":
|
|
10
|
-
if (action.parentId !== state.destinationParentId ||
|
|
11
|
-
action.index != state.destinationIndex) {
|
|
10
|
+
if (action.parentId !== state.destinationParentId || action.index != state.destinationIndex) {
|
|
12
11
|
return Object.assign(Object.assign({}, state), { destinationParentId: action.parentId, destinationIndex: action.index });
|
|
13
12
|
}
|
|
14
13
|
else {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { NodeApi } from "../interfaces/node-api";
|
|
2
|
-
export type NodeState = typeof NodeApi.prototype["state"];
|
|
2
|
+
export type NodeState = (typeof NodeApi.prototype)["state"];
|
|
@@ -2,10 +2,12 @@ import { BoolFunc } from "./utils";
|
|
|
2
2
|
import * as handlers from "./handlers";
|
|
3
3
|
import * as renderers from "./renderers";
|
|
4
4
|
import { ElementType, MouseEventHandler } from "react";
|
|
5
|
-
import { ListOnScrollProps } from "react-window";
|
|
5
|
+
import { ListOnScrollProps, CommonProps as ReactWindowCommonProps } from "react-window";
|
|
6
6
|
import { NodeApi } from "../interfaces/node-api";
|
|
7
7
|
import { OpenMap } from "../state/open-slice";
|
|
8
8
|
import { useDragDropManager, DndProviderProps } from "react-dnd";
|
|
9
|
+
/** Returns the height in pixels for a given node's row. */
|
|
10
|
+
export type RowHeightAccessor<T> = (node: NodeApi<T>) => number;
|
|
9
11
|
export interface TreeProps<T> {
|
|
10
12
|
data?: readonly T[];
|
|
11
13
|
initialData?: readonly T[];
|
|
@@ -18,7 +20,7 @@ export interface TreeProps<T> {
|
|
|
18
20
|
renderDragPreview?: ElementType<renderers.DragPreviewProps>;
|
|
19
21
|
renderCursor?: ElementType<renderers.CursorProps>;
|
|
20
22
|
renderContainer?: ElementType<{}>;
|
|
21
|
-
rowHeight?: number
|
|
23
|
+
rowHeight?: number | RowHeightAccessor<T>;
|
|
22
24
|
overscanCount?: number;
|
|
23
25
|
width?: number | string;
|
|
24
26
|
height?: number;
|
|
@@ -57,4 +59,6 @@ export interface TreeProps<T> {
|
|
|
57
59
|
backend: unknown;
|
|
58
60
|
}>["backend"];
|
|
59
61
|
dndManager?: ReturnType<typeof useDragDropManager>;
|
|
62
|
+
outerElementType?: ReactWindowCommonProps["outerElementType"];
|
|
63
|
+
innerElementType?: ReactWindowCommonProps["innerElementType"];
|
|
60
64
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-arborist",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"arborist",
|
|
6
|
+
"dnd",
|
|
7
|
+
"filterable",
|
|
8
|
+
"multiselection",
|
|
9
|
+
"react",
|
|
10
|
+
"react-arborist",
|
|
11
|
+
"tree",
|
|
12
|
+
"treeview",
|
|
13
|
+
"virtualized"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://react-arborist.netlify.app",
|
|
16
|
+
"bugs": "https://github.com/jameskerr/react-arborist/issues",
|
|
4
17
|
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/jameskerr/react-arborist.git"
|
|
21
|
+
},
|
|
5
22
|
"source": "src/index.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": false,
|
|
6
28
|
"main": "dist/main/index.js",
|
|
7
29
|
"module": "dist/module/index.js",
|
|
8
30
|
"types": "dist/module/index.d.ts",
|
|
9
|
-
"sideEffects": false,
|
|
10
31
|
"scripts": {
|
|
11
32
|
"build:cjs": "tsc --outDir dist/main",
|
|
12
33
|
"build:es": "tsc --outDir dist/module --module es2022 --moduleResolution node",
|
|
@@ -16,27 +37,6 @@
|
|
|
16
37
|
"test": "jest",
|
|
17
38
|
"watch": "yarn build:es --watch"
|
|
18
39
|
},
|
|
19
|
-
"files": [
|
|
20
|
-
"src",
|
|
21
|
-
"dist"
|
|
22
|
-
],
|
|
23
|
-
"repository": {
|
|
24
|
-
"type": "git",
|
|
25
|
-
"url": "https://github.com/jameskerr/react-arborist.git"
|
|
26
|
-
},
|
|
27
|
-
"homepage": "https://react-arborist.netlify.app",
|
|
28
|
-
"bugs": "https://github.com/jameskerr/react-arborist/issues",
|
|
29
|
-
"keywords": [
|
|
30
|
-
"react",
|
|
31
|
-
"arborist",
|
|
32
|
-
"react-arborist",
|
|
33
|
-
"treeview",
|
|
34
|
-
"tree",
|
|
35
|
-
"vitualized",
|
|
36
|
-
"dnd",
|
|
37
|
-
"multiselection",
|
|
38
|
-
"filterable"
|
|
39
|
-
],
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"react-dnd": "^14.0.3",
|
|
42
42
|
"react-dnd-html5-backend": "^14.0.3",
|
|
@@ -44,10 +44,6 @@
|
|
|
44
44
|
"redux": "^5.0.0",
|
|
45
45
|
"use-sync-external-store": "^1.2.0"
|
|
46
46
|
},
|
|
47
|
-
"peerDependencies": {
|
|
48
|
-
"react": ">= 16.14",
|
|
49
|
-
"react-dom": ">= 16.14"
|
|
50
|
-
},
|
|
51
47
|
"devDependencies": {
|
|
52
48
|
"@testing-library/dom": "^9.3.0",
|
|
53
49
|
"@testing-library/react": "^14.0.0",
|
|
@@ -64,5 +60,9 @@
|
|
|
64
60
|
"rimraf": "^5.0.5",
|
|
65
61
|
"ts-jest": "^29.1.1",
|
|
66
62
|
"typescript": "^5.6.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"react": ">= 16.14",
|
|
66
|
+
"react-dom": ">= 16.14"
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -7,8 +7,7 @@ export function Cursor() {
|
|
|
7
7
|
if (!cursor || cursor.type !== "line") return null;
|
|
8
8
|
const indent = tree.indent;
|
|
9
9
|
const top =
|
|
10
|
-
tree.
|
|
11
|
-
(tree.props.padding ?? tree.props.paddingTop ?? 0);
|
|
10
|
+
tree.rowTopPosition(cursor.index) + (tree.props.padding ?? tree.props.paddingTop ?? 0);
|
|
12
11
|
const left = indent * cursor.level;
|
|
13
12
|
const Cursor = tree.renderCursor;
|
|
14
13
|
return <Cursor {...{ top, left, indent }} />;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FixedSizeList } from "react-window";
|
|
1
|
+
import { FixedSizeList, VariableSizeList } from "react-window";
|
|
2
2
|
import { useDataUpdates, useTreeApi } from "../context";
|
|
3
3
|
import { focusNextElement, focusPrevElement } from "../utils";
|
|
4
4
|
import { ListOuterElement } from "./list-outer-element";
|
|
@@ -216,24 +216,45 @@ export function DefaultContainer() {
|
|
|
216
216
|
if (node) tree.focus(node.id);
|
|
217
217
|
}}
|
|
218
218
|
>
|
|
219
|
-
|
|
220
|
-
<FixedSizeList
|
|
221
|
-
className={tree.props.className}
|
|
222
|
-
outerRef={tree.listEl}
|
|
223
|
-
itemCount={tree.visibleNodes.length}
|
|
224
|
-
height={tree.height}
|
|
225
|
-
width={tree.width}
|
|
226
|
-
itemSize={tree.rowHeight}
|
|
227
|
-
overscanCount={tree.overscanCount}
|
|
228
|
-
itemKey={(index) => tree.visibleNodes[index]?.id || index}
|
|
229
|
-
outerElementType={ListOuterElement}
|
|
230
|
-
innerElementType={ListInnerElement}
|
|
231
|
-
onScroll={tree.props.onScroll}
|
|
232
|
-
onItemsRendered={tree.onItemsRendered.bind(tree)}
|
|
233
|
-
ref={tree.list}
|
|
234
|
-
>
|
|
235
|
-
{RowContainer}
|
|
236
|
-
</FixedSizeList>
|
|
219
|
+
<List />
|
|
237
220
|
</div>
|
|
238
221
|
);
|
|
239
222
|
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Fixed-height trees (numeric rowHeight) render a FixedSizeList, preserving the
|
|
226
|
+
* original O(1) layout and avoiding VariableSizeList's measurement cache. Only
|
|
227
|
+
* the function form, which needs per-row heights, uses VariableSizeList.
|
|
228
|
+
*/
|
|
229
|
+
function List() {
|
|
230
|
+
const tree = useTreeApi();
|
|
231
|
+
const commonProps = {
|
|
232
|
+
className: tree.props.className,
|
|
233
|
+
outerRef: tree.listEl,
|
|
234
|
+
itemCount: tree.visibleNodes.length,
|
|
235
|
+
height: tree.height,
|
|
236
|
+
width: tree.width,
|
|
237
|
+
overscanCount: tree.overscanCount,
|
|
238
|
+
itemKey: (index: number) => tree.visibleNodes[index]?.id || index,
|
|
239
|
+
outerElementType: tree.props.outerElementType ?? ListOuterElement,
|
|
240
|
+
innerElementType: tree.props.innerElementType ?? ListInnerElement,
|
|
241
|
+
onScroll: tree.props.onScroll,
|
|
242
|
+
onItemsRendered: tree.onItemsRendered.bind(tree),
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
if (typeof tree.props.rowHeight === "function") {
|
|
246
|
+
return (
|
|
247
|
+
// @ts-ignore
|
|
248
|
+
<VariableSizeList {...commonProps} itemSize={tree.rowHeightAt} ref={tree.list}>
|
|
249
|
+
{RowContainer}
|
|
250
|
+
</VariableSizeList>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
// @ts-ignore
|
|
256
|
+
<FixedSizeList {...commonProps} itemSize={tree.rowHeight} ref={tree.list}>
|
|
257
|
+
{RowContainer}
|
|
258
|
+
</FixedSizeList>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
@@ -21,11 +21,7 @@ const circleStyle = {
|
|
|
21
21
|
borderRadius: "50%",
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
export const DefaultCursor = React.memo(function DefaultCursor({
|
|
25
|
-
top,
|
|
26
|
-
left,
|
|
27
|
-
indent,
|
|
28
|
-
}: CursorProps) {
|
|
24
|
+
export const DefaultCursor = React.memo(function DefaultCursor({ top, left, indent }: CursorProps) {
|
|
29
25
|
const style: CSSProperties = {
|
|
30
26
|
position: "absolute",
|
|
31
27
|
pointerEvents: "none",
|
|
@@ -2,7 +2,6 @@ import React, { CSSProperties, memo } from "react";
|
|
|
2
2
|
import { XYCoord } from "react-dnd";
|
|
3
3
|
import { useTreeApi } from "../context";
|
|
4
4
|
import { DragPreviewProps } from "../types/renderers";
|
|
5
|
-
import { IdObj } from "../types/utils";
|
|
6
5
|
|
|
7
6
|
const layerStyles: CSSProperties = {
|
|
8
7
|
position: "fixed",
|
|
@@ -26,13 +25,7 @@ const getCountStyle = (offset: XYCoord | null) => {
|
|
|
26
25
|
return { transform: `translate(${x + 10}px, ${y + 10}px)` };
|
|
27
26
|
};
|
|
28
27
|
|
|
29
|
-
export function DefaultDragPreview({
|
|
30
|
-
offset,
|
|
31
|
-
mouse,
|
|
32
|
-
id,
|
|
33
|
-
dragIds,
|
|
34
|
-
isDragging,
|
|
35
|
-
}: DragPreviewProps) {
|
|
28
|
+
export function DefaultDragPreview({ offset, mouse, id, dragIds, isDragging }: DragPreviewProps) {
|
|
36
29
|
return (
|
|
37
30
|
<Overlay isDragging={isDragging}>
|
|
38
31
|
<Position offset={offset}>
|
|
@@ -43,10 +36,7 @@ export function DefaultDragPreview({
|
|
|
43
36
|
);
|
|
44
37
|
}
|
|
45
38
|
|
|
46
|
-
const Overlay = memo(function Overlay(props: {
|
|
47
|
-
children: JSX.Element[];
|
|
48
|
-
isDragging: boolean;
|
|
49
|
-
}) {
|
|
39
|
+
const Overlay = memo(function Overlay(props: { children: JSX.Element[]; isDragging: boolean }) {
|
|
50
40
|
if (!props.isDragging) return null;
|
|
51
41
|
return <div style={layerStyles}>{props.children}</div>;
|
|
52
42
|
});
|
|
@@ -70,10 +60,7 @@ function Count(props: { count: number; mouse: XYCoord | null }) {
|
|
|
70
60
|
else return null;
|
|
71
61
|
}
|
|
72
62
|
|
|
73
|
-
const PreviewNode = memo(function PreviewNode<T>(props: {
|
|
74
|
-
id: string | null;
|
|
75
|
-
dragIds: string[];
|
|
76
|
-
}) {
|
|
63
|
+
const PreviewNode = memo(function PreviewNode<T>(props: { id: string | null; dragIds: string[] }) {
|
|
77
64
|
const tree = useTreeApi<T>();
|
|
78
65
|
const node = tree.get(props.id);
|
|
79
66
|
if (!node) return null;
|
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { RowRendererProps } from "../types/renderers";
|
|
3
|
-
import { IdObj } from "../types/utils";
|
|
4
3
|
|
|
5
|
-
export function DefaultRow<T>({
|
|
6
|
-
node,
|
|
7
|
-
attrs,
|
|
8
|
-
innerRef,
|
|
9
|
-
children,
|
|
10
|
-
}: RowRendererProps<T>) {
|
|
4
|
+
export function DefaultRow<T>({ node, attrs, innerRef, children }: RowRendererProps<T>) {
|
|
11
5
|
return (
|
|
12
|
-
<div
|
|
13
|
-
{...attrs}
|
|
14
|
-
ref={innerRef}
|
|
15
|
-
onFocus={(e) => e.stopPropagation()}
|
|
16
|
-
onClick={node.handleClick}
|
|
17
|
-
>
|
|
6
|
+
<div {...attrs} ref={innerRef} onFocus={(e) => e.stopPropagation()} onClick={node.handleClick}>
|
|
18
7
|
{children}
|
|
19
8
|
</div>
|
|
20
9
|
);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { forwardRef } from "react";
|
|
2
2
|
import { useTreeApi } from "../context";
|
|
3
|
-
import { treeBlur } from "../state/focus-slice";
|
|
4
3
|
import { Cursor } from "./cursor";
|
|
5
4
|
|
|
6
5
|
export const ListOuterElement = forwardRef(function Outer(
|
|
7
6
|
props: React.HTMLProps<HTMLDivElement>,
|
|
8
|
-
ref
|
|
7
|
+
ref,
|
|
9
8
|
) {
|
|
10
9
|
const { children, ...rest } = props;
|
|
11
10
|
const tree = useTreeApi();
|
|
@@ -24,12 +23,12 @@ export const ListOuterElement = forwardRef(function Outer(
|
|
|
24
23
|
);
|
|
25
24
|
});
|
|
26
25
|
|
|
27
|
-
const DropContainer = () => {
|
|
26
|
+
export const DropContainer = () => {
|
|
28
27
|
const tree = useTreeApi();
|
|
29
28
|
return (
|
|
30
29
|
<div
|
|
31
30
|
style={{
|
|
32
|
-
height: tree.visibleNodes.length
|
|
31
|
+
height: tree.rowTopPosition(tree.visibleNodes.length),
|
|
33
32
|
width: "100%",
|
|
34
33
|
position: "absolute",
|
|
35
34
|
left: "0",
|