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.
- package/dist/components/{drop-cursor.d.ts → cursor.d.ts} +0 -0
- package/dist/components/default-container.d.ts +2 -0
- package/dist/components/default-cursor.d.ts +3 -0
- package/dist/components/default-drag-preview.d.ts +3 -0
- package/dist/components/default-node.d.ts +4 -0
- package/dist/components/default-row.d.ts +4 -0
- package/dist/components/drag-preview-container.d.ts +2 -0
- package/dist/components/list-inner-element.d.ts +2 -0
- package/dist/components/provider.d.ts +11 -0
- package/dist/components/row-container.d.ts +8 -0
- package/dist/components/tree-container.d.ts +2 -0
- package/dist/components/tree.d.ts +5 -4
- package/dist/context.d.ts +20 -2
- package/dist/data/create-index.d.ts +5 -0
- package/dist/data/create-list.d.ts +4 -0
- package/dist/data/create-root.d.ts +5 -0
- package/dist/data/flatten-tree.d.ts +4 -2
- package/dist/data/simple-tree.d.ts +43 -0
- package/dist/dnd/compute-drop.d.ts +4 -4
- package/dist/dnd/drag-hook.d.ts +3 -4
- package/dist/dnd/drop-hook.d.ts +2 -3
- package/dist/hooks/use-fresh-node.d.ts +2 -0
- package/dist/hooks/use-simple-tree.d.ts +13 -0
- package/dist/hooks/use-uncontrolled-tree.d.ts +24 -0
- package/dist/hooks/use-validated-props.d.ts +3 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.js +1900 -971
- package/dist/index.js.map +1 -1
- package/dist/interfaces/node-api.d.ts +67 -0
- package/dist/interfaces/tree-api.d.ts +112 -0
- package/dist/module.js +1886 -976
- package/dist/module.js.map +1 -1
- package/dist/state/dnd-slice.d.ts +20 -0
- package/dist/state/drag-slice.d.ts +7 -0
- package/dist/state/edit-slice.d.ts +8 -0
- package/dist/state/focus-slice.d.ts +12 -0
- package/dist/state/initial.d.ts +3 -0
- package/dist/state/open-slice.d.ts +30 -0
- package/dist/state/root-reducer.d.ts +13 -0
- package/dist/state/selection-slice.d.ts +36 -0
- package/dist/types/dnd.d.ts +9 -0
- package/dist/types/handlers.d.ts +24 -0
- package/dist/types/renderers.d.ts +30 -0
- package/dist/types/state.d.ts +2 -0
- package/dist/types/tree-props.d.ts +43 -0
- package/dist/types/utils.d.ts +21 -0
- package/dist/utils/props.d.ts +3 -0
- package/dist/utils.d.ts +15 -6
- package/package.json +10 -7
- package/src/components/cursor.tsx +15 -0
- package/src/components/default-container.tsx +229 -0
- package/src/components/{default-drop-cursor.tsx → default-cursor.tsx} +9 -8
- package/src/components/{preview.tsx → default-drag-preview.tsx} +25 -41
- package/src/components/default-node.tsx +15 -0
- package/src/components/default-row.tsx +21 -0
- package/src/components/drag-preview-container.tsx +26 -0
- package/src/components/list-inner-element.tsx +22 -0
- package/src/components/list-outer-element.tsx +26 -15
- package/src/components/provider.tsx +97 -0
- package/src/components/row-container.tsx +82 -0
- package/src/components/tree-container.tsx +13 -0
- package/src/components/tree.tsx +16 -44
- package/src/context.ts +36 -0
- package/src/data/create-index.ts +9 -0
- package/src/data/create-list.ts +56 -0
- package/src/data/create-root.ts +53 -0
- package/src/data/simple-tree.ts +103 -0
- package/src/dnd/compute-drop.ts +16 -16
- package/src/dnd/drag-hook.ts +25 -19
- package/src/dnd/drop-hook.ts +31 -17
- package/src/dnd/outer-drop-hook.ts +1 -1
- package/src/hooks/use-fresh-node.ts +16 -0
- package/src/hooks/use-simple-tree.ts +55 -0
- package/src/hooks/use-validated-props.ts +35 -0
- package/src/index.ts +9 -19
- package/src/interfaces/node-api.ts +187 -0
- package/src/interfaces/tree-api.ts +552 -0
- package/src/state/dnd-slice.ts +36 -0
- package/src/state/drag-slice.ts +31 -0
- package/src/state/edit-slice.ts +19 -0
- package/src/state/focus-slice.ts +28 -0
- package/src/state/initial.ts +14 -0
- package/src/state/open-slice.ts +53 -0
- package/src/state/root-reducer.ts +21 -0
- package/src/state/selection-slice.ts +75 -0
- package/src/types/dnd.ts +10 -0
- package/src/types/handlers.ts +24 -0
- package/src/types/renderers.ts +34 -0
- package/src/types/state.ts +3 -0
- package/src/types/tree-props.ts +63 -0
- package/src/types/utils.ts +26 -0
- package/src/utils/props.ts +8 -0
- package/src/utils.ts +125 -11
- package/README.md +0 -221
- package/dist/components/default-drop-cursor.d.ts +0 -3
- package/dist/components/list.d.ts +0 -4
- package/dist/components/preview.d.ts +0 -2
- package/dist/components/row.d.ts +0 -8
- package/dist/data/enrich-tree.d.ts +0 -2
- package/dist/provider.d.ts +0 -3
- package/dist/reducer.d.ts +0 -46
- package/dist/selection/range.d.ts +0 -13
- package/dist/selection/selection-hook.d.ts +0 -4
- package/dist/selection/selection.d.ts +0 -33
- package/dist/tree-api.d.ts +0 -50
- package/dist/types.d.ts +0 -122
- package/src/components/drop-cursor.tsx +0 -12
- package/src/components/list.tsx +0 -25
- package/src/components/row.tsx +0 -112
- package/src/context.tsx +0 -13
- package/src/data/enrich-tree.ts +0 -74
- package/src/data/flatten-tree.ts +0 -17
- package/src/provider.tsx +0 -41
- package/src/reducer.ts +0 -161
- package/src/selection/range.ts +0 -41
- package/src/selection/selection-hook.ts +0 -25
- package/src/selection/selection.test.ts +0 -111
- package/src/selection/selection.ts +0 -186
- package/src/tree-api.ts +0 -230
- 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
|
+
}
|
package/src/types/dnd.ts
ADDED
|
@@ -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,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> };
|
package/src/utils.ts
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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:
|
|
14
|
-
return node &&
|
|
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:
|
|
21
|
-
let n:
|
|
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:
|
|
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
|
+
}
|