react-arborist 3.7.0 → 3.9.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 +14 -0
- package/dist/main/components/cursor.js +1 -2
- package/dist/main/components/default-container.js +31 -2
- 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.js +1 -1
- 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.d.ts +2 -0
- package/dist/main/dnd/drag-hook.js +13 -4
- package/dist/main/dnd/drag-hook.test.d.ts +1 -0
- package/dist/main/dnd/drag-hook.test.js +19 -0
- package/dist/main/hooks/use-validated-props.js +1 -2
- 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/dnd.d.ts +3 -1
- package/dist/main/types/state.d.ts +1 -1
- package/dist/main/types/tree-props.d.ts +4 -1
- package/dist/module/components/cursor.js +1 -2
- package/dist/module/components/default-container.js +32 -3
- 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.js +1 -1
- 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.d.ts +2 -0
- package/dist/module/dnd/drag-hook.js +12 -4
- package/dist/module/dnd/drag-hook.test.d.ts +1 -0
- package/dist/module/dnd/drag-hook.test.js +17 -0
- package/dist/module/hooks/use-validated-props.js +1 -2
- 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/dnd.d.ts +3 -1
- package/dist/module/types/state.d.ts +1 -1
- package/dist/module/types/tree-props.d.ts +4 -1
- 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 +2 -3
- 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.test.ts +22 -0
- package/src/dnd/drag-hook.ts +15 -6
- 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/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/dnd.ts +6 -1
- 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 +15 -10
- package/src/types/utils.ts +2 -3
- package/src/utils.ts +5 -14
|
@@ -2,7 +2,7 @@ import { EditResult } from "../types/handlers";
|
|
|
2
2
|
import { BoolFunc, Identity, IdObj } from "../types/utils";
|
|
3
3
|
import { TreeProps } from "../types/tree-props";
|
|
4
4
|
import { MutableRefObject } from "react";
|
|
5
|
-
import { Align, FixedSizeList, ListOnItemsRenderedProps } from "react-window";
|
|
5
|
+
import { Align, FixedSizeList, ListOnItemsRenderedProps, VariableSizeList } from "react-window";
|
|
6
6
|
import * as utils from "../utils";
|
|
7
7
|
import { DefaultCursor } from "../components/default-cursor";
|
|
8
8
|
import { DefaultRow } from "../components/default-row";
|
|
@@ -30,12 +30,14 @@ export class TreeApi<T> {
|
|
|
30
30
|
visibleStartIndex: number = 0;
|
|
31
31
|
visibleStopIndex: number = 0;
|
|
32
32
|
idToIndex: { [id: string]: number };
|
|
33
|
+
/* Memoized prefix-sum of row heights; only used for variable heights. */
|
|
34
|
+
private rowOffsets: number[] | null = null;
|
|
33
35
|
|
|
34
36
|
constructor(
|
|
35
37
|
public store: Store<RootState, Actions>,
|
|
36
38
|
public props: TreeProps<T>,
|
|
37
|
-
public list: MutableRefObject<FixedSizeList | null>,
|
|
38
|
-
public listEl: MutableRefObject<HTMLDivElement | null
|
|
39
|
+
public list: MutableRefObject<FixedSizeList | VariableSizeList | null>,
|
|
40
|
+
public listEl: MutableRefObject<HTMLDivElement | null>,
|
|
39
41
|
) {
|
|
40
42
|
/* Changes here must also be made in update() */
|
|
41
43
|
this.root = createRoot<T>(this);
|
|
@@ -49,6 +51,18 @@ export class TreeApi<T> {
|
|
|
49
51
|
this.root = createRoot<T>(this);
|
|
50
52
|
this.visibleNodes = createList<T>(this);
|
|
51
53
|
this.idToIndex = createIndex(this.visibleNodes);
|
|
54
|
+
this.rowOffsets = null;
|
|
55
|
+
/* Variable-height mode renders a VariableSizeList, which caches item
|
|
56
|
+
measurements by index and never invalidates them on its own. When the
|
|
57
|
+
visible nodes change (insert/remove/reorder), those cached sizes belong
|
|
58
|
+
to the wrong rows, so drop them. Fixed-height mode renders a
|
|
59
|
+
FixedSizeList (no cache, nothing to reset). update() runs during render,
|
|
60
|
+
so pass shouldForceUpdate=false: the in-progress render repaints the list
|
|
61
|
+
and a forceUpdate here would warn about setting state mid-render. */
|
|
62
|
+
const list = this.list.current;
|
|
63
|
+
if (list && "resetAfterIndex" in list) {
|
|
64
|
+
list.resetAfterIndex(0, false);
|
|
65
|
+
}
|
|
52
66
|
}
|
|
53
67
|
|
|
54
68
|
/* Store helpers */
|
|
@@ -79,8 +93,65 @@ export class TreeApi<T> {
|
|
|
79
93
|
return this.props.indent ?? 24;
|
|
80
94
|
}
|
|
81
95
|
|
|
96
|
+
/**
|
|
97
|
+
* The fixed row height. When a `rowHeight` function is supplied for variable
|
|
98
|
+
* heights, this returns the default (24); use `rowHeightAt(index)` to get the
|
|
99
|
+
* height of a specific row.
|
|
100
|
+
*/
|
|
82
101
|
get rowHeight() {
|
|
83
|
-
return this.props.rowHeight
|
|
102
|
+
return typeof this.props.rowHeight === "number" ? this.props.rowHeight : 24;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The height of the row at `index`, evaluating the `rowHeight` function if
|
|
107
|
+
* given. Falls back to the default height for an out-of-range index so this
|
|
108
|
+
* never feeds an invalid `0` to react-window's `itemSize`.
|
|
109
|
+
*/
|
|
110
|
+
rowHeightAt = (index: number): number => {
|
|
111
|
+
const rowHeight = this.props.rowHeight;
|
|
112
|
+
if (typeof rowHeight === "function") {
|
|
113
|
+
const node = this.at(index);
|
|
114
|
+
return node ? rowHeight(node) : this.rowHeight;
|
|
115
|
+
}
|
|
116
|
+
return rowHeight ?? 24;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/** The pixel offset of the top of the row at `index` from the top of the list. */
|
|
120
|
+
rowTopPosition = (index: number): number => {
|
|
121
|
+
/* Fixed heights: O(1). */
|
|
122
|
+
if (typeof this.props.rowHeight !== "function") {
|
|
123
|
+
return index * this.rowHeight;
|
|
124
|
+
}
|
|
125
|
+
/* Variable heights: O(1) amortized via a memoized prefix sum. */
|
|
126
|
+
const offsets = this.getRowOffsets();
|
|
127
|
+
const clamped = Math.max(0, Math.min(index, offsets.length - 1));
|
|
128
|
+
return offsets[clamped];
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Tell the underlying virtualized list to recompute row heights at and after
|
|
133
|
+
* `index`. Call this if a `rowHeight` function's output changes for reasons
|
|
134
|
+
* the tree can't observe (e.g. external state).
|
|
135
|
+
*/
|
|
136
|
+
redrawList = (afterIndex: number = 0) => {
|
|
137
|
+
this.rowOffsets = null;
|
|
138
|
+
/* Only the VariableSizeList (function rowHeight) caches measurements; a
|
|
139
|
+
FixedSizeList has constant heights and nothing to recompute. */
|
|
140
|
+
const list = this.list.current;
|
|
141
|
+
if (list && "resetAfterIndex" in list) {
|
|
142
|
+
list.resetAfterIndex(Math.max(0, afterIndex));
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/** Lazily-built prefix sum where offsets[i] is the top of row i. */
|
|
147
|
+
private getRowOffsets(): number[] {
|
|
148
|
+
if (this.rowOffsets) return this.rowOffsets;
|
|
149
|
+
const offsets: number[] = [0];
|
|
150
|
+
for (let i = 0; i < this.visibleNodes.length; i++) {
|
|
151
|
+
offsets.push(offsets[i] + this.rowHeightAt(i));
|
|
152
|
+
}
|
|
153
|
+
this.rowOffsets = offsets;
|
|
154
|
+
return offsets;
|
|
84
155
|
}
|
|
85
156
|
|
|
86
157
|
get overscanCount() {
|
|
@@ -95,9 +166,7 @@ export class TreeApi<T> {
|
|
|
95
166
|
const match =
|
|
96
167
|
this.props.searchMatch ??
|
|
97
168
|
((node, term) => {
|
|
98
|
-
const string = JSON.stringify(
|
|
99
|
-
Object.values(node.data as { [k: string]: unknown })
|
|
100
|
-
);
|
|
169
|
+
const string = JSON.stringify(Object.values(node.data as { [k: string]: unknown }));
|
|
101
170
|
return string.toLocaleLowerCase().includes(term.toLocaleLowerCase());
|
|
102
171
|
});
|
|
103
172
|
return (node: NodeApi<T>) => match(node, this.searchTerm);
|
|
@@ -113,7 +182,7 @@ export class TreeApi<T> {
|
|
|
113
182
|
const id = utils.access<string>(data, get);
|
|
114
183
|
if (!id)
|
|
115
184
|
throw new Error(
|
|
116
|
-
"Data must contain an 'id' property or props.idAccessor must return a string"
|
|
185
|
+
"Data must contain an 'id' property or props.idAccessor must return a string",
|
|
117
186
|
);
|
|
118
187
|
return id;
|
|
119
188
|
}
|
|
@@ -150,8 +219,7 @@ export class TreeApi<T> {
|
|
|
150
219
|
|
|
151
220
|
get(id: string | null): NodeApi<T> | null {
|
|
152
221
|
if (!id) return null;
|
|
153
|
-
if (id in this.idToIndex)
|
|
154
|
-
return this.visibleNodes[this.idToIndex[id]] || null;
|
|
222
|
+
if (id in this.idToIndex) return this.visibleNodes[this.idToIndex[id]] || null;
|
|
155
223
|
else return null;
|
|
156
224
|
}
|
|
157
225
|
|
|
@@ -194,12 +262,9 @@ export class TreeApi<T> {
|
|
|
194
262
|
type?: "internal" | "leaf";
|
|
195
263
|
parentId?: null | string;
|
|
196
264
|
index?: null | number;
|
|
197
|
-
} = {}
|
|
265
|
+
} = {},
|
|
198
266
|
) {
|
|
199
|
-
const parentId =
|
|
200
|
-
opts.parentId === undefined
|
|
201
|
-
? utils.getInsertParentId(this)
|
|
202
|
-
: opts.parentId;
|
|
267
|
+
const parentId = opts.parentId === undefined ? utils.getInsertParentId(this) : opts.parentId;
|
|
203
268
|
const index = opts.index ?? utils.getInsertIndex(this);
|
|
204
269
|
const type = opts.type ?? "leaf";
|
|
205
270
|
const data = await safeRun(this.props.onCreate, {
|
|
@@ -224,7 +289,10 @@ export class TreeApi<T> {
|
|
|
224
289
|
const idents = Array.isArray(node) ? node : [node];
|
|
225
290
|
const ids = idents.map(identify);
|
|
226
291
|
const nodes = ids.map((id) => this.get(id)!).filter((n) => !!n);
|
|
292
|
+
/* Guard against Math.min(...[]) === Infinity when no ids resolve to nodes. */
|
|
293
|
+
const fromIndex = nodes.length ? Math.min(...nodes.map((n) => n.rowIndex ?? 0)) : 0;
|
|
227
294
|
await safeRun(this.props.onDelete, { nodes, ids });
|
|
295
|
+
this.redrawList(fromIndex);
|
|
228
296
|
}
|
|
229
297
|
|
|
230
298
|
edit(node: string | IdObj): Promise<EditResult> {
|
|
@@ -232,6 +300,7 @@ export class TreeApi<T> {
|
|
|
232
300
|
this.resolveEdit({ cancelled: true });
|
|
233
301
|
this.scrollTo(id);
|
|
234
302
|
this.dispatch(edit(id));
|
|
303
|
+
this.redrawList(this.get(id)?.rowIndex ?? 0);
|
|
235
304
|
return new Promise((resolve) => {
|
|
236
305
|
TreeApi.editPromise = resolve;
|
|
237
306
|
});
|
|
@@ -247,12 +316,14 @@ export class TreeApi<T> {
|
|
|
247
316
|
});
|
|
248
317
|
this.dispatch(edit(null));
|
|
249
318
|
this.resolveEdit({ cancelled: false, value });
|
|
319
|
+
this.redrawList(this.get(id)?.rowIndex ?? 0);
|
|
250
320
|
setTimeout(() => this.onFocus()); // Return focus to element;
|
|
251
321
|
}
|
|
252
322
|
|
|
253
323
|
reset() {
|
|
254
324
|
this.dispatch(edit(null));
|
|
255
325
|
this.resolveEdit({ cancelled: true });
|
|
326
|
+
this.redrawList();
|
|
256
327
|
setTimeout(() => this.onFocus()); // Return focus to element;
|
|
257
328
|
}
|
|
258
329
|
|
|
@@ -389,9 +460,7 @@ export class TreeApi<T> {
|
|
|
389
460
|
}
|
|
390
461
|
|
|
391
462
|
selectAll() {
|
|
392
|
-
const allSelectableNodes = this.filterSelectableNodes(
|
|
393
|
-
Object.keys(this.idToIndex),
|
|
394
|
-
);
|
|
463
|
+
const allSelectableNodes = this.filterSelectableNodes(Object.keys(this.idToIndex));
|
|
395
464
|
this.setSelection({
|
|
396
465
|
ids: allSelectableNodes,
|
|
397
466
|
anchor: allSelectableNodes[0] ?? null,
|
|
@@ -408,11 +477,7 @@ export class TreeApi<T> {
|
|
|
408
477
|
.filter((n): n is NodeApi<T> => !!n && n.isSelectable);
|
|
409
478
|
}
|
|
410
479
|
|
|
411
|
-
setSelection(args: {
|
|
412
|
-
ids: (IdObj | string)[] | null;
|
|
413
|
-
anchor: Identity;
|
|
414
|
-
mostRecent: Identity;
|
|
415
|
-
}) {
|
|
480
|
+
setSelection(args: { ids: (IdObj | string)[] | null; anchor: Identity; mostRecent: Identity }) {
|
|
416
481
|
const ids = new Set(args.ids?.map(identify));
|
|
417
482
|
const anchor = identifyNull(args.anchor);
|
|
418
483
|
const mostRecent = identifyNull(args.mostRecent);
|
|
@@ -437,9 +502,7 @@ export class TreeApi<T> {
|
|
|
437
502
|
}
|
|
438
503
|
|
|
439
504
|
get dragNodes() {
|
|
440
|
-
return this.state.dnd.dragIds
|
|
441
|
-
.map((id) => this.get(id))
|
|
442
|
-
.filter((n) => !!n) as NodeApi<T>[];
|
|
505
|
+
return this.state.dnd.dragIds.map((id) => this.get(id)).filter((n) => !!n) as NodeApi<T>[];
|
|
443
506
|
}
|
|
444
507
|
|
|
445
508
|
get dragNode() {
|
|
@@ -493,19 +556,21 @@ export class TreeApi<T> {
|
|
|
493
556
|
|
|
494
557
|
/* Visibility */
|
|
495
558
|
|
|
496
|
-
open(identity: Identity) {
|
|
559
|
+
open(identity: Identity, redraw: boolean = true) {
|
|
497
560
|
const id = identifyNull(identity);
|
|
498
561
|
if (!id) return;
|
|
499
562
|
if (this.isOpen(id)) return;
|
|
500
563
|
this.dispatch(visibility.open(id, this.isFiltered));
|
|
564
|
+
if (redraw) this.redrawList(this.get(id)?.rowIndex ?? 0);
|
|
501
565
|
safeRun(this.props.onToggle, id);
|
|
502
566
|
}
|
|
503
567
|
|
|
504
|
-
close(identity: Identity) {
|
|
568
|
+
close(identity: Identity, redraw: boolean = true) {
|
|
505
569
|
const id = identifyNull(identity);
|
|
506
570
|
if (!id) return;
|
|
507
571
|
if (!this.isOpen(id)) return;
|
|
508
572
|
this.dispatch(visibility.close(id, this.isFiltered));
|
|
573
|
+
if (redraw) this.redrawList(this.get(id)?.rowIndex ?? 0);
|
|
509
574
|
safeRun(this.props.onToggle, id);
|
|
510
575
|
}
|
|
511
576
|
|
|
@@ -522,9 +587,10 @@ export class TreeApi<T> {
|
|
|
522
587
|
let parent = node?.parent;
|
|
523
588
|
|
|
524
589
|
while (parent) {
|
|
525
|
-
this.open(parent.id);
|
|
590
|
+
this.open(parent.id, false);
|
|
526
591
|
parent = parent.parent;
|
|
527
592
|
}
|
|
593
|
+
this.redrawList();
|
|
528
594
|
}
|
|
529
595
|
|
|
530
596
|
openSiblings(node: NodeApi<T>) {
|
|
@@ -535,23 +601,27 @@ export class TreeApi<T> {
|
|
|
535
601
|
const isOpen = node.isOpen;
|
|
536
602
|
for (let sibling of parent.children) {
|
|
537
603
|
if (sibling.isInternal) {
|
|
538
|
-
isOpen
|
|
604
|
+
if (isOpen) this.close(sibling.id, false);
|
|
605
|
+
else this.open(sibling.id, false);
|
|
539
606
|
}
|
|
540
607
|
}
|
|
608
|
+
this.redrawList();
|
|
541
609
|
this.scrollTo(this.focusedNode);
|
|
542
610
|
}
|
|
543
611
|
}
|
|
544
612
|
|
|
545
613
|
openAll() {
|
|
546
614
|
utils.walk(this.root, (node) => {
|
|
547
|
-
if (node.isInternal)
|
|
615
|
+
if (node.isInternal) this.open(node.id, false);
|
|
548
616
|
});
|
|
617
|
+
this.redrawList();
|
|
549
618
|
}
|
|
550
619
|
|
|
551
620
|
closeAll() {
|
|
552
621
|
utils.walk(this.root, (node) => {
|
|
553
|
-
if (node.isInternal)
|
|
622
|
+
if (node.isInternal) this.close(node.id, false);
|
|
554
623
|
});
|
|
624
|
+
this.redrawList();
|
|
555
625
|
}
|
|
556
626
|
|
|
557
627
|
/* Scrolling */
|
|
@@ -626,10 +696,7 @@ export class TreeApi<T> {
|
|
|
626
696
|
return this.isActionPossible(data, this.props.disableSelect);
|
|
627
697
|
}
|
|
628
698
|
|
|
629
|
-
private isActionPossible(
|
|
630
|
-
data: T,
|
|
631
|
-
disabler: string | boolean | BoolFunc<T> = () => false,
|
|
632
|
-
) {
|
|
699
|
+
private isActionPossible(data: T, disabler: string | boolean | BoolFunc<T> = () => false) {
|
|
633
700
|
return !utils.access(data, disabler);
|
|
634
701
|
}
|
|
635
702
|
|
package/src/state/dnd-slice.ts
CHANGED
package/src/state/drag-slice.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type DragSlice = {
|
|
|
15
15
|
|
|
16
16
|
export function reducer(
|
|
17
17
|
state: DragSlice = initialState().nodes.drag,
|
|
18
|
-
action: ActionTypes<typeof dnd
|
|
18
|
+
action: ActionTypes<typeof dnd>,
|
|
19
19
|
): DragSlice {
|
|
20
20
|
switch (action.type) {
|
|
21
21
|
case "DND_DRAG_START":
|
|
@@ -29,10 +29,7 @@ export function reducer(
|
|
|
29
29
|
selectedIds: [],
|
|
30
30
|
};
|
|
31
31
|
case "DND_HOVERING":
|
|
32
|
-
if (
|
|
33
|
-
action.parentId !== state.destinationParentId ||
|
|
34
|
-
action.index != state.destinationIndex
|
|
35
|
-
) {
|
|
32
|
+
if (action.parentId !== state.destinationParentId || action.index != state.destinationIndex) {
|
|
36
33
|
return {
|
|
37
34
|
...state,
|
|
38
35
|
destinationParentId: action.parentId,
|
package/src/state/edit-slice.ts
CHANGED
|
@@ -7,10 +7,7 @@ export function edit(id: string | null) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/* Reducer */
|
|
10
|
-
export function reducer(
|
|
11
|
-
state: EditState = { id: null },
|
|
12
|
-
action: ReturnType<typeof edit>
|
|
13
|
-
) {
|
|
10
|
+
export function reducer(state: EditState = { id: null }, action: ReturnType<typeof edit>) {
|
|
14
11
|
if (action.type === "EDIT") {
|
|
15
12
|
return { ...state, id: action.id };
|
|
16
13
|
} else {
|
package/src/state/focus-slice.ts
CHANGED
|
@@ -16,7 +16,7 @@ export function treeBlur() {
|
|
|
16
16
|
|
|
17
17
|
export function reducer(
|
|
18
18
|
state: FocusState = { id: null, treeFocused: false },
|
|
19
|
-
action: ReturnType<typeof focus> | ReturnType<typeof treeBlur
|
|
19
|
+
action: ReturnType<typeof focus> | ReturnType<typeof treeBlur>,
|
|
20
20
|
) {
|
|
21
21
|
if (action.type === "FOCUS") {
|
|
22
22
|
return { ...state, id: action.id, treeFocused: true };
|
package/src/state/open-slice.ts
CHANGED
|
@@ -22,10 +22,7 @@ export const actions = {
|
|
|
22
22
|
|
|
23
23
|
/* Reducer */
|
|
24
24
|
|
|
25
|
-
function openMapReducer(
|
|
26
|
-
state: OpenMap = {},
|
|
27
|
-
action: ActionTypes<typeof actions>
|
|
28
|
-
) {
|
|
25
|
+
function openMapReducer(state: OpenMap = {}, action: ActionTypes<typeof actions>) {
|
|
29
26
|
if (action.type === "VISIBILITY_OPEN") {
|
|
30
27
|
return { ...state, [action.id]: true };
|
|
31
28
|
} else if (action.type === "VISIBILITY_CLOSE") {
|
|
@@ -42,7 +39,7 @@ function openMapReducer(
|
|
|
42
39
|
|
|
43
40
|
export function reducer(
|
|
44
41
|
state: OpenSlice = { filtered: {}, unfiltered: {} },
|
|
45
|
-
action: ActionTypes<typeof actions
|
|
42
|
+
action: ActionTypes<typeof actions>,
|
|
46
43
|
): OpenSlice {
|
|
47
44
|
if (!action.type.startsWith("VISIBILITY")) return state;
|
|
48
45
|
if (action.filtered) {
|
|
@@ -28,11 +28,7 @@ export const actions = {
|
|
|
28
28
|
ids: (Array.isArray(id) ? id : [id]).map(identify),
|
|
29
29
|
}),
|
|
30
30
|
|
|
31
|
-
set: (args: {
|
|
32
|
-
ids: Set<string>;
|
|
33
|
-
anchor: string | null;
|
|
34
|
-
mostRecent: string | null;
|
|
35
|
-
}) => ({
|
|
31
|
+
set: (args: { ids: Set<string>; anchor: string | null; mostRecent: string | null }) => ({
|
|
36
32
|
type: "SELECTION_SET" as const,
|
|
37
33
|
...args,
|
|
38
34
|
}),
|
|
@@ -51,7 +47,7 @@ export const actions = {
|
|
|
51
47
|
/* Reducer */
|
|
52
48
|
export function reducer(
|
|
53
49
|
state: SelectionState = initialState()["nodes"]["selection"],
|
|
54
|
-
action: ActionTypes<typeof actions
|
|
50
|
+
action: ActionTypes<typeof actions>,
|
|
55
51
|
): SelectionState {
|
|
56
52
|
const ids = state.ids;
|
|
57
53
|
switch (action.type) {
|
package/src/types/dnd.ts
CHANGED
|
@@ -4,6 +4,11 @@ export type CursorLocation = {
|
|
|
4
4
|
parentId: string | null;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
export type DragItem = {
|
|
7
|
+
export type DragItem<T = any> = {
|
|
8
|
+
/* The id of the row the drag started on. */
|
|
8
9
|
id: string;
|
|
10
|
+
/* Every node carried by the drag (the selection, or just `id`). */
|
|
11
|
+
dragIds: string[];
|
|
12
|
+
/* The dragged node's data, so external drop targets can read it. */
|
|
13
|
+
data: T;
|
|
9
14
|
};
|
package/src/types/handlers.ts
CHANGED
|
@@ -27,6 +27,4 @@ export type DeleteHandler<T> = (args: {
|
|
|
27
27
|
nodes: NodeApi<T>[];
|
|
28
28
|
}) => void | Promise<void>;
|
|
29
29
|
|
|
30
|
-
export type EditResult =
|
|
31
|
-
| { cancelled: true }
|
|
32
|
-
| { cancelled: false; value: string };
|
|
30
|
+
export type EditResult = { cancelled: true } | { cancelled: false; value: string };
|
package/src/types/renderers.ts
CHANGED
package/src/types/state.ts
CHANGED
package/src/types/tree-props.ts
CHANGED
|
@@ -7,6 +7,9 @@ import { NodeApi } from "../interfaces/node-api";
|
|
|
7
7
|
import { OpenMap } from "../state/open-slice";
|
|
8
8
|
import { useDragDropManager, DndProviderProps } from "react-dnd";
|
|
9
9
|
|
|
10
|
+
/** Returns the height in pixels for a given node's row. */
|
|
11
|
+
export type RowHeightAccessor<T> = (node: NodeApi<T>) => number;
|
|
12
|
+
|
|
10
13
|
export interface TreeProps<T> {
|
|
11
14
|
/* Data Options */
|
|
12
15
|
data?: readonly T[];
|
|
@@ -26,7 +29,7 @@ export interface TreeProps<T> {
|
|
|
26
29
|
renderContainer?: ElementType<{}>;
|
|
27
30
|
|
|
28
31
|
/* Sizes */
|
|
29
|
-
rowHeight?: number
|
|
32
|
+
rowHeight?: number | RowHeightAccessor<T>;
|
|
30
33
|
overscanCount?: number;
|
|
31
34
|
width?: number | string;
|
|
32
35
|
height?: number;
|
|
@@ -47,11 +50,7 @@ export interface TreeProps<T> {
|
|
|
47
50
|
disableDrop?:
|
|
48
51
|
| string
|
|
49
52
|
| boolean
|
|
50
|
-
| ((args: {
|
|
51
|
-
parentNode: NodeApi<T>;
|
|
52
|
-
dragNodes: NodeApi<T>[];
|
|
53
|
-
index: number;
|
|
54
|
-
}) => boolean);
|
|
53
|
+
| ((args: { parentNode: NodeApi<T>; dragNodes: NodeApi<T>[]; index: number }) => boolean);
|
|
55
54
|
|
|
56
55
|
/* Event Handlers */
|
|
57
56
|
onActivate?: (node: NodeApi<T>) => void;
|
|
@@ -77,12 +76,18 @@ export interface TreeProps<T> {
|
|
|
77
76
|
dndRootElement?: globalThis.Node | null;
|
|
78
77
|
onClick?: MouseEventHandler;
|
|
79
78
|
onContextMenu?: MouseEventHandler;
|
|
80
|
-
dndBackend?: Extract<
|
|
81
|
-
DndProviderProps<unknown, unknown>,
|
|
82
|
-
{ backend: unknown }
|
|
83
|
-
>["backend"];
|
|
79
|
+
dndBackend?: Extract<DndProviderProps<unknown, unknown>, { backend: unknown }>["backend"];
|
|
84
80
|
dndManager?: ReturnType<typeof useDragDropManager>;
|
|
85
81
|
|
|
82
|
+
/* The react-dnd item type each row's drag source advertises. Defaults to
|
|
83
|
+
"NODE". Set a custom value (or a per-node function) so rows can be dropped
|
|
84
|
+
onto external react-dnd targets that accept that type. The dragged node's
|
|
85
|
+
data is always exposed on the drag item, so an external target accepting
|
|
86
|
+
the default "NODE" type can read it without setting this. Note: the tree's
|
|
87
|
+
own drop targets only accept "NODE", so a row given a custom type is no
|
|
88
|
+
longer reorderable within the tree. */
|
|
89
|
+
dragType?: string | ((node: NodeApi<T>) => string);
|
|
90
|
+
|
|
86
91
|
/* Custom react-window outer/inner elements */
|
|
87
92
|
outerElementType?: ReactWindowCommonProps["outerElementType"];
|
|
88
93
|
innerElementType?: ReactWindowCommonProps["innerElementType"];
|
package/src/types/utils.ts
CHANGED
|
@@ -9,9 +9,8 @@ export type Identity = string | IdObj | null;
|
|
|
9
9
|
|
|
10
10
|
export type BoolFunc<T> = (data: T) => boolean;
|
|
11
11
|
|
|
12
|
-
export type ActionTypes<
|
|
13
|
-
Actions
|
|
14
|
-
> = ReturnType<Actions[keyof Actions]>;
|
|
12
|
+
export type ActionTypes<Actions extends { [name: string]: (...args: any[]) => AnyAction }> =
|
|
13
|
+
ReturnType<Actions[keyof Actions]>;
|
|
15
14
|
|
|
16
15
|
export type SelectOptions = { multi?: boolean; contiguous?: boolean };
|
|
17
16
|
|
package/src/utils.ts
CHANGED
|
@@ -49,10 +49,7 @@ export function dfs(node: NodeApi<any>, id: string): NodeApi<any> | null {
|
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export function walk(
|
|
53
|
-
node: NodeApi<any>,
|
|
54
|
-
fn: (node: NodeApi<any>) => void
|
|
55
|
-
): void {
|
|
52
|
+
export function walk(node: NodeApi<any>, fn: (node: NodeApi<any>) => void): void {
|
|
56
53
|
fn(node);
|
|
57
54
|
if (node.children) {
|
|
58
55
|
for (let child of node.children) {
|
|
@@ -110,15 +107,12 @@ function prevItem(list: HTMLElement[], index: number) {
|
|
|
110
107
|
function getFocusable(target: HTMLElement) {
|
|
111
108
|
return Array.from(
|
|
112
109
|
document.querySelectorAll(
|
|
113
|
-
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), details:not([disabled]), summary:not(:disabled)'
|
|
114
|
-
)
|
|
110
|
+
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), details:not([disabled]), summary:not(:disabled)',
|
|
111
|
+
),
|
|
115
112
|
).filter((e) => e === target || !target.contains(e)) as HTMLElement[];
|
|
116
113
|
}
|
|
117
114
|
|
|
118
|
-
export function access<T = boolean>(
|
|
119
|
-
obj: any,
|
|
120
|
-
accessor: string | boolean | Function
|
|
121
|
-
): T {
|
|
115
|
+
export function access<T = boolean>(obj: any, accessor: string | boolean | Function): T {
|
|
122
116
|
if (typeof accessor === "boolean") return accessor as unknown as T;
|
|
123
117
|
if (typeof accessor === "string") return obj[accessor] as T;
|
|
124
118
|
return accessor(obj) as T;
|
|
@@ -234,10 +228,7 @@ const defaultTreeLineChars: TreeLineChars = {
|
|
|
234
228
|
* getTreeLinePrefix(node, { last: "`- ", middle: "|- ", pipe: "|", blank: " " })
|
|
235
229
|
* ```
|
|
236
230
|
*/
|
|
237
|
-
export function getTreeLinePrefix(
|
|
238
|
-
node: NodeApi<any>,
|
|
239
|
-
chars: Partial<TreeLineChars> = {}
|
|
240
|
-
): string {
|
|
231
|
+
export function getTreeLinePrefix(node: NodeApi<any>, chars: Partial<TreeLineChars> = {}): string {
|
|
241
232
|
const c = { ...defaultTreeLineChars, ...chars };
|
|
242
233
|
if (node.level === 0) return "";
|
|
243
234
|
|