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
@@ -0,0 +1,552 @@
1
+ import { EditResult } from "../types/handlers";
2
+ import { Identity, IdObj } from "../types/utils";
3
+ import { TreeProps } from "../types/tree-props";
4
+ import { MutableRefObject } from "react";
5
+ import {
6
+ Align,
7
+ FixedSizeList,
8
+ ListOnItemsRenderedProps,
9
+ ListOnScrollProps,
10
+ } from "react-window";
11
+ import * as utils from "../utils";
12
+ import { DefaultCursor } from "../components/default-cursor";
13
+ import { DefaultRow } from "../components/default-row";
14
+ import { DefaultNode } from "../components/default-node";
15
+ import { NodeApi } from "./node-api";
16
+ import { edit } from "../state/edit-slice";
17
+ import { Actions, RootState } from "../state/root-reducer";
18
+ import { focus, treeBlur } from "../state/focus-slice";
19
+ import { createRoot, ROOT_ID } from "../data/create-root";
20
+ import { actions as visibility } from "../state/open-slice";
21
+ import { actions as selection } from "../state/selection-slice";
22
+ import { actions as dnd } from "../state/dnd-slice";
23
+ import { DefaultDragPreview } from "../components/default-drag-preview";
24
+ import { DefaultContainer } from "../components/default-container";
25
+ import { Cursor } from "../dnd/compute-drop";
26
+ import { Store } from "redux";
27
+ import { createList } from "../data/create-list";
28
+ import { createIndex } from "../data/create-index";
29
+
30
+ const { safeRun, identify, identifyNull } = utils;
31
+ export class TreeApi<T extends IdObj> {
32
+ static editPromise: null | ((args: EditResult) => void);
33
+ root: NodeApi<T>;
34
+ visibleNodes: NodeApi<T>[];
35
+ visibleStartIndex: number = 0;
36
+ visibleStopIndex: number = 0;
37
+ idToIndex: { [id: string]: number };
38
+
39
+ constructor(
40
+ public store: Store<RootState, Actions>,
41
+ public props: TreeProps<T>,
42
+ public list: MutableRefObject<FixedSizeList | null>,
43
+ public listEl: MutableRefObject<HTMLDivElement | null>
44
+ ) {
45
+ /* Changes here must also be made in update() */
46
+ this.root = createRoot<T>(this);
47
+ this.visibleNodes = createList<T>(this);
48
+ this.idToIndex = createIndex(this.visibleNodes);
49
+ }
50
+
51
+ /* Changes here must also be made in constructor() */
52
+ update(props: TreeProps<T>) {
53
+ this.props = props;
54
+ this.root = createRoot<T>(this);
55
+ this.visibleNodes = createList<T>(this);
56
+ this.idToIndex = createIndex(this.visibleNodes);
57
+ }
58
+
59
+ /* Store helpers */
60
+
61
+ dispatch(action: Actions) {
62
+ return this.store.dispatch(action);
63
+ }
64
+
65
+ get state() {
66
+ return this.store.getState();
67
+ }
68
+
69
+ get openState() {
70
+ return this.state.nodes.open.unfiltered;
71
+ }
72
+
73
+ /* Tree Props */
74
+
75
+ get width() {
76
+ return this.props.width || 300;
77
+ }
78
+
79
+ get height() {
80
+ return this.props.height || 500;
81
+ }
82
+
83
+ get indent() {
84
+ return this.props.indent || 24;
85
+ }
86
+
87
+ get rowHeight() {
88
+ return this.props.rowHeight || 24;
89
+ }
90
+
91
+ get searchTerm() {
92
+ return (this.props.searchTerm || "").trim();
93
+ }
94
+
95
+ get matchFn() {
96
+ const match =
97
+ this.props.searchMatch ??
98
+ ((node, term) => {
99
+ const string = JSON.stringify(Object.values(node.data));
100
+ return string.toLocaleLowerCase().includes(term.toLocaleLowerCase());
101
+ });
102
+ return (node: NodeApi<T>) => match(node, this.searchTerm);
103
+ }
104
+
105
+ getChildren(data: T) {
106
+ const get = this.props.getChildren || "children";
107
+ return utils.access<T[] | undefined>(data, get) ?? null;
108
+ }
109
+
110
+ /* Node Access */
111
+
112
+ get firstNode() {
113
+ return this.visibleNodes[0] ?? null;
114
+ }
115
+
116
+ get lastNode() {
117
+ return this.visibleNodes[this.visibleNodes.length - 1] ?? null;
118
+ }
119
+
120
+ get focusedNode() {
121
+ return this.get(this.state.nodes.focus.id) ?? null;
122
+ }
123
+
124
+ get mostRecentNode() {
125
+ return this.get(this.state.nodes.selection.mostRecent) ?? null;
126
+ }
127
+
128
+ get nextNode() {
129
+ const index = this.indexOf(this.focusedNode);
130
+ if (index === null) return null;
131
+ else return this.at(index + 1);
132
+ }
133
+
134
+ get prevNode() {
135
+ const index = this.indexOf(this.focusedNode);
136
+ if (index === null) return null;
137
+ else return this.at(index - 1);
138
+ }
139
+
140
+ get(id: string | null): NodeApi<T> | null {
141
+ if (!id) return null;
142
+ if (id in this.idToIndex)
143
+ return this.visibleNodes[this.idToIndex[id]] || null;
144
+ else return null;
145
+ }
146
+
147
+ at(index: number): NodeApi<T> | null {
148
+ return this.visibleNodes[index] || null;
149
+ }
150
+
151
+ nodesBetween(startId: string | null, endId: string | null) {
152
+ if (startId === null || endId === null) return [];
153
+ const index1 = this.indexOf(startId) ?? 0;
154
+ const index2 = this.indexOf(endId);
155
+ if (index2 === null) return [];
156
+ const start = Math.min(index1, index2);
157
+ const end = Math.max(index1, index2);
158
+ return this.visibleNodes.slice(start, end + 1);
159
+ }
160
+
161
+ indexOf(id: string | null | IdObj) {
162
+ const key = utils.identifyNull(id);
163
+ if (!key) return null;
164
+ return this.idToIndex[key];
165
+ }
166
+
167
+ /* Data Operations */
168
+
169
+ get editingId() {
170
+ return this.state.nodes.edit.id;
171
+ }
172
+
173
+ createInternal() {
174
+ return this.create("internal");
175
+ }
176
+
177
+ createLeaf() {
178
+ return this.create("leaf");
179
+ }
180
+
181
+ private async create(type: "internal" | "leaf") {
182
+ let index;
183
+ let parentId;
184
+ const focus = this.focusedNode;
185
+ if (focus && focus.parent) {
186
+ if (focus.isInternal && focus.isOpen) {
187
+ parentId = focus.id;
188
+ index = 0;
189
+ } else {
190
+ index = focus.childIndex + 1;
191
+ parentId = focus.parent.isRoot ? null : focus.parent.id;
192
+ }
193
+ } else {
194
+ index = this.root?.children?.length || -1;
195
+ parentId = null;
196
+ }
197
+ const data = await safeRun(this.props.onCreate, {
198
+ parentId,
199
+ index,
200
+ type,
201
+ });
202
+ if (data) {
203
+ this.focus(data);
204
+ setTimeout(() => {
205
+ this.edit(data).then(() => {
206
+ this.select(data);
207
+ this.activate(data);
208
+ });
209
+ });
210
+ }
211
+ }
212
+
213
+ async delete(node: string | IdObj | null | string[] | IdObj[]) {
214
+ if (!node) return;
215
+ const nodes = Array.isArray(node) ? node : [node];
216
+ const ids = nodes.map(identify);
217
+ await safeRun(this.props.onDelete, { ids });
218
+ }
219
+
220
+ edit(node: string | IdObj): Promise<EditResult> {
221
+ const id = identify(node);
222
+ this.resolveEdit({ cancelled: true });
223
+ this.scrollTo(id);
224
+ this.dispatch(edit(id));
225
+ return new Promise((resolve) => {
226
+ TreeApi.editPromise = resolve;
227
+ });
228
+ }
229
+
230
+ async submit(node: Identity, value: string) {
231
+ if (!node) return;
232
+ const id = identify(node);
233
+ await safeRun(this.props.onRename, { id, name: value });
234
+ this.dispatch(edit(null));
235
+ this.resolveEdit({ cancelled: false, value });
236
+ setTimeout(() => this.onFocus()); // Return focus to element;
237
+ }
238
+
239
+ reset() {
240
+ this.dispatch(edit(null));
241
+ this.resolveEdit({ cancelled: true });
242
+ setTimeout(() => this.onFocus()); // Return focus to element;
243
+ }
244
+
245
+ activate(id: string | IdObj | null) {
246
+ const node = this.get(identifyNull(id));
247
+ if (!node) return;
248
+ safeRun(this.props.onActivate, node);
249
+ }
250
+
251
+ private resolveEdit(value: EditResult) {
252
+ const resolve = TreeApi.editPromise;
253
+ if (resolve) resolve(value);
254
+ TreeApi.editPromise = null;
255
+ }
256
+
257
+ /* Focus and Selection */
258
+
259
+ get selectedIds() {
260
+ return this.state.nodes.selection.ids;
261
+ }
262
+
263
+ get selectedNodes() {
264
+ let nodes = [];
265
+ for (let id of Array.from(this.selectedIds)) {
266
+ const node = this.get(id);
267
+ if (node) nodes.push(node);
268
+ }
269
+ return nodes;
270
+ }
271
+
272
+ focus(node: Identity, opts: { scroll?: boolean } = {}) {
273
+ if (!node) return;
274
+ /* Focus is responsible for scrolling, while selection is
275
+ * responsible for focus. If selectionFollowsFocus, then
276
+ * just select it. */
277
+ if (this.props.selectionFollowsFocus) {
278
+ this.select(node);
279
+ } else {
280
+ this.dispatch(focus(identify(node)));
281
+ if (opts.scroll !== false) this.scrollTo(node);
282
+ }
283
+ }
284
+
285
+ pageUp() {
286
+ const start = this.visibleStartIndex;
287
+ const stop = this.visibleStopIndex;
288
+ const page = stop - start;
289
+ let index = this.focusedNode?.rowIndex ?? 0;
290
+ if (index > start) {
291
+ index = start;
292
+ } else {
293
+ index = Math.max(start - page, 0);
294
+ }
295
+ this.focus(this.at(index));
296
+ }
297
+
298
+ pageDown() {
299
+ const start = this.visibleStartIndex;
300
+ const stop = this.visibleStopIndex;
301
+ const page = stop - start;
302
+ let index = this.focusedNode?.rowIndex ?? 0;
303
+ if (index < stop) {
304
+ index = stop;
305
+ } else {
306
+ index = Math.min(index + page, this.visibleNodes.length - 1);
307
+ }
308
+ this.focus(this.at(index));
309
+ }
310
+
311
+ select(node: Identity, opts: { align?: Align } = {}) {
312
+ if (!node) return;
313
+ const id = identify(node);
314
+ this.dispatch(focus(id));
315
+ this.dispatch(selection.only(id));
316
+ this.dispatch(selection.anchor(id));
317
+ this.dispatch(selection.mostRecent(id));
318
+ this.scrollTo(id, opts.align);
319
+ safeRun(this.props.onSelect, this.selectedNodes);
320
+ }
321
+
322
+ deselect(node: Identity) {
323
+ if (!node) return;
324
+ const id = identify(node);
325
+ this.dispatch(selection.remove(id));
326
+ }
327
+
328
+ selectMulti(identity: Identity) {
329
+ const node = this.get(identifyNull(identity));
330
+ if (!node) return;
331
+ this.dispatch(focus(node.id));
332
+ this.dispatch(selection.add(node.id));
333
+ this.dispatch(selection.anchor(node.id));
334
+ this.dispatch(selection.mostRecent(node.id));
335
+ this.scrollTo(node);
336
+ safeRun(this.props.onSelect, this.selectedNodes);
337
+ }
338
+
339
+ selectContiguous(identity: Identity) {
340
+ if (!identity) return;
341
+ const id = identify(identity);
342
+ const { anchor, mostRecent } = this.state.nodes.selection;
343
+ this.dispatch(focus(id));
344
+ this.dispatch(selection.remove(this.nodesBetween(anchor, mostRecent)));
345
+ this.dispatch(selection.add(this.nodesBetween(anchor, identifyNull(id))));
346
+ this.dispatch(selection.mostRecent(id));
347
+ this.scrollTo(id);
348
+ safeRun(this.props.onSelect, this.selectedNodes);
349
+ }
350
+
351
+ selectNone() {
352
+ this.dispatch(selection.clear());
353
+ this.dispatch(selection.anchor(null));
354
+ this.dispatch(selection.mostRecent(null));
355
+ safeRun(this.props.onSelect, this.selectedNodes);
356
+ }
357
+
358
+ selectAll() {
359
+ this.dispatch(selection.set(new Set(Object.keys(this.idToIndex))));
360
+ this.dispatch(focus(this.lastNode?.id));
361
+ this.dispatch(selection.anchor(this.firstNode));
362
+ this.dispatch(selection.mostRecent(this.lastNode));
363
+ safeRun(this.props.onSelect, this.selectedNodes);
364
+ }
365
+
366
+ /* Drag and Drop */
367
+
368
+ get cursorParentId() {
369
+ const { cursor } = this.state.dnd;
370
+ switch (cursor.type) {
371
+ case "highlight":
372
+ return cursor.id;
373
+ default:
374
+ return null;
375
+ }
376
+ }
377
+
378
+ get cursorOverFolder() {
379
+ return this.state.dnd.cursor.type === "highlight";
380
+ }
381
+
382
+ hideCursor() {
383
+ this.dispatch(dnd.cursor({ type: "none" }));
384
+ }
385
+
386
+ showCursor(cursor: Cursor) {
387
+ this.dispatch(dnd.cursor(cursor));
388
+ }
389
+
390
+ /* Visibility */
391
+
392
+ open(identity: Identity) {
393
+ const id = identifyNull(identity);
394
+ if (!id) return;
395
+ this.dispatch(visibility.open(id, this.isFiltered));
396
+ }
397
+
398
+ close(identity: Identity) {
399
+ const id = identifyNull(identity);
400
+ if (!id) return;
401
+ this.dispatch(visibility.close(id, this.isFiltered));
402
+ }
403
+
404
+ toggle(identity: Identity) {
405
+ const id = identifyNull(identity);
406
+ if (!id) return;
407
+ return this.isOpen(id) ? this.close(id) : this.open(id);
408
+ }
409
+
410
+ openParents(identity: Identity) {
411
+ const id = identifyNull(identity);
412
+ if (!id) return;
413
+ const node = utils.dfs(this.root, id);
414
+ let parent = node?.parent;
415
+
416
+ while (parent) {
417
+ this.open(parent.id);
418
+ parent = parent.parent;
419
+ }
420
+ }
421
+
422
+ openSiblings(node: NodeApi<T>) {
423
+ const parent = node.parent;
424
+ if (!parent) {
425
+ this.toggle(node.id);
426
+ } else if (parent.children) {
427
+ const isOpen = node.isOpen;
428
+ for (let sibling of parent.children) {
429
+ if (sibling.isInternal) {
430
+ isOpen ? this.close(sibling.id) : this.open(sibling.id);
431
+ }
432
+ }
433
+ this.scrollTo(this.focusedNode);
434
+ }
435
+ }
436
+
437
+ /* Scrolling */
438
+
439
+ scrollTo(identity: Identity, align: Align = "smart") {
440
+ if (!identity) return;
441
+ const id = identify(identity);
442
+ this.openParents(id);
443
+ return utils
444
+ .waitFor(() => id in this.idToIndex)
445
+ .then(() => {
446
+ const index = this.idToIndex[id];
447
+ if (index === undefined) return;
448
+ this.list.current?.scrollToItem(index, align);
449
+ })
450
+ .catch(() => {
451
+ console.log(`Id: ${id} never appeared in the list.`);
452
+ });
453
+ }
454
+
455
+ /* State Checks */
456
+
457
+ get isEditing() {
458
+ return this.state.nodes.edit.id !== null;
459
+ }
460
+
461
+ get isFiltered() {
462
+ return !!this.props.searchTerm?.trim();
463
+ }
464
+
465
+ get hasFocus() {
466
+ return this.state.nodes.focus.treeFocused;
467
+ }
468
+
469
+ isSelected(id?: string) {
470
+ if (!id) return false;
471
+ return this.state.nodes.selection.ids.has(id);
472
+ }
473
+
474
+ isOpen(id?: string) {
475
+ if (!id) return false;
476
+ if (id === ROOT_ID) return true;
477
+ const def = this.props.openByDefault ?? true;
478
+ if (this.isFiltered) {
479
+ return this.state.nodes.open.filtered[id] ?? true; // Filtered folders are always opened by default
480
+ } else {
481
+ return this.state.nodes.open.unfiltered[id] ?? def;
482
+ }
483
+ }
484
+
485
+ isDraggable(data: T) {
486
+ const check = this.props.disableDrag || (() => false);
487
+ return !utils.access(data, check) ?? true;
488
+ }
489
+
490
+ isDroppable(data: T) {
491
+ const check = this.props.disableDrop || (() => false);
492
+ return !utils.access(data, check) ?? true;
493
+ }
494
+
495
+ isDragging(node: string | IdObj | null) {
496
+ const id = identifyNull(node);
497
+ if (!id) return false;
498
+ return this.state.nodes.drag.id === id;
499
+ }
500
+
501
+ isFocused(id: string) {
502
+ return this.hasFocus && this.state.nodes.focus.id === id;
503
+ }
504
+
505
+ isMatch(node: NodeApi<T>) {
506
+ return this.matchFn(node);
507
+ }
508
+
509
+ willReceiveDrop(node: string | IdObj | null) {
510
+ const id = identifyNull(node);
511
+ if (!id) return false;
512
+ return id === this.state.nodes.drag.idWillReceiveDrop;
513
+ }
514
+
515
+ /* Tree Event Handlers */
516
+
517
+ onFocus() {
518
+ const node = this.focusedNode || this.firstNode;
519
+ if (node) this.dispatch(focus(node.id));
520
+ }
521
+
522
+ onBlur() {
523
+ this.dispatch(treeBlur());
524
+ }
525
+
526
+ onItemsRendered(args: ListOnItemsRenderedProps) {
527
+ this.visibleStartIndex = args.visibleStartIndex;
528
+ this.visibleStopIndex = args.visibleStopIndex;
529
+ }
530
+
531
+ /* Get Renderers */
532
+
533
+ get renderContainer() {
534
+ return this.props.renderContainer || DefaultContainer;
535
+ }
536
+
537
+ get renderRow() {
538
+ return this.props.renderRow || DefaultRow;
539
+ }
540
+
541
+ get renderNode() {
542
+ return this.props.children || DefaultNode;
543
+ }
544
+
545
+ get renderDragPreview() {
546
+ return this.props.renderDragPreview || DefaultDragPreview;
547
+ }
548
+
549
+ get renderCursor() {
550
+ return this.props.renderCursor || DefaultCursor;
551
+ }
552
+ }
@@ -0,0 +1,36 @@
1
+ import { Cursor } from "../dnd/compute-drop";
2
+ import { ActionTypes } from "../types/utils";
3
+ import { initialState } from "./initial";
4
+
5
+ /* Types */
6
+ export type DndState = { dragId: null | string; cursor: Cursor };
7
+
8
+ /* Actions */
9
+ export const actions = {
10
+ cursor(cursor: Cursor) {
11
+ return { type: "DND_CURSOR" as const, cursor };
12
+ },
13
+ dragStart(id: string) {
14
+ return { type: "DND_DRAG_START" as const, id };
15
+ },
16
+ dragEnd() {
17
+ return { type: "DND_DRAG_END" as const };
18
+ },
19
+ };
20
+
21
+ /* Reducer */
22
+ export function reducer(
23
+ state: DndState = initialState()["dnd"],
24
+ action: ActionTypes<typeof actions>
25
+ ) {
26
+ switch (action.type) {
27
+ case "DND_CURSOR":
28
+ return { ...state, cursor: action.cursor };
29
+ case "DND_DRAG_START":
30
+ return { ...state, dragId: action.id };
31
+ case "DND_DRAG_END":
32
+ return { ...state, dragId: null };
33
+ default:
34
+ return state;
35
+ }
36
+ }
@@ -0,0 +1,31 @@
1
+ import { ActionTypes } from "../types/utils";
2
+ import { actions as dnd } from "./dnd-slice";
3
+
4
+ /* Types */
5
+
6
+ export type DragSlice = { id: string | null; idWillReceiveDrop: string | null };
7
+
8
+ /* Reducer */
9
+
10
+ export function reducer(
11
+ state: DragSlice = { id: null, idWillReceiveDrop: null },
12
+ action: ActionTypes<typeof dnd>
13
+ ) {
14
+ switch (action.type) {
15
+ case "DND_DRAG_START":
16
+ return { ...state, id: action.id };
17
+ case "DND_DRAG_END":
18
+ return { ...state, id: null };
19
+ case "DND_CURSOR":
20
+ const c = action.cursor;
21
+ if (c.type === "highlight" && c.id !== state.idWillReceiveDrop) {
22
+ return { ...state, idWillReceiveDrop: c.id };
23
+ } else if (c.type !== "highlight" && state.idWillReceiveDrop !== null) {
24
+ return { ...state, idWillReceiveDrop: null };
25
+ } else {
26
+ return state;
27
+ }
28
+ default:
29
+ return state;
30
+ }
31
+ }
@@ -0,0 +1,19 @@
1
+ /* Types */
2
+ export type EditState = { id: string | null };
3
+
4
+ /* Actions */
5
+ export function edit(id: string | null) {
6
+ return { type: "EDIT" as const, id };
7
+ }
8
+
9
+ /* Reducer */
10
+ export function reducer(
11
+ state: EditState = { id: null },
12
+ action: ReturnType<typeof edit>
13
+ ) {
14
+ if (action.type === "EDIT") {
15
+ return { ...state, id: action.id };
16
+ } else {
17
+ return state;
18
+ }
19
+ }
@@ -0,0 +1,28 @@
1
+ /* Types */
2
+
3
+ export type FocusState = { id: string | null; treeFocused: boolean };
4
+
5
+ /* Actions */
6
+
7
+ export function focus(id: string | null) {
8
+ return { type: "FOCUS" as const, id };
9
+ }
10
+
11
+ export function treeBlur() {
12
+ return { type: "TREE_BLUR" } as const;
13
+ }
14
+
15
+ /* Reducer */
16
+
17
+ export function reducer(
18
+ state: FocusState = { id: null, treeFocused: false },
19
+ action: ReturnType<typeof focus> | ReturnType<typeof treeBlur>
20
+ ) {
21
+ if (action.type === "FOCUS") {
22
+ return { ...state, id: action.id, treeFocused: true };
23
+ } else if (action.type === "TREE_BLUR") {
24
+ return { ...state, treeFocused: false };
25
+ } else {
26
+ return state;
27
+ }
28
+ }
@@ -0,0 +1,14 @@
1
+ import { TreeProps } from "../types/tree-props";
2
+ import { RootState } from "./root-reducer";
3
+
4
+ export const initialState = (props?: TreeProps<any>): RootState => ({
5
+ nodes: {
6
+ // Changes together
7
+ open: { filtered: {}, unfiltered: props?.initialOpenState ?? {} },
8
+ focus: { id: null, treeFocused: false },
9
+ edit: { id: null },
10
+ drag: { id: null, idWillReceiveDrop: null },
11
+ selection: { ids: new Set(), anchor: null, mostRecent: null },
12
+ },
13
+ dnd: { cursor: { type: "none" }, dragId: null },
14
+ });