react-arborist 1.2.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 (120) 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/provider.d.ts +11 -0
  10. package/dist/components/row-container.d.ts +8 -0
  11. package/dist/components/tree-container.d.ts +2 -0
  12. package/dist/components/tree.d.ts +5 -4
  13. package/dist/context.d.ts +20 -2
  14. package/dist/data/create-index.d.ts +5 -0
  15. package/dist/data/create-list.d.ts +4 -0
  16. package/dist/data/create-root.d.ts +5 -0
  17. package/dist/data/flatten-tree.d.ts +4 -2
  18. package/dist/data/simple-tree.d.ts +43 -0
  19. package/dist/dnd/compute-drop.d.ts +4 -4
  20. package/dist/dnd/drag-hook.d.ts +3 -4
  21. package/dist/dnd/drop-hook.d.ts +2 -3
  22. package/dist/hooks/use-fresh-node.d.ts +2 -0
  23. package/dist/hooks/use-simple-tree.d.ts +13 -0
  24. package/dist/hooks/use-uncontrolled-tree.d.ts +24 -0
  25. package/dist/hooks/use-validated-props.d.ts +3 -0
  26. package/dist/index.d.ts +8 -4
  27. package/dist/index.js +1900 -971
  28. package/dist/index.js.map +1 -1
  29. package/dist/interfaces/node-api.d.ts +67 -0
  30. package/dist/interfaces/tree-api.d.ts +112 -0
  31. package/dist/module.js +1886 -976
  32. package/dist/module.js.map +1 -1
  33. package/dist/state/dnd-slice.d.ts +20 -0
  34. package/dist/state/drag-slice.d.ts +7 -0
  35. package/dist/state/edit-slice.d.ts +8 -0
  36. package/dist/state/focus-slice.d.ts +12 -0
  37. package/dist/state/initial.d.ts +3 -0
  38. package/dist/state/open-slice.d.ts +30 -0
  39. package/dist/state/root-reducer.d.ts +13 -0
  40. package/dist/state/selection-slice.d.ts +36 -0
  41. package/dist/types/dnd.d.ts +9 -0
  42. package/dist/types/handlers.d.ts +24 -0
  43. package/dist/types/renderers.d.ts +30 -0
  44. package/dist/types/state.d.ts +2 -0
  45. package/dist/types/tree-props.d.ts +43 -0
  46. package/dist/types/utils.d.ts +21 -0
  47. package/dist/utils/props.d.ts +3 -0
  48. package/dist/utils.d.ts +15 -6
  49. package/package.json +10 -7
  50. package/src/components/cursor.tsx +15 -0
  51. package/src/components/default-container.tsx +229 -0
  52. package/src/components/{default-drop-cursor.tsx → default-cursor.tsx} +9 -8
  53. package/src/components/{preview.tsx → default-drag-preview.tsx} +25 -41
  54. package/src/components/default-node.tsx +15 -0
  55. package/src/components/default-row.tsx +21 -0
  56. package/src/components/drag-preview-container.tsx +26 -0
  57. package/src/components/list-inner-element.tsx +22 -0
  58. package/src/components/list-outer-element.tsx +26 -15
  59. package/src/components/provider.tsx +97 -0
  60. package/src/components/row-container.tsx +82 -0
  61. package/src/components/tree-container.tsx +13 -0
  62. package/src/components/tree.tsx +16 -44
  63. package/src/context.ts +36 -0
  64. package/src/data/create-index.ts +9 -0
  65. package/src/data/create-list.ts +56 -0
  66. package/src/data/create-root.ts +53 -0
  67. package/src/data/simple-tree.ts +103 -0
  68. package/src/dnd/compute-drop.ts +16 -16
  69. package/src/dnd/drag-hook.ts +25 -19
  70. package/src/dnd/drop-hook.ts +31 -17
  71. package/src/dnd/outer-drop-hook.ts +1 -1
  72. package/src/hooks/use-fresh-node.ts +16 -0
  73. package/src/hooks/use-simple-tree.ts +55 -0
  74. package/src/hooks/use-validated-props.ts +35 -0
  75. package/src/index.ts +9 -19
  76. package/src/interfaces/node-api.ts +187 -0
  77. package/src/interfaces/tree-api.ts +552 -0
  78. package/src/state/dnd-slice.ts +36 -0
  79. package/src/state/drag-slice.ts +31 -0
  80. package/src/state/edit-slice.ts +19 -0
  81. package/src/state/focus-slice.ts +28 -0
  82. package/src/state/initial.ts +14 -0
  83. package/src/state/open-slice.ts +53 -0
  84. package/src/state/root-reducer.ts +21 -0
  85. package/src/state/selection-slice.ts +75 -0
  86. package/src/types/dnd.ts +10 -0
  87. package/src/types/handlers.ts +24 -0
  88. package/src/types/renderers.ts +34 -0
  89. package/src/types/state.ts +3 -0
  90. package/src/types/tree-props.ts +63 -0
  91. package/src/types/utils.ts +26 -0
  92. package/src/utils/props.ts +8 -0
  93. package/src/utils.ts +125 -11
  94. package/README.md +0 -221
  95. package/dist/components/default-drop-cursor.d.ts +0 -3
  96. package/dist/components/list.d.ts +0 -4
  97. package/dist/components/preview.d.ts +0 -2
  98. package/dist/components/row.d.ts +0 -8
  99. package/dist/data/enrich-tree.d.ts +0 -2
  100. package/dist/provider.d.ts +0 -3
  101. package/dist/reducer.d.ts +0 -46
  102. package/dist/selection/range.d.ts +0 -13
  103. package/dist/selection/selection-hook.d.ts +0 -4
  104. package/dist/selection/selection.d.ts +0 -33
  105. package/dist/tree-api.d.ts +0 -50
  106. package/dist/types.d.ts +0 -122
  107. package/src/components/drop-cursor.tsx +0 -12
  108. package/src/components/list.tsx +0 -25
  109. package/src/components/row.tsx +0 -112
  110. package/src/context.tsx +0 -13
  111. package/src/data/enrich-tree.ts +0 -74
  112. package/src/data/flatten-tree.ts +0 -17
  113. package/src/provider.tsx +0 -41
  114. package/src/reducer.ts +0 -161
  115. package/src/selection/range.ts +0 -41
  116. package/src/selection/selection-hook.ts +0 -25
  117. package/src/selection/selection.test.ts +0 -111
  118. package/src/selection/selection.ts +0 -186
  119. package/src/tree-api.ts +0 -230
  120. package/src/types.ts +0 -148
