@headless-tree/core 0.0.4 → 0.0.6
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/CHANGELOG.md +12 -0
- package/lib/cjs/core/create-tree.js +11 -10
- package/lib/cjs/features/async-data-loader/feature.js +30 -21
- package/lib/cjs/features/drag-and-drop/feature.js +34 -22
- package/lib/cjs/features/drag-and-drop/types.d.ts +14 -1
- package/lib/cjs/features/drag-and-drop/utils.d.ts +1 -1
- package/lib/cjs/features/drag-and-drop/utils.js +34 -17
- package/lib/cjs/features/expand-all/feature.js +12 -9
- package/lib/cjs/features/expand-all/types.d.ts +2 -2
- package/lib/cjs/features/main/types.d.ts +4 -1
- package/lib/cjs/features/renaming/feature.js +10 -9
- package/lib/cjs/features/search/feature.js +21 -7
- package/lib/cjs/features/search/types.d.ts +1 -1
- package/lib/cjs/features/selection/feature.js +6 -4
- package/lib/cjs/features/sync-data-loader/feature.js +6 -0
- package/lib/cjs/features/sync-data-loader/types.d.ts +1 -1
- package/lib/cjs/features/tree/feature.js +41 -24
- package/lib/cjs/features/tree/types.d.ts +7 -2
- package/lib/cjs/index.d.ts +3 -1
- package/lib/cjs/index.js +3 -1
- package/lib/cjs/types/core.d.ts +1 -3
- package/lib/cjs/utilities/create-on-drop-handler.d.ts +3 -0
- package/lib/cjs/utilities/create-on-drop-handler.js +11 -0
- package/lib/cjs/utilities/insert-items-at-target.d.ts +3 -0
- package/lib/cjs/utilities/insert-items-at-target.js +24 -0
- package/lib/cjs/utilities/remove-items-from-parents.d.ts +2 -0
- package/lib/cjs/utilities/remove-items-from-parents.js +17 -0
- package/lib/cjs/utils.d.ts +1 -4
- package/lib/cjs/utils.js +1 -53
- package/lib/esm/core/create-tree.js +11 -10
- package/lib/esm/features/async-data-loader/feature.js +30 -21
- package/lib/esm/features/drag-and-drop/feature.js +34 -22
- package/lib/esm/features/drag-and-drop/types.d.ts +14 -1
- package/lib/esm/features/drag-and-drop/utils.d.ts +1 -1
- package/lib/esm/features/drag-and-drop/utils.js +35 -18
- package/lib/esm/features/expand-all/feature.js +12 -9
- package/lib/esm/features/expand-all/types.d.ts +2 -2
- package/lib/esm/features/main/types.d.ts +4 -1
- package/lib/esm/features/renaming/feature.js +10 -9
- package/lib/esm/features/search/feature.js +21 -7
- package/lib/esm/features/search/types.d.ts +1 -1
- package/lib/esm/features/selection/feature.js +6 -4
- package/lib/esm/features/sync-data-loader/feature.js +6 -0
- package/lib/esm/features/sync-data-loader/types.d.ts +1 -1
- package/lib/esm/features/tree/feature.js +41 -24
- package/lib/esm/features/tree/types.d.ts +7 -2
- package/lib/esm/index.d.ts +3 -1
- package/lib/esm/index.js +3 -1
- package/lib/esm/types/core.d.ts +1 -3
- package/lib/esm/utilities/create-on-drop-handler.d.ts +3 -0
- package/lib/esm/utilities/create-on-drop-handler.js +7 -0
- package/lib/esm/utilities/insert-items-at-target.d.ts +3 -0
- package/lib/esm/utilities/insert-items-at-target.js +20 -0
- package/lib/esm/utilities/remove-items-from-parents.d.ts +2 -0
- package/lib/esm/utilities/remove-items-from-parents.js +13 -0
- package/lib/esm/utils.d.ts +1 -4
- package/lib/esm/utils.js +0 -50
- package/package.json +3 -3
- package/src/core/create-tree.ts +12 -8
- package/src/features/async-data-loader/feature.ts +15 -5
- package/src/features/drag-and-drop/feature.ts +42 -20
- package/src/features/drag-and-drop/types.ts +23 -6
- package/src/features/drag-and-drop/utils.ts +53 -24
- package/src/features/expand-all/feature.ts +10 -8
- package/src/features/expand-all/types.ts +2 -2
- package/src/features/main/types.ts +7 -0
- package/src/features/renaming/feature.ts +10 -5
- package/src/features/search/feature.ts +22 -5
- package/src/features/search/types.ts +1 -0
- package/src/features/selection/feature.ts +8 -3
- package/src/features/sync-data-loader/feature.ts +17 -2
- package/src/features/sync-data-loader/types.ts +1 -1
- package/src/features/tree/feature.ts +43 -23
- package/src/features/tree/types.ts +10 -2
- package/src/index.ts +4 -1
- package/src/types/core.ts +4 -4
- package/src/utilities/create-on-drop-handler.ts +14 -0
- package/src/utilities/insert-items-at-target.ts +30 -0
- package/src/utilities/remove-items-from-parents.ts +21 -0
- package/src/utils.ts +1 -69
- package/tsconfig.json +1 -1
- package/lib/cjs/data-adapters/nested-data-adapter.d.ts +0 -9
- package/lib/cjs/data-adapters/nested-data-adapter.js +0 -32
- package/lib/cjs/data-adapters/types.d.ts +0 -7
- package/lib/cjs/data-adapters/types.js +0 -2
- package/lib/esm/data-adapters/nested-data-adapter.d.ts +0 -9
- package/lib/esm/data-adapters/nested-data-adapter.js +0 -28
- package/lib/esm/data-adapters/types.d.ts +0 -7
- package/lib/esm/data-adapters/types.js +0 -1
- package/src/data-adapters/nested-data-adapter.ts +0 -48
- package/src/data-adapters/types.ts +0 -9
|
@@ -7,6 +7,10 @@ export type ItemMeta = {
|
|
|
7
7
|
setSize: number;
|
|
8
8
|
posInSet: number;
|
|
9
9
|
};
|
|
10
|
+
export type TreeItemDataRef = {
|
|
11
|
+
memoizedValues: Record<string, any>;
|
|
12
|
+
memoizedDeps: Record<string, any[] | undefined>;
|
|
13
|
+
};
|
|
10
14
|
export type TreeFeatureDef<T> = {
|
|
11
15
|
state: {
|
|
12
16
|
expandedItems: string[];
|
|
@@ -30,8 +34,7 @@ export type TreeFeatureDef<T> = {
|
|
|
30
34
|
getFocusedItem: () => ItemInstance<any>;
|
|
31
35
|
focusNextItem: () => void;
|
|
32
36
|
focusPreviousItem: () => void;
|
|
33
|
-
|
|
34
|
-
updateDomFocus: (scrollIntoView?: boolean) => void;
|
|
37
|
+
updateDomFocus: () => void;
|
|
35
38
|
getContainerProps: () => Record<string, any>;
|
|
36
39
|
};
|
|
37
40
|
itemInstance: {
|
|
@@ -52,6 +55,8 @@ export type TreeFeatureDef<T> = {
|
|
|
52
55
|
getTree: () => TreeInstance<T>;
|
|
53
56
|
getItemAbove: () => ItemInstance<T> | null;
|
|
54
57
|
getItemBelow: () => ItemInstance<T> | null;
|
|
58
|
+
getMemoizedProp: <X>(name: string, create: () => X, deps?: any[]) => X;
|
|
59
|
+
scrollTo: (scrollIntoViewArg?: boolean | ScrollIntoViewOptions) => Promise<void>;
|
|
55
60
|
};
|
|
56
61
|
hotkeys: "focusNextItem" | "focusPreviousItem" | "expandOrDown" | "collapseOrUp" | "focusFirstItem" | "focusLastItem";
|
|
57
62
|
};
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -18,4 +18,6 @@ export * from "./features/drag-and-drop/feature";
|
|
|
18
18
|
export * from "./features/search/feature";
|
|
19
19
|
export * from "./features/renaming/feature";
|
|
20
20
|
export * from "./features/expand-all/feature";
|
|
21
|
-
export * from "./
|
|
21
|
+
export * from "./utilities/create-on-drop-handler";
|
|
22
|
+
export * from "./utilities/insert-items-at-target";
|
|
23
|
+
export * from "./utilities/remove-items-from-parents";
|
package/lib/esm/index.js
CHANGED
|
@@ -18,4 +18,6 @@ export * from "./features/drag-and-drop/feature";
|
|
|
18
18
|
export * from "./features/search/feature";
|
|
19
19
|
export * from "./features/renaming/feature";
|
|
20
20
|
export * from "./features/expand-all/feature";
|
|
21
|
-
export * from "./
|
|
21
|
+
export * from "./utilities/create-on-drop-handler";
|
|
22
|
+
export * from "./utilities/insert-items-at-target";
|
|
23
|
+
export * from "./utilities/remove-items-from-parents";
|
package/lib/esm/types/core.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ export type CustomHotkeysConfig<T, F extends FeatureDef = FeatureDefs<T>> = Part
|
|
|
52
52
|
export type FeatureImplementation<T = any, D extends FeatureDef = any, F extends FeatureDef = EmptyFeatureDef> = {
|
|
53
53
|
key?: string;
|
|
54
54
|
dependingFeatures?: string[];
|
|
55
|
+
stateHandlerNames?: Partial<Record<keyof MergedFeatures<F>["state"], keyof MergedFeatures<F>["config"]>>;
|
|
55
56
|
getInitialState?: (initialState: Partial<MergedFeatures<F>["state"]>, tree: MergedFeatures<F>["treeInstance"]) => Partial<D["state"] & MergedFeatures<F>["state"]>;
|
|
56
57
|
getDefaultConfig?: (defaultConfig: Partial<MergedFeatures<F>["config"]>, tree: MergedFeatures<F>["treeInstance"]) => Partial<D["config"] & MergedFeatures<F>["config"]>;
|
|
57
58
|
createTreeInstance?: (prev: MergedFeatures<F>["treeInstance"], instance: MergedFeatures<F>["treeInstance"]) => D["treeInstance"] & MergedFeatures<F>["treeInstance"];
|
|
@@ -60,9 +61,6 @@ export type FeatureImplementation<T = any, D extends FeatureDef = any, F extends
|
|
|
60
61
|
onTreeUnmount?: (instance: MergedFeatures<F>["treeInstance"], treeElement: HTMLElement) => void;
|
|
61
62
|
onItemMount?: (instance: MergedFeatures<F>["itemInstance"], itemElement: HTMLElement, tree: MergedFeatures<F>["treeInstance"]) => void;
|
|
62
63
|
onItemUnmount?: (instance: MergedFeatures<F>["itemInstance"], itemElement: HTMLElement, tree: MergedFeatures<F>["treeInstance"]) => void;
|
|
63
|
-
setState?: (instance: MergedFeatures<F>["treeInstance"]) => void;
|
|
64
|
-
onConfigChange?: (instance: MergedFeatures<F>["treeInstance"]) => void;
|
|
65
|
-
onStateOrConfigChange?: (instance: MergedFeatures<F>["treeInstance"]) => void;
|
|
66
64
|
hotkeys?: HotkeysConfig<T, D>;
|
|
67
65
|
};
|
|
68
66
|
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ItemInstance } from "../types/core";
|
|
2
|
+
import { DropTarget } from "../features/drag-and-drop/types";
|
|
3
|
+
export declare const createOnDropHandler: <T>(onChangeChildren: (item: ItemInstance<T>, newChildren: string[]) => void) => (items: ItemInstance<T>[], target: DropTarget<T>) => void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { removeItemsFromParents } from "./remove-items-from-parents";
|
|
2
|
+
import { insertItemsAtTarget } from "./insert-items-at-target";
|
|
3
|
+
export const createOnDropHandler = (onChangeChildren) => (items, target) => {
|
|
4
|
+
const itemIds = items.map((item) => item.getId());
|
|
5
|
+
removeItemsFromParents(items, onChangeChildren);
|
|
6
|
+
insertItemsAtTarget(itemIds, target, onChangeChildren);
|
|
7
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ItemInstance } from "../types/core";
|
|
2
|
+
import { DropTarget } from "../features/drag-and-drop/types";
|
|
3
|
+
export declare const insertItemsAtTarget: <T>(itemIds: string[], target: DropTarget<T>, onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => void) => void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const insertItemsAtTarget = (itemIds, target, onChangeChildren) => {
|
|
2
|
+
// add moved items to new common parent, if dropped onto parent
|
|
3
|
+
if (target.childIndex === null) {
|
|
4
|
+
onChangeChildren(target.item, [
|
|
5
|
+
...target.item.getChildren().map((item) => item.getId()),
|
|
6
|
+
...itemIds,
|
|
7
|
+
]);
|
|
8
|
+
// TODO items[0].getTree().rebuildTree();
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
// add moved items to new common parent, if dropped between siblings
|
|
12
|
+
const oldChildren = target.item.getChildren();
|
|
13
|
+
const newChildren = [
|
|
14
|
+
...oldChildren.slice(0, target.insertionIndex).map((item) => item.getId()),
|
|
15
|
+
...itemIds,
|
|
16
|
+
...oldChildren.slice(target.insertionIndex).map((item) => item.getId()),
|
|
17
|
+
];
|
|
18
|
+
onChangeChildren(target.item, newChildren);
|
|
19
|
+
target.item.getTree().rebuildTree();
|
|
20
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const removeItemsFromParents = (movedItems, onChangeChildren) => {
|
|
2
|
+
var _a;
|
|
3
|
+
// TODO bulk sibling changes together
|
|
4
|
+
for (const item of movedItems) {
|
|
5
|
+
const siblings = (_a = item.getParent()) === null || _a === void 0 ? void 0 : _a.getChildren();
|
|
6
|
+
if (siblings) {
|
|
7
|
+
onChangeChildren(item.getParent(), siblings
|
|
8
|
+
.filter((sibling) => sibling.getId() !== item.getId())
|
|
9
|
+
.map((i) => i.getId()));
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
movedItems[0].getTree().rebuildTree();
|
|
13
|
+
};
|
package/lib/esm/utils.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DropTarget } from "./features/drag-and-drop/types";
|
|
1
|
+
import { TreeState, Updater } from "./types/core";
|
|
3
2
|
export type NoInfer<T> = [T][T extends any ? 0 : never];
|
|
4
3
|
export declare const memo: <D extends readonly any[], R>(fn: (...args_0: D) => R, deps: () => [...D]) => () => R;
|
|
5
4
|
export declare function functionalUpdate<T>(updater: Updater<T>, input: T): T;
|
|
6
5
|
export declare function makeStateUpdater<K extends keyof TreeState<any>>(key: K, instance: unknown): (updater: Updater<TreeState<any>[K]>) => void;
|
|
7
|
-
export declare const scrollIntoView: (element: Element | undefined | null) => void;
|
|
8
|
-
export declare const performItemsMove: <T>(items: ItemInstance<T>[], target: DropTarget<T>, onChangeChildren: (item: ItemInstance<T>, newChildren: ItemInstance<T>[]) => void) => void;
|
|
9
6
|
export declare const poll: (fn: () => boolean, interval?: number, timeout?: number) => Promise<void>;
|
package/lib/esm/utils.js
CHANGED
|
@@ -31,56 +31,6 @@ export function makeStateUpdater(key, instance) {
|
|
|
31
31
|
});
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
export const scrollIntoView = (element) => {
|
|
35
|
-
if (!element) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (element.scrollIntoViewIfNeeded) {
|
|
39
|
-
element.scrollIntoViewIfNeeded();
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
const boundingBox = element.getBoundingClientRect();
|
|
43
|
-
const isElementInViewport = boundingBox.top >= 0 &&
|
|
44
|
-
boundingBox.left >= 0 &&
|
|
45
|
-
boundingBox.bottom <=
|
|
46
|
-
(window.innerHeight || document.documentElement.clientHeight) &&
|
|
47
|
-
boundingBox.right <=
|
|
48
|
-
(window.innerWidth || document.documentElement.clientWidth);
|
|
49
|
-
if (!isElementInViewport) {
|
|
50
|
-
element.scrollIntoView();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
export const performItemsMove = (items, target, onChangeChildren) => {
|
|
55
|
-
var _a;
|
|
56
|
-
const numberOfDragItemsBeforeTarget = !target.childIndex
|
|
57
|
-
? 0
|
|
58
|
-
: target.item
|
|
59
|
-
.getChildren()
|
|
60
|
-
.slice(0, target.childIndex)
|
|
61
|
-
.filter((child) => items.some((item) => item.getId() === child.getId()))
|
|
62
|
-
.length;
|
|
63
|
-
// TODO bulk sibling changes together
|
|
64
|
-
for (const item of items) {
|
|
65
|
-
const siblings = (_a = item.getParent()) === null || _a === void 0 ? void 0 : _a.getChildren();
|
|
66
|
-
if (siblings) {
|
|
67
|
-
onChangeChildren(item.getParent(), siblings.filter((sibling) => sibling.getId() !== item.getId()));
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (target.childIndex === null) {
|
|
71
|
-
onChangeChildren(target.item, [...target.item.getChildren(), ...items]);
|
|
72
|
-
items[0].getTree().rebuildTree();
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const oldChildren = target.item.getChildren();
|
|
76
|
-
const newChildren = [
|
|
77
|
-
...oldChildren.slice(0, target.childIndex - numberOfDragItemsBeforeTarget),
|
|
78
|
-
...items,
|
|
79
|
-
...oldChildren.slice(target.childIndex - numberOfDragItemsBeforeTarget),
|
|
80
|
-
];
|
|
81
|
-
onChangeChildren(target.item, newChildren);
|
|
82
|
-
items[0].getTree().rebuildTree();
|
|
83
|
-
};
|
|
84
34
|
export const poll = (fn, interval = 100, timeout = 1000) => new Promise((resolve) => {
|
|
85
35
|
let clear;
|
|
86
36
|
const i = setInterval(() => {
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@headless-tree/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"main": "lib/cjs/index.js",
|
|
5
5
|
"module": "lib/esm/index.js",
|
|
6
6
|
"types": "lib/esm/index.d.ts",
|
|
7
7
|
"sideEffects": false,
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build:cjs": "tsc",
|
|
10
|
-
"build:esm": "tsc
|
|
9
|
+
"build:cjs": "tsc -m commonjs --outDir lib/cjs",
|
|
10
|
+
"build:esm": "tsc",
|
|
11
11
|
"start": "tsc -w"
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
package/src/core/create-tree.ts
CHANGED
|
@@ -38,12 +38,16 @@ export const createTree = <T>(
|
|
|
38
38
|
const additionalFeatures = [treeFeature, ...(initialConfig.features ?? [])];
|
|
39
39
|
let state = additionalFeatures.reduce(
|
|
40
40
|
(acc, feature) => feature.getInitialState?.(acc, treeInstance) ?? acc,
|
|
41
|
-
initialConfig.state ?? {}
|
|
41
|
+
initialConfig.initialState ?? initialConfig.state ?? {}
|
|
42
42
|
) as TreeState<T>;
|
|
43
43
|
let config = additionalFeatures.reduce(
|
|
44
44
|
(acc, feature) => feature.getDefaultConfig?.(acc, treeInstance) ?? acc,
|
|
45
45
|
initialConfig
|
|
46
46
|
) as TreeConfig<T>;
|
|
47
|
+
const stateHandlerNames = additionalFeatures.reduce(
|
|
48
|
+
(acc, feature) => ({ ...acc, ...feature.stateHandlerNames }),
|
|
49
|
+
{} as Record<string, string>
|
|
50
|
+
);
|
|
47
51
|
|
|
48
52
|
let treeElement: HTMLElement | undefined | null;
|
|
49
53
|
const treeDataRef: { current: any } = { current: {} };
|
|
@@ -109,10 +113,14 @@ export const createTree = <T>(
|
|
|
109
113
|
...prev,
|
|
110
114
|
getState: () => state,
|
|
111
115
|
setState: (updater) => {
|
|
112
|
-
|
|
116
|
+
// Not necessary, since I think the subupdate below keeps the state fresh anyways?
|
|
117
|
+
// state = typeof updater === "function" ? updater(state) : updater;
|
|
113
118
|
config.setState?.(state);
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
},
|
|
120
|
+
applySubStateUpdate: (stateName, updater) => {
|
|
121
|
+
state[stateName] =
|
|
122
|
+
typeof updater === "function" ? updater(state[stateName]) : updater;
|
|
123
|
+
config[stateHandlerNames[stateName]]!(state[stateName]);
|
|
116
124
|
},
|
|
117
125
|
rebuildTree: () => {
|
|
118
126
|
rebuildItemMeta(mainFeature);
|
|
@@ -124,11 +132,7 @@ export const createTree = <T>(
|
|
|
124
132
|
|
|
125
133
|
if (config.state) {
|
|
126
134
|
state = { ...state, ...config.state };
|
|
127
|
-
eachFeature((feature) => feature.setState?.(treeInstance));
|
|
128
135
|
}
|
|
129
|
-
|
|
130
|
-
eachFeature((feature) => feature.onConfigChange?.(treeInstance));
|
|
131
|
-
eachFeature((feature) => feature.onStateOrConfigChange?.(treeInstance));
|
|
132
136
|
},
|
|
133
137
|
getItemInstance: (itemId) => itemInstancesMap[itemId],
|
|
134
138
|
getItems: () => itemInstances,
|
|
@@ -22,6 +22,10 @@ export const asyncDataLoaderFeature: FeatureImplementation<
|
|
|
22
22
|
...defaultConfig,
|
|
23
23
|
}),
|
|
24
24
|
|
|
25
|
+
stateHandlerNames: {
|
|
26
|
+
loadingItems: "setLoadingItems",
|
|
27
|
+
},
|
|
28
|
+
|
|
25
29
|
createTreeInstance: (prev, instance) => ({
|
|
26
30
|
...prev,
|
|
27
31
|
|
|
@@ -36,11 +40,14 @@ export const asyncDataLoaderFeature: FeatureImplementation<
|
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
if (!instance.getState().loadingItems.includes(itemId)) {
|
|
39
|
-
|
|
43
|
+
instance.applySubStateUpdate("loadingItems", (loadingItems) => [
|
|
44
|
+
...loadingItems,
|
|
45
|
+
itemId,
|
|
46
|
+
]);
|
|
40
47
|
config.asyncDataLoader?.getItem(itemId).then((item) => {
|
|
41
48
|
dataRef.current.itemData[itemId] = item;
|
|
42
49
|
config.onLoadedItem?.(itemId, item);
|
|
43
|
-
|
|
50
|
+
instance.applySubStateUpdate("loadingItems", (loadingItems) =>
|
|
44
51
|
loadingItems.filter((id) => id !== itemId)
|
|
45
52
|
);
|
|
46
53
|
});
|
|
@@ -62,7 +69,10 @@ export const asyncDataLoaderFeature: FeatureImplementation<
|
|
|
62
69
|
return [];
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
|
|
72
|
+
instance.applySubStateUpdate("loadingItems", (loadingItems) => [
|
|
73
|
+
...loadingItems,
|
|
74
|
+
itemId,
|
|
75
|
+
]);
|
|
66
76
|
|
|
67
77
|
if (config.asyncDataLoader?.getChildrenWithData) {
|
|
68
78
|
config.asyncDataLoader?.getChildrenWithData(itemId).then((children) => {
|
|
@@ -73,7 +83,7 @@ export const asyncDataLoaderFeature: FeatureImplementation<
|
|
|
73
83
|
const childrenIds = children.map(({ id }) => id);
|
|
74
84
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
75
85
|
config.onLoadedChildren?.(itemId, childrenIds);
|
|
76
|
-
|
|
86
|
+
instance.applySubStateUpdate("loadingItems", (loadingItems) =>
|
|
77
87
|
loadingItems.filter((id) => id !== itemId)
|
|
78
88
|
);
|
|
79
89
|
instance.rebuildTree();
|
|
@@ -82,7 +92,7 @@ export const asyncDataLoaderFeature: FeatureImplementation<
|
|
|
82
92
|
config.asyncDataLoader?.getChildren(itemId).then((childrenIds) => {
|
|
83
93
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
84
94
|
config.onLoadedChildren?.(itemId, childrenIds);
|
|
85
|
-
|
|
95
|
+
instance.applySubStateUpdate("loadingItems", (loadingItems) =>
|
|
86
96
|
loadingItems.filter((id) => id !== itemId)
|
|
87
97
|
);
|
|
88
98
|
instance.rebuildTree();
|
|
@@ -13,10 +13,15 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
13
13
|
|
|
14
14
|
getDefaultConfig: (defaultConfig, tree) => ({
|
|
15
15
|
canDrop: (_, target) => target.item.isFolder(),
|
|
16
|
+
canDropForeignDragObject: () => false,
|
|
16
17
|
setDndState: makeStateUpdater("dnd", tree),
|
|
17
18
|
...defaultConfig,
|
|
18
19
|
}),
|
|
19
20
|
|
|
21
|
+
stateHandlerNames: {
|
|
22
|
+
dnd: "setDndState",
|
|
23
|
+
},
|
|
24
|
+
|
|
20
25
|
createTreeInstance: (prev, tree) => ({
|
|
21
26
|
...prev,
|
|
22
27
|
|
|
@@ -33,7 +38,7 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
33
38
|
|
|
34
39
|
draggable: tree.getConfig().isItemDraggable?.(item) ?? true,
|
|
35
40
|
|
|
36
|
-
onDragStart: (e) => {
|
|
41
|
+
onDragStart: item.getMemoizedProp("dnd/onDragStart", () => (e) => {
|
|
37
42
|
const selectedItems = tree.getSelectedItems();
|
|
38
43
|
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
39
44
|
const config = tree.getConfig();
|
|
@@ -52,13 +57,13 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
52
57
|
e.dataTransfer?.setData(format, data);
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
tree.
|
|
60
|
+
tree.applySubStateUpdate("dnd", {
|
|
56
61
|
draggedItems: items,
|
|
57
62
|
draggingOverItem: tree.getFocusedItem(),
|
|
58
63
|
});
|
|
59
|
-
},
|
|
64
|
+
}),
|
|
60
65
|
|
|
61
|
-
onDragOver: (e) => {
|
|
66
|
+
onDragOver: item.getMemoizedProp("dnd/onDragOver", () => (e) => {
|
|
62
67
|
const target = getDropTarget(e, item, tree);
|
|
63
68
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
64
69
|
|
|
@@ -82,24 +87,35 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
82
87
|
|
|
83
88
|
dataRef.current.lastDragCode = nextDragCode;
|
|
84
89
|
|
|
85
|
-
tree.
|
|
90
|
+
tree.applySubStateUpdate("dnd", (state) => ({
|
|
86
91
|
...state,
|
|
87
92
|
dragTarget: target,
|
|
88
93
|
draggingOverItem: item,
|
|
89
94
|
}));
|
|
90
|
-
},
|
|
95
|
+
}),
|
|
91
96
|
|
|
92
|
-
onDragLeave: () => {
|
|
97
|
+
onDragLeave: item.getMemoizedProp("dnd/onDragLeave", () => () => {
|
|
93
98
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
94
99
|
dataRef.current.lastDragCode = "no-drag";
|
|
95
|
-
tree.
|
|
100
|
+
tree.applySubStateUpdate("dnd", (state) => ({
|
|
96
101
|
...state,
|
|
97
102
|
draggingOverItem: undefined,
|
|
98
103
|
dragTarget: undefined,
|
|
99
104
|
}));
|
|
100
|
-
},
|
|
105
|
+
}),
|
|
106
|
+
|
|
107
|
+
onDragEnd: item.getMemoizedProp("dnd/onDragEnd", () => (e) => {
|
|
108
|
+
const draggedItems = tree.getState().dnd?.draggedItems;
|
|
109
|
+
tree.applySubStateUpdate("dnd", null);
|
|
101
110
|
|
|
102
|
-
|
|
111
|
+
if (e.dataTransfer.dropEffect === "none" || !draggedItems) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
tree.getConfig().onCompleteForeignDrop?.(draggedItems);
|
|
116
|
+
}),
|
|
117
|
+
|
|
118
|
+
onDrop: item.getMemoizedProp("dnd/onDrop", () => (e) => {
|
|
103
119
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
104
120
|
const target = getDropTarget(e, item, tree);
|
|
105
121
|
|
|
@@ -112,7 +128,7 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
112
128
|
const draggedItems = tree.getState().dnd?.draggedItems;
|
|
113
129
|
|
|
114
130
|
dataRef.current.lastDragCode = undefined;
|
|
115
|
-
tree.
|
|
131
|
+
tree.applySubStateUpdate("dnd", null);
|
|
116
132
|
|
|
117
133
|
if (draggedItems) {
|
|
118
134
|
config.onDrop?.(draggedItems, target);
|
|
@@ -120,7 +136,7 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
120
136
|
config.onDropForeignDragObject?.(e.dataTransfer, target);
|
|
121
137
|
}
|
|
122
138
|
// TODO rebuild tree?
|
|
123
|
-
},
|
|
139
|
+
}),
|
|
124
140
|
}),
|
|
125
141
|
|
|
126
142
|
isDropTarget: () => {
|
|
@@ -131,19 +147,25 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
131
147
|
isDropTargetAbove: () => {
|
|
132
148
|
const target = tree.getDropTarget();
|
|
133
149
|
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
150
|
+
if (
|
|
151
|
+
!target ||
|
|
152
|
+
target.childIndex === null ||
|
|
153
|
+
target.item !== item.getParent()
|
|
154
|
+
)
|
|
155
|
+
return false;
|
|
156
|
+
return target.childIndex === item.getItemMeta().posInSet;
|
|
138
157
|
},
|
|
139
158
|
|
|
140
159
|
isDropTargetBelow: () => {
|
|
141
160
|
const target = tree.getDropTarget();
|
|
142
161
|
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
162
|
+
if (
|
|
163
|
+
!target ||
|
|
164
|
+
target.childIndex === null ||
|
|
165
|
+
target.item !== item.getParent()
|
|
166
|
+
)
|
|
167
|
+
return false;
|
|
168
|
+
return target.childIndex - 1 === item.getItemMeta().posInSet;
|
|
147
169
|
},
|
|
148
170
|
|
|
149
171
|
isDraggingOver: () => {
|
|
@@ -5,15 +5,22 @@ export type DndDataRef = {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
export type DndState<T> = {
|
|
8
|
-
draggedItems?: ItemInstance<T>[];
|
|
8
|
+
draggedItems?: ItemInstance<T>[]; // TODO not used anymore?
|
|
9
9
|
draggingOverItem?: ItemInstance<T>;
|
|
10
10
|
dragTarget?: DropTarget<T>;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
export type DropTarget<T> =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
export type DropTarget<T> =
|
|
14
|
+
| {
|
|
15
|
+
item: ItemInstance<T>;
|
|
16
|
+
childIndex: number;
|
|
17
|
+
insertionIndex: number;
|
|
18
|
+
}
|
|
19
|
+
| {
|
|
20
|
+
item: ItemInstance<T>;
|
|
21
|
+
childIndex: null;
|
|
22
|
+
insertionIndex: null;
|
|
23
|
+
};
|
|
17
24
|
|
|
18
25
|
export enum DropTargetPosition {
|
|
19
26
|
Top = "top",
|
|
@@ -44,12 +51,22 @@ export type DragAndDropFeatureDef<T> = {
|
|
|
44
51
|
dataTransfer: DataTransfer,
|
|
45
52
|
target: DropTarget<T>
|
|
46
53
|
) => boolean;
|
|
47
|
-
|
|
48
54
|
onDrop?: (items: ItemInstance<T>[], target: DropTarget<T>) => void;
|
|
49
55
|
onDropForeignDragObject?: (
|
|
50
56
|
dataTransfer: DataTransfer,
|
|
51
57
|
target: DropTarget<T>
|
|
52
58
|
) => void;
|
|
59
|
+
|
|
60
|
+
/** Runs in the onDragEnd event, if `ev.dataTransfer.dropEffect` is not `none`, i.e. the drop
|
|
61
|
+
* was not aborted. No target is provided as parameter since the target may be a foreign drop target.
|
|
62
|
+
* This is useful to seperate out the logic to move dragged items out of their previous parents.
|
|
63
|
+
* Use `onDrop` to handle drop-related logic.
|
|
64
|
+
*
|
|
65
|
+
* This ignores the `canDrop` handler, since the drop target is unknown in this handler.
|
|
66
|
+
*/
|
|
67
|
+
// onSuccessfulDragEnd?: (items: ItemInstance<T>[]) => void;
|
|
68
|
+
|
|
69
|
+
onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void;
|
|
53
70
|
};
|
|
54
71
|
treeInstance: {
|
|
55
72
|
getDropTarget: () => DropTarget<T> | null;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { ItemInstance, TreeInstance } from "../../types/core";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DndState,
|
|
4
|
+
DragAndDropFeatureDef,
|
|
5
|
+
DropTarget,
|
|
6
|
+
DropTargetPosition,
|
|
7
|
+
} from "./types";
|
|
3
8
|
|
|
4
9
|
export const getDragCode = ({ item, childIndex }: DropTarget<any>) =>
|
|
5
10
|
`${item.getId()}__${childIndex ?? "none"}`;
|
|
@@ -49,40 +54,64 @@ const getDropTargetPosition = (
|
|
|
49
54
|
export const getDropTarget = (
|
|
50
55
|
e: any,
|
|
51
56
|
item: ItemInstance<any>,
|
|
52
|
-
tree: TreeInstance<any
|
|
57
|
+
tree: TreeInstance<any>,
|
|
58
|
+
canDropInbetween = tree.getConfig().canDropInbetween
|
|
53
59
|
): DropTarget<any> => {
|
|
54
60
|
const config = tree.getConfig();
|
|
55
|
-
const
|
|
61
|
+
const draggedItems = tree.getState().dnd?.draggedItems ?? [];
|
|
62
|
+
const itemTarget = { item, childIndex: null, insertionIndex: null };
|
|
63
|
+
const parentTarget = {
|
|
64
|
+
item: item.getParent(),
|
|
65
|
+
childIndex: null,
|
|
66
|
+
insertionIndex: null,
|
|
67
|
+
};
|
|
56
68
|
|
|
57
|
-
|
|
69
|
+
if (!canDropInbetween) {
|
|
70
|
+
if (!canDrop(e.dataTransfer, parentTarget, tree)) {
|
|
71
|
+
return getDropTarget(e, item.getParent(), tree, false);
|
|
72
|
+
}
|
|
73
|
+
return itemTarget;
|
|
74
|
+
}
|
|
58
75
|
|
|
59
|
-
const
|
|
60
|
-
offset,
|
|
61
|
-
config.topLinePercentage ?? 0.3,
|
|
62
|
-
config.bottomLinePercentage ?? 0.7
|
|
63
|
-
);
|
|
64
|
-
const inbetweenPos = getDropTargetPosition(offset, 0.5, 0.5);
|
|
76
|
+
const canDropInside = canDrop(e.dataTransfer, itemTarget, tree);
|
|
65
77
|
|
|
66
|
-
|
|
67
|
-
return dropOnItemTarget;
|
|
68
|
-
}
|
|
78
|
+
const offset = getDropOffset(e, item);
|
|
69
79
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
80
|
+
const pos = canDropInside
|
|
81
|
+
? getDropTargetPosition(
|
|
82
|
+
offset,
|
|
83
|
+
config.topLinePercentage ?? 0.3,
|
|
84
|
+
config.bottomLinePercentage ?? 0.7
|
|
85
|
+
)
|
|
86
|
+
: getDropTargetPosition(offset, 0.5, 0.5);
|
|
78
87
|
|
|
79
88
|
if (pos === DropTargetPosition.Item) {
|
|
80
|
-
return
|
|
89
|
+
return itemTarget;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!canDrop(e.dataTransfer, parentTarget, tree)) {
|
|
93
|
+
return getDropTarget(e, item.getParent(), tree, false);
|
|
81
94
|
}
|
|
82
95
|
|
|
96
|
+
const childIndex =
|
|
97
|
+
item.getIndexInParent() + (pos === DropTargetPosition.Top ? 0 : 1);
|
|
98
|
+
|
|
99
|
+
const numberOfDragItemsBeforeTarget = item
|
|
100
|
+
.getParent()
|
|
101
|
+
.getChildren()
|
|
102
|
+
.slice(0, childIndex)
|
|
103
|
+
.reduce(
|
|
104
|
+
(counter, child) =>
|
|
105
|
+
child && draggedItems?.some((i) => i.getId() === child.getId())
|
|
106
|
+
? ++counter
|
|
107
|
+
: counter,
|
|
108
|
+
0
|
|
109
|
+
);
|
|
110
|
+
|
|
83
111
|
return {
|
|
84
112
|
item: item.getParent(),
|
|
85
|
-
childIndex
|
|
86
|
-
|
|
113
|
+
childIndex,
|
|
114
|
+
// TODO performance could be improved by computing this only when dragcode changed
|
|
115
|
+
insertionIndex: childIndex - numberOfDragItemsBeforeTarget,
|
|
87
116
|
};
|
|
88
117
|
};
|
|
@@ -25,8 +25,9 @@ export const expandAllFeature: FeatureImplementation<
|
|
|
25
25
|
);
|
|
26
26
|
},
|
|
27
27
|
|
|
28
|
-
collapseAll:
|
|
29
|
-
tree.
|
|
28
|
+
collapseAll: () => {
|
|
29
|
+
tree.applySubStateUpdate("expandedItems", []);
|
|
30
|
+
tree.rebuildTree();
|
|
30
31
|
},
|
|
31
32
|
}),
|
|
32
33
|
|
|
@@ -37,6 +38,9 @@ export const expandAllFeature: FeatureImplementation<
|
|
|
37
38
|
if (cancelToken?.current) {
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
41
|
+
if (!item.isFolder()) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
40
44
|
|
|
41
45
|
item.expand();
|
|
42
46
|
await poll(() => !tree.getState().loadingItems.includes(item.getId()));
|
|
@@ -50,12 +54,10 @@ export const expandAllFeature: FeatureImplementation<
|
|
|
50
54
|
);
|
|
51
55
|
},
|
|
52
56
|
|
|
53
|
-
collapseAll:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
})
|
|
58
|
-
);
|
|
57
|
+
collapseAll: () => {
|
|
58
|
+
for (const child of item.getChildren()) {
|
|
59
|
+
child?.collapseAll();
|
|
60
|
+
}
|
|
59
61
|
item.collapse();
|
|
60
62
|
},
|
|
61
63
|
}),
|
|
@@ -3,11 +3,11 @@ export type ExpandAllFeatureDef = {
|
|
|
3
3
|
config: {};
|
|
4
4
|
treeInstance: {
|
|
5
5
|
expandAll: (cancelToken?: { current: boolean }) => Promise<void>;
|
|
6
|
-
collapseAll: () =>
|
|
6
|
+
collapseAll: () => void;
|
|
7
7
|
};
|
|
8
8
|
itemInstance: {
|
|
9
9
|
expandAll: (cancelToken?: { current: boolean }) => Promise<void>;
|
|
10
|
-
collapseAll: () =>
|
|
10
|
+
collapseAll: () => void;
|
|
11
11
|
};
|
|
12
12
|
hotkeys: never;
|
|
13
13
|
};
|