react-arborist 2.0.0-rc.1 → 2.0.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 +60 -21
- package/dist/components/cursor.d.ts +1 -1
- package/dist/components/default-cursor.d.ts +2 -2
- package/dist/index.js +112 -44
- package/dist/index.js.map +1 -1
- package/dist/interfaces/node-api.d.ts +9 -4
- package/dist/interfaces/tree-api.d.ts +11 -2
- package/dist/module.js +112 -44
- package/dist/module.js.map +1 -1
- package/dist/types/renderers.d.ts +1 -1
- package/dist/types/tree-props.d.ts +4 -1
- package/dist/utils.d.ts +4 -0
- package/package.json +13 -2
- package/src/components/cursor.tsx +1 -1
- package/src/components/default-cursor.tsx +2 -2
- package/src/components/list-outer-element.tsx +2 -5
- package/src/components/row-container.tsx +1 -0
- package/src/dnd/outer-drop-hook.ts +32 -6
- package/src/interfaces/node-api.ts +15 -4
- package/src/interfaces/tree-api.ts +54 -27
- package/src/types/renderers.ts +1 -1
- package/src/types/tree-props.ts +5 -1
- package/src/utils.ts +29 -0
|
@@ -15,7 +15,7 @@ export interface TreeProps<T extends IdObj> {
|
|
|
15
15
|
children?: ElementType<renderers.NodeRendererProps<T>>;
|
|
16
16
|
renderRow?: ElementType<renderers.RowRendererProps<T>>;
|
|
17
17
|
renderDragPreview?: ElementType<renderers.DragPreviewProps>;
|
|
18
|
-
renderCursor?: ElementType<renderers.
|
|
18
|
+
renderCursor?: ElementType<renderers.CursorProps>;
|
|
19
19
|
renderContainer?: ElementType<{}>;
|
|
20
20
|
rowHeight?: number;
|
|
21
21
|
width?: number;
|
|
@@ -33,11 +33,14 @@ export interface TreeProps<T extends IdObj> {
|
|
|
33
33
|
onActivate?: (node: NodeApi<T>) => void;
|
|
34
34
|
onSelect?: (nodes: NodeApi<T>[]) => void;
|
|
35
35
|
onScroll?: (props: ListOnScrollProps) => void;
|
|
36
|
+
onToggle?: (id: string) => void;
|
|
37
|
+
onFocus?: (node: NodeApi<T>) => void;
|
|
36
38
|
selection?: string;
|
|
37
39
|
initialOpenState?: OpenMap;
|
|
38
40
|
searchTerm?: string;
|
|
39
41
|
searchMatch?: (node: NodeApi<T>, searchTerm: string) => boolean;
|
|
40
42
|
className?: string | undefined;
|
|
43
|
+
rowClassName?: string | undefined;
|
|
41
44
|
dndRootElement?: globalThis.Node | null;
|
|
42
45
|
onClick?: MouseEventHandler;
|
|
43
46
|
onContextMenu?: MouseEventHandler;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NodeApi } from "./interfaces/node-api";
|
|
2
|
+
import { TreeApi } from "./interfaces/tree-api";
|
|
2
3
|
import { IdObj } from "./types/utils";
|
|
3
4
|
export declare function bound(n: number, min: number, max: number): number;
|
|
4
5
|
export declare function isItem(node: NodeApi<any> | null): boolean | null;
|
|
@@ -10,6 +11,7 @@ export declare const isDecendent: (a: NodeApi<any>, b: NodeApi<any>) => boolean;
|
|
|
10
11
|
export declare const indexOf: (node: NodeApi<any>) => number;
|
|
11
12
|
export declare function noop(): void;
|
|
12
13
|
export declare function dfs(node: NodeApi<any>, id: string): NodeApi<any> | null;
|
|
14
|
+
export declare function walk(node: NodeApi<any>, fn: (node: NodeApi<any>) => void): void;
|
|
13
15
|
export declare function focusNextElement(target: HTMLElement): void;
|
|
14
16
|
export declare function focusPrevElement(target: HTMLElement): void;
|
|
15
17
|
export declare function access<T = boolean>(obj: any, accessor: string | boolean | Function): T;
|
|
@@ -18,3 +20,5 @@ export declare function identify(obj: string | IdObj): string;
|
|
|
18
20
|
export declare function mergeRefs(...refs: any): (instance: any) => void;
|
|
19
21
|
export declare function safeRun<T extends (...args: any[]) => any>(fn: T | undefined, ...args: Parameters<T>): any;
|
|
20
22
|
export declare function waitFor(fn: () => boolean): Promise<void>;
|
|
23
|
+
export declare function getInsertIndex(tree: TreeApi<any>): number;
|
|
24
|
+
export declare function getInsertParentId(tree: TreeApi<any>): string | null;
|
package/package.json
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-arborist",
|
|
3
|
-
"version": "2.0.0
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"source": "src/index.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/module.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
|
-
"repository": "github
|
|
9
|
+
"repository": "https://github.com/brimdata/react-arborist",
|
|
10
10
|
"homepage": "https://react-arborist.netlify.app",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react",
|
|
13
|
+
"arborist",
|
|
14
|
+
"react-arborist",
|
|
15
|
+
"treeview",
|
|
16
|
+
"tree",
|
|
17
|
+
"vitualized",
|
|
18
|
+
"dnd",
|
|
19
|
+
"multiselection",
|
|
20
|
+
"filterable"
|
|
21
|
+
],
|
|
11
22
|
"dependencies": {
|
|
12
23
|
"react-dnd": "^14.0.3",
|
|
13
24
|
"react-dnd-html5-backend": "^14.0.1",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { CSSProperties } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { CursorProps } from "../types/renderers";
|
|
3
3
|
|
|
4
4
|
const placeholderStyle = {
|
|
5
5
|
display: "flex",
|
|
@@ -25,7 +25,7 @@ export const DefaultCursor = React.memo(function DefaultCursor({
|
|
|
25
25
|
top,
|
|
26
26
|
left,
|
|
27
27
|
indent,
|
|
28
|
-
}:
|
|
28
|
+
}: CursorProps) {
|
|
29
29
|
const style: CSSProperties = {
|
|
30
30
|
position: "absolute",
|
|
31
31
|
pointerEvents: "none",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { forwardRef } from "react";
|
|
2
2
|
import { useTreeApi } from "../context";
|
|
3
3
|
import { treeBlur } from "../state/focus-slice";
|
|
4
|
-
import {
|
|
4
|
+
import { Cursor } from "./cursor";
|
|
5
5
|
|
|
6
6
|
export const ListOuterElement = forwardRef(function Outer(
|
|
7
7
|
props: React.HTMLProps<HTMLDivElement>,
|
|
@@ -35,11 +35,8 @@ const DropContainer = () => {
|
|
|
35
35
|
left: "0",
|
|
36
36
|
right: "0",
|
|
37
37
|
}}
|
|
38
|
-
onClick={(e) => {
|
|
39
|
-
console.log(e.currentTarget, e.target);
|
|
40
|
-
}}
|
|
41
38
|
>
|
|
42
|
-
<
|
|
39
|
+
<Cursor />
|
|
43
40
|
</div>
|
|
44
41
|
);
|
|
45
42
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useDrop } from "react-dnd";
|
|
2
2
|
import { useTreeApi } from "../context";
|
|
3
3
|
import { DragItem } from "../types/dnd";
|
|
4
|
+
import { isDecendent } from "../utils";
|
|
4
5
|
import { computeDrop } from "./compute-drop";
|
|
5
6
|
import { DropResult } from "./drop-hook";
|
|
6
7
|
|
|
@@ -13,9 +14,28 @@ export function useOuterDrop() {
|
|
|
13
14
|
accept: "NODE",
|
|
14
15
|
hover: (item, m) => {
|
|
15
16
|
if (!m.isOver({ shallow: true })) return;
|
|
17
|
+
if (m.canDrop()) {
|
|
18
|
+
const offset = m.getClientOffset();
|
|
19
|
+
if (!tree.listEl.current || !offset) return;
|
|
20
|
+
const { cursor } = computeDrop({
|
|
21
|
+
element: tree.listEl.current,
|
|
22
|
+
offset: offset,
|
|
23
|
+
indent: tree.indent,
|
|
24
|
+
node: null,
|
|
25
|
+
prevNode: tree.visibleNodes[tree.visibleNodes.length - 1],
|
|
26
|
+
nextNode: null,
|
|
27
|
+
});
|
|
28
|
+
if (cursor) tree.showCursor(cursor);
|
|
29
|
+
} else {
|
|
30
|
+
tree.hideCursor();
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
canDrop: (item, m) => {
|
|
34
|
+
if (!m.isOver({ shallow: true })) return false;
|
|
35
|
+
if (tree.isFiltered) return false;
|
|
16
36
|
const offset = m.getClientOffset();
|
|
17
|
-
if (!tree.listEl.current || !offset) return;
|
|
18
|
-
const {
|
|
37
|
+
if (!tree.listEl.current || !offset) return false;
|
|
38
|
+
const { drop } = computeDrop({
|
|
19
39
|
element: tree.listEl.current,
|
|
20
40
|
offset: offset,
|
|
21
41
|
indent: tree.indent,
|
|
@@ -23,10 +43,16 @@ export function useOuterDrop() {
|
|
|
23
43
|
prevNode: tree.visibleNodes[tree.visibleNodes.length - 1],
|
|
24
44
|
nextNode: null,
|
|
25
45
|
});
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
46
|
+
if (!drop) return false;
|
|
47
|
+
const dropParent = tree.get(drop.parentId) ?? tree.root;
|
|
48
|
+
|
|
49
|
+
for (let id of item.dragIds) {
|
|
50
|
+
const drag = tree.get(id);
|
|
51
|
+
if (!drag) return false;
|
|
52
|
+
if (!dropParent) return false;
|
|
53
|
+
if (drag.isInternal && isDecendent(dropParent, drag)) return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
30
56
|
},
|
|
31
57
|
drop: (item, m) => {
|
|
32
58
|
if (m.didDrop()) return;
|
|
@@ -54,6 +54,10 @@ export class NodeApi<T extends IdObj = IdObj> {
|
|
|
54
54
|
return this.isLeaf ? false : this.tree.isOpen(this.id);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
get isClosed() {
|
|
58
|
+
return this.isLeaf ? false : !this.tree.isOpen(this.id);
|
|
59
|
+
}
|
|
60
|
+
|
|
57
61
|
get isEditing() {
|
|
58
62
|
return this.tree.editingId === this.id;
|
|
59
63
|
}
|
|
@@ -62,6 +66,10 @@ export class NodeApi<T extends IdObj = IdObj> {
|
|
|
62
66
|
return this.tree.isSelected(this.id);
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
get isOnlySelection() {
|
|
70
|
+
return this.isSelected && this.tree.hasOneSelection;
|
|
71
|
+
}
|
|
72
|
+
|
|
65
73
|
get isSelectedStart() {
|
|
66
74
|
return this.isSelected && !this.prev?.isSelected;
|
|
67
75
|
}
|
|
@@ -84,13 +92,16 @@ export class NodeApi<T extends IdObj = IdObj> {
|
|
|
84
92
|
|
|
85
93
|
get state() {
|
|
86
94
|
return {
|
|
87
|
-
|
|
95
|
+
isClosed: this.isClosed,
|
|
88
96
|
isDragging: this.isDragging,
|
|
89
|
-
|
|
90
|
-
isSelectedStart: this.isSelectedStart,
|
|
91
|
-
isSelectedEnd: this.isSelectedEnd,
|
|
97
|
+
isEditing: this.isEditing,
|
|
92
98
|
isFocused: this.isFocused,
|
|
99
|
+
isInternal: this.isInternal,
|
|
100
|
+
isLeaf: this.isLeaf,
|
|
93
101
|
isOpen: this.isOpen,
|
|
102
|
+
isSelected: this.isSelected,
|
|
103
|
+
isSelectedEnd: this.isSelectedEnd,
|
|
104
|
+
isSelectedStart: this.isSelectedStart,
|
|
94
105
|
willReceiveDrop: this.willReceiveDrop,
|
|
95
106
|
};
|
|
96
107
|
}
|
|
@@ -68,19 +68,19 @@ export class TreeApi<T extends IdObj> {
|
|
|
68
68
|
/* Tree Props */
|
|
69
69
|
|
|
70
70
|
get width() {
|
|
71
|
-
return this.props.width
|
|
71
|
+
return this.props.width ?? 300;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
get height() {
|
|
75
|
-
return this.props.height
|
|
75
|
+
return this.props.height ?? 500;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
get indent() {
|
|
79
|
-
return this.props.indent
|
|
79
|
+
return this.props.indent ?? 24;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
get rowHeight() {
|
|
83
|
-
return this.props.rowHeight
|
|
83
|
+
return this.props.rowHeight ?? 24;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
get searchTerm() {
|
|
@@ -176,33 +176,27 @@ export class TreeApi<T extends IdObj> {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
createInternal() {
|
|
179
|
-
return this.create("internal");
|
|
179
|
+
return this.create({ type: "internal" });
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
createLeaf() {
|
|
183
|
-
return this.create("leaf");
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
index = 0;
|
|
194
|
-
} else {
|
|
195
|
-
index = focus.childIndex + 1;
|
|
196
|
-
parentId = focus.parent.isRoot ? null : focus.parent.id;
|
|
197
|
-
}
|
|
198
|
-
} else {
|
|
199
|
-
index = this.root?.children?.length || -1;
|
|
200
|
-
parentId = null;
|
|
201
|
-
}
|
|
183
|
+
return this.create({ type: "leaf" });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async create(
|
|
187
|
+
opts: {
|
|
188
|
+
type?: "internal" | "leaf";
|
|
189
|
+
parentId?: null | string;
|
|
190
|
+
index?: null | number;
|
|
191
|
+
} = {}
|
|
192
|
+
) {
|
|
202
193
|
const data = await safeRun(this.props.onCreate, {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
194
|
+
type: opts.type ?? "leaf",
|
|
195
|
+
parentId:
|
|
196
|
+
opts.parentId === undefined
|
|
197
|
+
? utils.getInsertParentId(this)
|
|
198
|
+
: opts.parentId,
|
|
199
|
+
index: opts.index ?? utils.getInsertIndex(this),
|
|
206
200
|
});
|
|
207
201
|
if (data) {
|
|
208
202
|
this.focus(data);
|
|
@@ -284,6 +278,7 @@ export class TreeApi<T extends IdObj> {
|
|
|
284
278
|
} else {
|
|
285
279
|
this.dispatch(focus(identify(node)));
|
|
286
280
|
if (opts.scroll !== false) this.scrollTo(node);
|
|
281
|
+
if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode);
|
|
287
282
|
}
|
|
288
283
|
}
|
|
289
284
|
|
|
@@ -321,6 +316,7 @@ export class TreeApi<T extends IdObj> {
|
|
|
321
316
|
this.dispatch(selection.anchor(id));
|
|
322
317
|
this.dispatch(selection.mostRecent(id));
|
|
323
318
|
this.scrollTo(id, opts.align);
|
|
319
|
+
if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode);
|
|
324
320
|
safeRun(this.props.onSelect, this.selectedNodes);
|
|
325
321
|
}
|
|
326
322
|
|
|
@@ -338,6 +334,7 @@ export class TreeApi<T extends IdObj> {
|
|
|
338
334
|
this.dispatch(selection.anchor(node.id));
|
|
339
335
|
this.dispatch(selection.mostRecent(node.id));
|
|
340
336
|
this.scrollTo(node);
|
|
337
|
+
if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode);
|
|
341
338
|
safeRun(this.props.onSelect, this.selectedNodes);
|
|
342
339
|
}
|
|
343
340
|
|
|
@@ -350,6 +347,7 @@ export class TreeApi<T extends IdObj> {
|
|
|
350
347
|
this.dispatch(selection.add(this.nodesBetween(anchor, identifyNull(id))));
|
|
351
348
|
this.dispatch(selection.mostRecent(id));
|
|
352
349
|
this.scrollTo(id);
|
|
350
|
+
if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode);
|
|
353
351
|
safeRun(this.props.onSelect, this.selectedNodes);
|
|
354
352
|
}
|
|
355
353
|
|
|
@@ -365,6 +363,7 @@ export class TreeApi<T extends IdObj> {
|
|
|
365
363
|
this.dispatch(focus(this.lastNode?.id));
|
|
366
364
|
this.dispatch(selection.anchor(this.firstNode));
|
|
367
365
|
this.dispatch(selection.mostRecent(this.lastNode));
|
|
366
|
+
if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode);
|
|
368
367
|
safeRun(this.props.onSelect, this.selectedNodes);
|
|
369
368
|
}
|
|
370
369
|
|
|
@@ -397,13 +396,17 @@ export class TreeApi<T extends IdObj> {
|
|
|
397
396
|
open(identity: Identity) {
|
|
398
397
|
const id = identifyNull(identity);
|
|
399
398
|
if (!id) return;
|
|
399
|
+
if (this.isOpen(id)) return;
|
|
400
400
|
this.dispatch(visibility.open(id, this.isFiltered));
|
|
401
|
+
safeRun(this.props.onToggle, id);
|
|
401
402
|
}
|
|
402
403
|
|
|
403
404
|
close(identity: Identity) {
|
|
404
405
|
const id = identifyNull(identity);
|
|
405
406
|
if (!id) return;
|
|
407
|
+
if (!this.isOpen(id)) return;
|
|
406
408
|
this.dispatch(visibility.close(id, this.isFiltered));
|
|
409
|
+
safeRun(this.props.onToggle, id);
|
|
407
410
|
}
|
|
408
411
|
|
|
409
412
|
toggle(identity: Identity) {
|
|
@@ -439,6 +442,18 @@ export class TreeApi<T extends IdObj> {
|
|
|
439
442
|
}
|
|
440
443
|
}
|
|
441
444
|
|
|
445
|
+
openAll() {
|
|
446
|
+
utils.walk(this.root, (node) => {
|
|
447
|
+
if (node.isInternal) node.open();
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
closeAll() {
|
|
452
|
+
utils.walk(this.root, (node) => {
|
|
453
|
+
if (node.isInternal) node.close();
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
442
457
|
/* Scrolling */
|
|
443
458
|
|
|
444
459
|
scrollTo(identity: Identity, align: Align = "smart") {
|
|
@@ -471,6 +486,18 @@ export class TreeApi<T extends IdObj> {
|
|
|
471
486
|
return this.state.nodes.focus.treeFocused;
|
|
472
487
|
}
|
|
473
488
|
|
|
489
|
+
get hasNoSelection() {
|
|
490
|
+
return this.state.nodes.selection.ids.size === 0;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
get hasOneSelection() {
|
|
494
|
+
return this.state.nodes.selection.ids.size === 1;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
get hasMultipleSelections() {
|
|
498
|
+
return this.state.nodes.selection.ids.size > 1;
|
|
499
|
+
}
|
|
500
|
+
|
|
474
501
|
isSelected(id?: string) {
|
|
475
502
|
if (!id) return false;
|
|
476
503
|
return this.state.nodes.selection.ids.has(id);
|
package/src/types/renderers.ts
CHANGED
package/src/types/tree-props.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface TreeProps<T extends IdObj> {
|
|
|
21
21
|
children?: ElementType<renderers.NodeRendererProps<T>>;
|
|
22
22
|
renderRow?: ElementType<renderers.RowRendererProps<T>>;
|
|
23
23
|
renderDragPreview?: ElementType<renderers.DragPreviewProps>;
|
|
24
|
-
renderCursor?: ElementType<renderers.
|
|
24
|
+
renderCursor?: ElementType<renderers.CursorProps>;
|
|
25
25
|
renderContainer?: ElementType<{}>;
|
|
26
26
|
|
|
27
27
|
/* Sizes */
|
|
@@ -45,6 +45,8 @@ export interface TreeProps<T extends IdObj> {
|
|
|
45
45
|
onActivate?: (node: NodeApi<T>) => void;
|
|
46
46
|
onSelect?: (nodes: NodeApi<T>[]) => void;
|
|
47
47
|
onScroll?: (props: ListOnScrollProps) => void;
|
|
48
|
+
onToggle?: (id: string) => void;
|
|
49
|
+
onFocus?: (node: NodeApi<T>) => void;
|
|
48
50
|
|
|
49
51
|
/* Selection */
|
|
50
52
|
selection?: string;
|
|
@@ -58,6 +60,8 @@ export interface TreeProps<T extends IdObj> {
|
|
|
58
60
|
|
|
59
61
|
/* Extra */
|
|
60
62
|
className?: string | undefined;
|
|
63
|
+
rowClassName?: string | undefined;
|
|
64
|
+
|
|
61
65
|
dndRootElement?: globalThis.Node | null;
|
|
62
66
|
onClick?: MouseEventHandler;
|
|
63
67
|
onContextMenu?: MouseEventHandler;
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NodeApi } from "./interfaces/node-api";
|
|
2
|
+
import { TreeApi } from "./interfaces/tree-api";
|
|
2
3
|
import { IdObj } from "./types/utils";
|
|
3
4
|
|
|
4
5
|
export function bound(n: number, min: number, max: number) {
|
|
@@ -44,6 +45,18 @@ export function dfs(node: NodeApi<any>, id: string): NodeApi<any> | null {
|
|
|
44
45
|
return null;
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
export function walk(
|
|
49
|
+
node: NodeApi<any>,
|
|
50
|
+
fn: (node: NodeApi<any>) => void
|
|
51
|
+
): void {
|
|
52
|
+
fn(node);
|
|
53
|
+
if (node.children) {
|
|
54
|
+
for (let child of node.children) {
|
|
55
|
+
walk(child, fn);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
47
60
|
export function focusNextElement(target: HTMLElement) {
|
|
48
61
|
const elements = getFocusable(target);
|
|
49
62
|
|
|
@@ -147,3 +160,19 @@ export function waitFor(fn: () => boolean) {
|
|
|
147
160
|
check();
|
|
148
161
|
});
|
|
149
162
|
}
|
|
163
|
+
|
|
164
|
+
export function getInsertIndex(tree: TreeApi<any>) {
|
|
165
|
+
const focus = tree.focusedNode;
|
|
166
|
+
if (!focus) return tree.root.children?.length ?? 0;
|
|
167
|
+
if (focus.isOpen) return 0;
|
|
168
|
+
if (focus.parent) return focus.childIndex + 1;
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getInsertParentId(tree: TreeApi<any>) {
|
|
173
|
+
const focus = tree.focusedNode;
|
|
174
|
+
if (!focus) return null;
|
|
175
|
+
if (focus.isOpen) return focus.id;
|
|
176
|
+
if (focus.parent) return focus.parent.id;
|
|
177
|
+
return null;
|
|
178
|
+
}
|