@@ -0,0 +1,53 @@
1
+ import { ActionTypes } from "../types/utils";
2
+
3
+ /* Types */
4
+ export type OpenMap = { [id: string]: boolean };
5
+ export type OpenSlice = { unfiltered: OpenMap; filtered: OpenMap };
6
+
7
+ /* Actions */
8
+ export const actions = {
9
+ open(id: string, filtered: boolean) {
10
+ return { type: "VISIBILITY_OPEN" as const, id, filtered };
11
+ },
12
+ close(id: string, filtered: boolean) {
13
+ return { type: "VISIBILITY_CLOSE" as const, id, filtered };
14
+ },
15
+ toggle(id: string, filtered: boolean) {
16
+ return { type: "VISIBILITY_TOGGLE" as const, id, filtered };
17
+ },
18
+ clear(filtered: boolean) {
19
+ return { type: "VISIBILITY_CLEAR" as const, filtered };
20
+ },
21
+ };
22
+
23
+ /* Reducer */
24
+
25
+ function openMapReducer(
26
+ state: OpenMap = {},
27
+ action: ActionTypes<typeof actions>
28
+ ) {
29
+ if (action.type === "VISIBILITY_OPEN") {
30
+ return { ...state, [action.id]: true };
31
+ } else if (action.type === "VISIBILITY_CLOSE") {
32
+ return { ...state, [action.id]: false };
33
+ } else if (action.type === "VISIBILITY_TOGGLE") {
34
+ const prev = state[action.id];
35
+ return { ...state, [action.id]: !prev };
36
+ } else if (action.type === "VISIBILITY_CLEAR") {
37
+ return {};
38
+ } else {
39
+ return state;
40
+ }
41
+ }
42
+
43
+ export function reducer(
44
+ state: OpenSlice = { filtered: {}, unfiltered: {} },
45
+ action: ActionTypes<typeof actions>
46
+ ): OpenSlice {
47
+ if (!action.type.startsWith("VISIBILITY")) return state;
48
+ if (action.filtered) {
49
+ return { ...state, filtered: openMapReducer(state.filtered, action) };
50
+ } else {
51
+ return { ...state, unfiltered: openMapReducer(state.unfiltered, action) };
52
+ }
53
+ }
@@ -0,0 +1,21 @@
1
+ import { ActionFromReducer, combineReducers } from "redux";
2
+ import { reducer as focus } from "./focus-slice";
3
+ import { reducer as edit } from "./edit-slice";
4
+ import { reducer as dnd } from "./dnd-slice";
5
+ import { reducer as selection } from "./selection-slice";
6
+ import { reducer as open } from "./open-slice";
7
+ import { reducer as drag } from "./drag-slice";
8
+
9
+ export const rootReducer = combineReducers({
10
+ nodes: combineReducers({
11
+ focus,
12
+ edit,
13
+ open,
14
+ selection,
15
+ drag,
16
+ }),
17
+ dnd,
18
+ });
19
+
20
+ export type RootState = ReturnType<typeof rootReducer>;
21
+ export type Actions = ActionFromReducer<typeof rootReducer>;
@@ -0,0 +1,75 @@
1
+ import { ActionTypes, IdObj } from "../types/utils";
2
+ import { identify } from "../utils";
3
+ import { initialState } from "./initial";
4
+
5
+ /* Types */
6
+ export type SelectionState = {
7
+ ids: Set<string>;
8
+ anchor: string | null;
9
+ mostRecent: string | null;
10
+ };
11
+
12
+ /* Actions */
13
+ export const actions = {
14
+ clear: () => ({ type: "SELECTION_CLEAR" as const }),
15
+
16
+ only: (id: string | IdObj) => ({
17
+ type: "SELECTION_ONLY" as const,
18
+ id: identify(id),
19
+ }),
20
+
21
+ add: (id: string | string[] | IdObj | IdObj[]) => ({
22
+ type: "SELECTION_ADD" as const,
23
+ ids: (Array.isArray(id) ? id : [id]).map(identify),
24
+ }),
25
+
26
+ remove: (id: string | string[] | IdObj | IdObj[]) => ({
27
+ type: "SELECTION_REMOVE" as const,
28
+ ids: (Array.isArray(id) ? id : [id]).map(identify),
29
+ }),
30
+
31
+ set: (ids: Set<string>) => ({
32
+ type: "SELECTION_SET" as const,
33
+ ids,
34
+ }),
35
+
36
+ mostRecent: (id: string | null | IdObj) => ({
37
+ type: "SELECTION_MOST_RECENT" as const,
38
+ id: id === null ? null : identify(id),
39
+ }),
40
+
41
+ anchor: (id: string | null | IdObj) => ({
42
+ type: "SELECTION_ANCHOR" as const,
43
+ id: id === null ? null : identify(id),
44
+ }),
45
+ };
46
+
47
+ /* Reducer */
48
+ export function reducer(
49
+ state: SelectionState = initialState()["nodes"]["selection"],
50
+ action: ActionTypes<typeof actions>
51
+ ): SelectionState {
52
+ const ids = state.ids;
53
+ switch (action.type) {
54
+ case "SELECTION_CLEAR":
55
+ return { ...state, ids: new Set() };
56
+ case "SELECTION_ONLY":
57
+ return { ...state, ids: new Set([action.id]) };
58
+ case "SELECTION_ADD":
59
+ if (action.ids.length === 0) return state;
60
+ action.ids.forEach((id) => ids.add(id));
61
+ return { ...state, ids: new Set(ids) };
62
+ case "SELECTION_REMOVE":
63
+ if (action.ids.length === 0) return state;
64
+ action.ids.forEach((id) => ids.delete(id));
65
+ return { ...state, ids: new Set(ids) };
66
+ case "SELECTION_SET":
67
+ return { ...state, ids: new Set(action.ids) };
68
+ case "SELECTION_MOST_RECENT":
69
+ return { ...state, mostRecent: action.id };
70
+ case "SELECTION_ANCHOR":
71
+ return { ...state, anchor: action.id };
72
+ default:
73
+ return state;
74
+ }
75
+ }
@@ -0,0 +1,10 @@
1
+ export type CursorLocation = {
2
+ index: number | null;
3
+ level: number | null;
4
+ parentId: string | null;
5
+ };
6
+
7
+ export type DragItem = {
8
+ dragIds: string[];
9
+ id: string;
10
+ };
@@ -0,0 +1,24 @@
1
+ import { IdObj } from "./utils";
2
+
3
+ export type CreateHandler = (args: {
4
+ parentId: string | null;
5
+ index: number;
6
+ type: "internal" | "leaf";
7
+ }) => (IdObj | null) | Promise<IdObj | null>;
8
+
9
+ export type MoveHandler = (args: {
10
+ dragIds: string[];
11
+ parentId: string | null;
12
+ index: number;
13
+ }) => void | Promise<void>;
14
+
15
+ export type RenameHandler = (args: {
16
+ id: string;
17
+ name: string;
18
+ }) => void | Promise<void>;
19
+
20
+ export type DeleteHandler = (args: { ids: string[] }) => void | Promise<void>;
21
+
22
+ export type EditResult =
23
+ | { cancelled: true }
24
+ | { cancelled: false; value: string };
@@ -0,0 +1,34 @@
1
+ import { CSSProperties, HTMLAttributes, ReactElement } from "react";
2
+ import { IdObj } from "./utils";
3
+ import { NodeApi } from "../interfaces/node-api";
4
+ import { TreeApi } from "../interfaces/tree-api";
5
+ import { XYCoord } from "react-dnd";
6
+
7
+ export type NodeRendererProps<T extends IdObj> = {
8
+ style: CSSProperties;
9
+ node: NodeApi<T>;
10
+ tree: TreeApi<T>;
11
+ dragHandle?: (el: HTMLDivElement | null) => void;
12
+ preview?: boolean;
13
+ };
14
+
15
+ export type RowRendererProps<T extends IdObj> = {
16
+ node: NodeApi<T>;
17
+ innerRef: (el: HTMLDivElement | null) => void;
18
+ attrs: HTMLAttributes<any>;
19
+ children: ReactElement;
20
+ };
21
+
22
+ export type DragPreviewProps = {
23
+ offset: XYCoord | null;
24
+ mouse: XYCoord | null;
25
+ id: string | null;
26
+ dragIds: string[];
27
+ isDragging: boolean;
28
+ };
29
+
30
+ export type DropCursorProps = {
31
+ top: number;
32
+ left: number;
33
+ indent: number;
34
+ };
@@ -0,0 +1,3 @@
1
+ import { NodeApi } from "../interfaces/node-api";
2
+
3
+ export type NodeState = typeof NodeApi.prototype["state"];
@@ -0,0 +1,63 @@
1
+ import { BoolFunc, IdObj } from "./utils";
2
+ import * as handlers from "./handlers";
3
+ import * as renderers from "./renderers";
4
+ import { ElementType, MouseEventHandler } from "react";
5
+ import { ListOnScrollProps } from "react-window";
6
+ import { NodeApi } from "../interfaces/node-api";
7
+ import { OpenMap, OpenSlice } from "../state/open-slice";
8
+
9
+ export interface TreeProps<T extends IdObj> {
10
+ /* Data Options */
11
+ data?: T[];
12
+ initialData?: T[];
13
+
14
+ /* Data Handlers */
15
+ onCreate?: handlers.CreateHandler;
16
+ onMove?: handlers.MoveHandler;
17
+ onRename?: handlers.RenameHandler;
18
+ onDelete?: handlers.DeleteHandler;
19
+
20
+ /* Renderers*/
21
+ children?: ElementType<renderers.NodeRendererProps<T>>;
22
+ renderRow?: ElementType<renderers.RowRendererProps<T>>;
23
+ renderDragPreview?: ElementType<renderers.DragPreviewProps>;
24
+ renderCursor?: ElementType<renderers.DropCursorProps>;
25
+ renderContainer?: ElementType<{}>;
26
+
27
+ /* Sizes */
28
+ rowHeight?: number;
29
+ width?: number;
30
+ height?: number;
31
+ indent?: number;
32
+ paddingTop?: number;
33
+ paddingBottom?: number;
34
+ padding?: number;
35
+
36
+ /* Config */
37
+ openByDefault?: boolean;
38
+ selectionFollowsFocus?: boolean;
39
+ disableDrag?: string | boolean | BoolFunc<T>;
40
+ disableDrop?: string | boolean | BoolFunc<T>;
41
+ getChildren?: string | ((d: T) => T[]);
42
+
43
+ /* Event Handlers */
44
+ onActivate?: (node: NodeApi<T>) => void;
45
+ onSelect?: (nodes: NodeApi<T>[]) => void;
46
+ onScroll?: (props: ListOnScrollProps) => void;
47
+
48
+ /* Selection */
49
+ selection?: string;
50
+
51
+ /* Open State */
52
+ initialOpenState?: OpenMap;
53
+
54
+ /* Search */
55
+ searchTerm?: string;
56
+ searchMatch?: (node: NodeApi<T>, searchTerm: string) => boolean;
57
+
58
+ /* Extra */
59
+ className?: string | undefined;
60
+ dndRootElement?: globalThis.Node | null;
61
+ onClick?: MouseEventHandler;
62
+ onContextMenu?: MouseEventHandler;
63
+ }
@@ -0,0 +1,26 @@
1
+ import { AnyAction } from "redux";
2
+ import { NodeApi } from "../interfaces/node-api";
3
+
4
+ export interface IdObj {
5
+ id: string;
6
+ }
7
+
8
+ export type Identity = string | IdObj | null;
9
+
10
+ // Forward ref can't forward generics without this little re-declare
11
+ // https://fettblog.eu/typescript-react-generic-forward-refs/
12
+ declare module "react" {
13
+ function forwardRef<T, P = {}>(
14
+ render: (props: P, ref: React.Ref<T>) => React.ReactElement | null
15
+ ): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
16
+ }
17
+
18
+ export type BoolFunc<T> = (data: T) => boolean;
19
+
20
+ export type ActionTypes<
21
+ Actions extends { [name: string]: (...args: any[]) => AnyAction }
22
+ > = ReturnType<Actions[keyof Actions]>;
23
+
24
+ export type SelectOptions = { multi?: boolean; contiguous?: boolean };
25
+
26
+ export type NodesById<T extends IdObj> = { [id: string]: NodeApi<T> };
@@ -0,0 +1,8 @@
1
+ import { TreeProps } from "../types/tree-props";
2
+ import { IdObj } from "../types/utils";
3
+
4
+ export function validateProps<T extends IdObj>(
5
+ props: TreeProps<T>
6
+ ): TreeProps<T> {
7
+ return props;
8
+ }
package/src/utils.ts CHANGED
@@ -1,24 +1,23 @@
1
- import { Node } from "./types";
1
+ import { NodeApi } from "./interfaces/node-api";
2
+ import { IdObj } from "./types/utils";
2
3
 
3
4
  export function bound(n: number, min: number, max: number) {
4
5
  return Math.max(Math.min(n, max), min);
5
6
  }
6
7
 
7
- export const isFolder = (node: Node<any>) => !!node.children;
8
-
9
- export function isItem(node: Node | null) {
10
- return node && !isFolder(node);
8
+ export function isItem(node: NodeApi<any> | null) {
9
+ return node && node.isLeaf;
11
10
  }
12
11
 
13
- export function isClosed(node: Node | null) {
14
- return node && isFolder(node) && !node.isOpen;
12
+ export function isClosed(node: NodeApi<any> | null) {
13
+ return node && node.isInternal && !node.isOpen;
15
14
  }
16
15
 
17
16
  /**
18
17
  * Is first param a decendent of the second param
19
18
  */
20
- export const isDecendent = (a: Node, b: Node) => {
21
- let n: Node | null = a;
19
+ export const isDecendent = (a: NodeApi<any>, b: NodeApi<any>) => {
20
+ let n: NodeApi<any> | null = a;
22
21
  while (n) {
23
22
  if (n.id === b.id) return true;
24
23
  n = n.parent;
@@ -26,10 +25,125 @@ export const isDecendent = (a: Node, b: Node) => {
26
25
  return false;
27
26
  };
28
27
 
29
- export const indexOf = (node: Node) => {
30
- // This should probably not throw an error, but instead return null
28
+ export const indexOf = (node: NodeApi<any>) => {
31
29
  if (!node.parent) throw Error("Node does not have a parent");
32
30
  return node.parent.children!.findIndex((c) => c.id === node.id);
33
31
  };
34
32
 
35
33
  export function noop() {}
34
+
35
+ export function dfs(node: NodeApi<any>, id: string): NodeApi<any> | null {
36
+ if (!node) return null;
37
+ if (node.id === id) return node;
38
+ if (node.children) {
39
+ for (let child of node.children) {
40
+ const result = dfs(child, id);
41
+ if (result) return result;
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+
47
+ export function focusNextElement(target: HTMLElement) {
48
+ const elements = getFocusable(target);
49
+
50
+ let next: HTMLElement;
51
+ for (let i = 0; i < elements.length; ++i) {
52
+ const item = elements[i];
53
+ if (item === target) {
54
+ next = nextItem(elements, i);
55
+ break;
56
+ }
57
+ }
58
+
59
+ // @ts-ignore ??
60
+ next?.focus();
61
+ }
62
+
63
+ export function focusPrevElement(target: HTMLElement) {
64
+ const elements = getFocusable(target);
65
+ let next: HTMLElement;
66
+ for (let i = 0; i < elements.length; ++i) {
67
+ const item = elements[i];
68
+ if (item === target) {
69
+ next = prevItem(elements, i);
70
+ break;
71
+ }
72
+ }
73
+ // @ts-ignore
74
+ next?.focus();
75
+ }
76
+
77
+ function nextItem(list: HTMLElement[], index: number) {
78
+ if (index + 1 < list.length) {
79
+ return list[index + 1] as HTMLElement;
80
+ } else {
81
+ return list[0] as HTMLElement;
82
+ }
83
+ }
84
+
85
+ function prevItem(list: HTMLElement[], index: number) {
86
+ if (index - 1 >= 0) {
87
+ return list[index - 1];
88
+ } else {
89
+ return list[list.length - 1];
90
+ }
91
+ }
92
+
93
+ function getFocusable(target: HTMLElement) {
94
+ return Array.from(
95
+ document.querySelectorAll(
96
+ '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)'
97
+ )
98
+ ).filter((e) => e === target || !target.contains(e)) as HTMLElement[];
99
+ }
100
+
101
+ export function access<T = boolean>(
102
+ obj: any,
103
+ accessor: string | boolean | Function
104
+ ): T {
105
+ if (typeof accessor === "boolean") return accessor as unknown as T;
106
+ if (typeof accessor === "string") return obj[accessor] as T;
107
+ return accessor(obj) as T;
108
+ }
109
+
110
+ export function identifyNull(obj: string | IdObj | null) {
111
+ if (obj === null) return null;
112
+ else return identify(obj);
113
+ }
114
+
115
+ export function identify(obj: string | IdObj) {
116
+ return typeof obj === "string" ? obj : obj.id;
117
+ }
118
+
119
+ export function mergeRefs(...refs: any) {
120
+ return (instance: any) => {
121
+ refs.forEach((ref: any) => {
122
+ if (typeof ref === "function") {
123
+ ref(instance);
124
+ } else if (ref != null) {
125
+ ref.current = instance;
126
+ }
127
+ });
128
+ };
129
+ }
130
+
131
+ export function safeRun<T extends (...args: any[]) => any>(
132
+ fn: T | undefined,
133
+ ...args: Parameters<T>
134
+ ) {
135
+ if (fn) return fn(...args);
136
+ }
137
+
138
+ export function waitFor(fn: () => boolean) {
139
+ return new Promise<void>((resolve, reject) => {
140
+ let tries = 0;
141
+ function check() {
142
+ tries += 1;
143
+ if (tries === 100) reject();
144
+ if (fn()) resolve();
145
+ else setTimeout(check, 10);
146
+ }
147
+ check();
148
+ });
149
+ }