@headless-tree/core 0.0.5 → 0.0.7
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 +34 -12
- package/lib/cjs/features/async-data-loader/feature.js +30 -22
- package/lib/cjs/features/drag-and-drop/feature.js +58 -18
- package/lib/cjs/features/drag-and-drop/types.d.ts +21 -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 -10
- package/lib/cjs/features/expand-all/types.d.ts +2 -2
- package/lib/cjs/features/hotkeys-core/feature.js +0 -1
- package/lib/cjs/features/main/types.d.ts +3 -1
- package/lib/cjs/features/renaming/feature.js +10 -11
- package/lib/cjs/features/search/feature.js +21 -8
- package/lib/cjs/features/search/types.d.ts +1 -1
- package/lib/cjs/features/selection/feature.js +6 -5
- package/lib/cjs/features/sync-data-loader/feature.js +6 -1
- package/lib/cjs/features/sync-data-loader/types.d.ts +1 -1
- package/lib/cjs/features/tree/feature.js +23 -24
- package/lib/cjs/features/tree/types.d.ts +2 -2
- package/lib/cjs/index.d.ts +3 -1
- package/lib/cjs/index.js +3 -1
- package/lib/cjs/types/core.d.ts +3 -4
- 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 +34 -12
- package/lib/esm/features/async-data-loader/feature.js +30 -22
- package/lib/esm/features/drag-and-drop/feature.js +58 -18
- package/lib/esm/features/drag-and-drop/types.d.ts +21 -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 -10
- package/lib/esm/features/expand-all/types.d.ts +2 -2
- package/lib/esm/features/hotkeys-core/feature.js +0 -1
- package/lib/esm/features/main/types.d.ts +3 -1
- package/lib/esm/features/renaming/feature.js +10 -11
- package/lib/esm/features/search/feature.js +21 -8
- package/lib/esm/features/search/types.d.ts +1 -1
- package/lib/esm/features/selection/feature.js +6 -5
- package/lib/esm/features/sync-data-loader/feature.js +6 -1
- package/lib/esm/features/sync-data-loader/types.d.ts +1 -1
- package/lib/esm/features/tree/feature.js +23 -24
- package/lib/esm/features/tree/types.d.ts +2 -2
- package/lib/esm/index.d.ts +3 -1
- package/lib/esm/index.js +3 -1
- package/lib/esm/types/core.d.ts +3 -4
- 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 +1 -1
- package/src/core/create-tree.ts +42 -9
- package/src/features/async-data-loader/feature.ts +15 -6
- package/src/features/drag-and-drop/feature.ts +75 -14
- package/src/features/drag-and-drop/types.ts +30 -5
- package/src/features/drag-and-drop/utils.ts +53 -24
- package/src/features/expand-all/feature.ts +10 -9
- package/src/features/expand-all/types.ts +2 -2
- package/src/features/hotkeys-core/feature.ts +0 -1
- package/src/features/main/types.ts +6 -0
- package/src/features/renaming/feature.ts +10 -7
- package/src/features/search/feature.ts +22 -6
- package/src/features/search/types.ts +1 -0
- package/src/features/selection/feature.ts +7 -3
- package/src/features/sync-data-loader/feature.ts +17 -3
- package/src/features/sync-data-loader/types.ts +1 -1
- package/src/features/tree/feature.ts +23 -22
- package/src/features/tree/types.ts +4 -2
- package/src/index.ts +4 -1
- package/src/types/core.ts +6 -5
- 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/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
|
@@ -10,9 +10,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { makeStateUpdater, memo, poll } from "../../utils";
|
|
11
11
|
export const treeFeature = {
|
|
12
12
|
key: "tree",
|
|
13
|
-
dependingFeatures: ["main"],
|
|
14
13
|
getInitialState: (initialState) => (Object.assign({ expandedItems: [], focusedItem: null }, initialState)),
|
|
15
14
|
getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setExpandedItems: makeStateUpdater("expandedItems", tree), setFocusedItem: makeStateUpdater("focusedItem", tree) }, defaultConfig)),
|
|
15
|
+
stateHandlerNames: {
|
|
16
|
+
expandedItems: "setExpandedItems",
|
|
17
|
+
focusedItem: "setFocusedItem",
|
|
18
|
+
},
|
|
16
19
|
createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { retrieveItemData: () => {
|
|
17
20
|
throw new Error("No data-loader registered");
|
|
18
21
|
}, retrieveChildrenIds: () => {
|
|
@@ -20,7 +23,6 @@ export const treeFeature = {
|
|
|
20
23
|
}, isItemExpanded: (itemId) => instance.getState().expandedItems.includes(itemId), getItemsMeta: () => {
|
|
21
24
|
const { rootItemId } = instance.getConfig();
|
|
22
25
|
const { expandedItems } = instance.getState();
|
|
23
|
-
// console.log("!", instance.getConfig());
|
|
24
26
|
const flatItems = [];
|
|
25
27
|
const recursiveAdd = (itemId, parentId, level, setSize, posInSet) => {
|
|
26
28
|
var _a;
|
|
@@ -47,23 +49,23 @@ export const treeFeature = {
|
|
|
47
49
|
}
|
|
48
50
|
return flatItems;
|
|
49
51
|
}, expandItem: (itemId) => {
|
|
50
|
-
var _a
|
|
52
|
+
var _a;
|
|
51
53
|
if (!instance.getItemInstance(itemId).isFolder()) {
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
54
56
|
if ((_a = instance.getState().loadingItems) === null || _a === void 0 ? void 0 : _a.includes(itemId)) {
|
|
55
57
|
return;
|
|
56
58
|
}
|
|
57
|
-
(
|
|
58
|
-
|
|
59
|
+
instance.applySubStateUpdate("expandedItems", (expandedItems) => [
|
|
60
|
+
...expandedItems,
|
|
61
|
+
itemId,
|
|
62
|
+
]);
|
|
59
63
|
instance.rebuildTree();
|
|
60
64
|
}, collapseItem: (itemId) => {
|
|
61
|
-
var _a, _b;
|
|
62
65
|
if (!instance.getItemInstance(itemId).isFolder()) {
|
|
63
66
|
return;
|
|
64
67
|
}
|
|
65
|
-
(
|
|
66
|
-
.getConfig()).setExpandedItems) === null || _b === void 0 ? void 0 : _b.call(_a, (expandedItems) => expandedItems.filter((id) => id !== itemId));
|
|
68
|
+
instance.applySubStateUpdate("expandedItems", (expandedItems) => expandedItems.filter((id) => id !== itemId));
|
|
67
69
|
instance.rebuildTree();
|
|
68
70
|
},
|
|
69
71
|
// TODO memo
|
|
@@ -71,8 +73,7 @@ export const treeFeature = {
|
|
|
71
73
|
var _a, _b;
|
|
72
74
|
return ((_b = instance.getItemInstance((_a = instance.getState().focusedItem) !== null && _a !== void 0 ? _a : "")) !== null && _b !== void 0 ? _b : instance.getItems()[0]);
|
|
73
75
|
}, focusItem: (itemId) => {
|
|
74
|
-
|
|
75
|
-
(_b = (_a = instance.getConfig()).setFocusedItem) === null || _b === void 0 ? void 0 : _b.call(_a, itemId);
|
|
76
|
+
instance.applySubStateUpdate("focusedItem", itemId);
|
|
76
77
|
}, focusNextItem: () => {
|
|
77
78
|
const { index } = instance.getFocusedItem().getItemMeta();
|
|
78
79
|
const nextIndex = Math.min(index + 1, instance.getItems().length - 1);
|
|
@@ -81,7 +82,7 @@ export const treeFeature = {
|
|
|
81
82
|
const { index } = instance.getFocusedItem().getItemMeta();
|
|
82
83
|
const nextIndex = Math.max(index - 1, 0);
|
|
83
84
|
instance.focusItem(instance.getItems()[nextIndex].getId());
|
|
84
|
-
}, updateDomFocus: (
|
|
85
|
+
}, updateDomFocus: () => {
|
|
85
86
|
// Required because if the state is managed outside in react, the state only updated during next render
|
|
86
87
|
setTimeout(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
87
88
|
var _a, _b;
|
|
@@ -92,9 +93,6 @@ export const treeFeature = {
|
|
|
92
93
|
if (!focusedElement)
|
|
93
94
|
return;
|
|
94
95
|
focusedElement.focus();
|
|
95
|
-
// if (scrollIntoView) {
|
|
96
|
-
// focusedElement.scrollIntoView();
|
|
97
|
-
// }
|
|
98
96
|
}));
|
|
99
97
|
}, getContainerProps: () => {
|
|
100
98
|
var _a;
|
|
@@ -102,11 +100,15 @@ export const treeFeature = {
|
|
|
102
100
|
} })),
|
|
103
101
|
createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { isLoading: () => {
|
|
104
102
|
throw new Error("No data-loader registered");
|
|
105
|
-
},
|
|
103
|
+
}, scrollTo: (scrollIntoViewArg) => __awaiter(void 0, void 0, void 0, function* () {
|
|
104
|
+
var _a, _b;
|
|
105
|
+
(_b = (_a = tree.getConfig()).scrollToItem) === null || _b === void 0 ? void 0 : _b.call(_a, item);
|
|
106
|
+
yield poll(() => item.getElement() !== null, 20);
|
|
107
|
+
item.getElement().scrollIntoView(scrollIntoViewArg);
|
|
108
|
+
}), getId: () => item.getItemMeta().itemId, getProps: () => {
|
|
106
109
|
var _a;
|
|
107
110
|
const itemMeta = item.getItemMeta();
|
|
108
|
-
return Object.assign(Object.assign({}, (_a = prev.getProps) === null || _a === void 0 ? void 0 : _a.call(prev)), { role: "treeitem", "aria-setsize": itemMeta.setSize, "aria-posinset": itemMeta.posInSet, "aria-selected": false, "aria-label": item.getItemName(), "aria-level": itemMeta.level, tabIndex: item.isFocused() ? 0 : -1, onClick: item.getMemoizedProp("tree/onClick", () => (e) => {
|
|
109
|
-
console.log("onClick", item.getId());
|
|
111
|
+
return Object.assign(Object.assign({}, (_a = prev.getProps) === null || _a === void 0 ? void 0 : _a.call(prev)), { role: "treeitem", "aria-setsize": itemMeta.setSize, "aria-posinset": itemMeta.posInSet, "aria-selected": "false", "aria-label": item.getItemName(), "aria-level": itemMeta.level, tabIndex: item.isFocused() ? 0 : -1, onClick: item.getMemoizedProp("tree/onClick", () => (e) => {
|
|
110
112
|
item.setFocused();
|
|
111
113
|
item.primaryAction();
|
|
112
114
|
if (e.ctrlKey || e.shiftKey || e.metaKey) {
|
|
@@ -135,12 +137,9 @@ export const treeFeature = {
|
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
139
|
return tree.getItemInstance(tree.getConfig().rootItemId);
|
|
138
|
-
}, () => [item.getItemMeta()]),
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
((_b = (_a = item.getParent()) === null || _a === void 0 ? void 0 : _a.getItemMeta().index) !== null && _b !== void 0 ? _b : 0) -
|
|
142
|
-
1;
|
|
143
|
-
}, getChildren: () => tree
|
|
140
|
+
}, () => [item.getItemMeta()]),
|
|
141
|
+
// TODO remove
|
|
142
|
+
getIndexInParent: () => item.getItemMeta().posInSet, getChildren: () => tree
|
|
144
143
|
.retrieveChildrenIds(item.getItemMeta().itemId)
|
|
145
144
|
.map((id) => tree.getItemInstance(id)), getTree: () => tree, getItemAbove: () => tree.getItems()[item.getItemMeta().index - 1], getItemBelow: () => tree.getItems()[item.getItemMeta().index + 1], getMemoizedProp: (name, create, deps) => {
|
|
146
145
|
var _a, _b, _c, _d, _e;
|
|
@@ -203,7 +202,7 @@ export const treeFeature = {
|
|
|
203
202
|
if ((!item.isExpanded() || !item.isFolder()) &&
|
|
204
203
|
item.getItemMeta().level !== 0) {
|
|
205
204
|
(_a = item.getParent()) === null || _a === void 0 ? void 0 : _a.setFocused();
|
|
206
|
-
tree.updateDomFocus(
|
|
205
|
+
tree.updateDomFocus();
|
|
207
206
|
}
|
|
208
207
|
else {
|
|
209
208
|
item.collapse();
|
|
@@ -34,8 +34,7 @@ export type TreeFeatureDef<T> = {
|
|
|
34
34
|
getFocusedItem: () => ItemInstance<any>;
|
|
35
35
|
focusNextItem: () => void;
|
|
36
36
|
focusPreviousItem: () => void;
|
|
37
|
-
|
|
38
|
-
updateDomFocus: (scrollIntoView?: boolean) => void;
|
|
37
|
+
updateDomFocus: () => void;
|
|
39
38
|
getContainerProps: () => Record<string, any>;
|
|
40
39
|
};
|
|
41
40
|
itemInstance: {
|
|
@@ -57,6 +56,7 @@ export type TreeFeatureDef<T> = {
|
|
|
57
56
|
getItemAbove: () => ItemInstance<T> | null;
|
|
58
57
|
getItemBelow: () => ItemInstance<T> | null;
|
|
59
58
|
getMemoizedProp: <X>(name: string, create: () => X, deps?: any[]) => X;
|
|
59
|
+
scrollTo: (scrollIntoViewArg?: boolean | ScrollIntoViewOptions) => Promise<void>;
|
|
60
60
|
};
|
|
61
61
|
hotkeys: "focusNextItem" | "focusPreviousItem" | "expandOrDown" | "collapseOrUp" | "focusFirstItem" | "focusLastItem";
|
|
62
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
|
@@ -51,7 +51,9 @@ export type HotkeysConfig<T, F extends FeatureDef = FeatureDefs<T>> = Record<Hot
|
|
|
51
51
|
export type CustomHotkeysConfig<T, F extends FeatureDef = FeatureDefs<T>> = Partial<Record<HotkeyName<F> | `custom${string}`, Partial<HotkeyConfig<T>>>>;
|
|
52
52
|
export type FeatureImplementation<T = any, D extends FeatureDef = any, F extends FeatureDef = EmptyFeatureDef> = {
|
|
53
53
|
key?: string;
|
|
54
|
-
|
|
54
|
+
deps?: string[];
|
|
55
|
+
overwrites?: string[];
|
|
56
|
+
stateHandlerNames?: Partial<Record<keyof MergedFeatures<F>["state"], keyof MergedFeatures<F>["config"]>>;
|
|
55
57
|
getInitialState?: (initialState: Partial<MergedFeatures<F>["state"]>, tree: MergedFeatures<F>["treeInstance"]) => Partial<D["state"] & MergedFeatures<F>["state"]>;
|
|
56
58
|
getDefaultConfig?: (defaultConfig: Partial<MergedFeatures<F>["config"]>, tree: MergedFeatures<F>["treeInstance"]) => Partial<D["config"] & MergedFeatures<F>["config"]>;
|
|
57
59
|
createTreeInstance?: (prev: MergedFeatures<F>["treeInstance"], instance: MergedFeatures<F>["treeInstance"]) => D["treeInstance"] & MergedFeatures<F>["treeInstance"];
|
|
@@ -60,9 +62,6 @@ export type FeatureImplementation<T = any, D extends FeatureDef = any, F extends
|
|
|
60
62
|
onTreeUnmount?: (instance: MergedFeatures<F>["treeInstance"], treeElement: HTMLElement) => void;
|
|
61
63
|
onItemMount?: (instance: MergedFeatures<F>["itemInstance"], itemElement: HTMLElement, tree: MergedFeatures<F>["treeInstance"]) => void;
|
|
62
64
|
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
65
|
hotkeys?: HotkeysConfig<T, D>;
|
|
67
66
|
};
|
|
68
67
|
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
package/src/core/create-tree.ts
CHANGED
|
@@ -30,12 +30,42 @@ const buildItemInstance = (
|
|
|
30
30
|
return itemInstance;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
const verifyFeatures = (features: FeatureImplementation[] | undefined) => {
|
|
34
|
+
const loadedFeatures = features?.map((feature) => feature.key);
|
|
35
|
+
for (const feature of features ?? []) {
|
|
36
|
+
const missingDependency = feature.deps?.find(
|
|
37
|
+
(dep) => !loadedFeatures?.includes(dep)
|
|
38
|
+
);
|
|
39
|
+
if (missingDependency) {
|
|
40
|
+
throw new Error(`${feature.key} needs ${missingDependency}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const compareFeatures = (
|
|
46
|
+
feature1: FeatureImplementation,
|
|
47
|
+
feature2: FeatureImplementation
|
|
48
|
+
) => {
|
|
49
|
+
if (feature2.key && feature1.overwrites?.includes(feature2.key)) {
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
return -1;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const sortFeatures = (features: FeatureImplementation[] = []) =>
|
|
56
|
+
features.sort(compareFeatures);
|
|
57
|
+
|
|
33
58
|
export const createTree = <T>(
|
|
34
59
|
initialConfig: TreeConfig<T>
|
|
35
60
|
): TreeInstance<T> => {
|
|
36
61
|
const treeInstance: TreeInstance<T> = {} as any;
|
|
37
62
|
|
|
38
|
-
const additionalFeatures = [
|
|
63
|
+
const additionalFeatures = [
|
|
64
|
+
treeFeature,
|
|
65
|
+
...sortFeatures(initialConfig.features),
|
|
66
|
+
];
|
|
67
|
+
verifyFeatures(additionalFeatures);
|
|
68
|
+
|
|
39
69
|
let state = additionalFeatures.reduce(
|
|
40
70
|
(acc, feature) => feature.getInitialState?.(acc, treeInstance) ?? acc,
|
|
41
71
|
initialConfig.initialState ?? initialConfig.state ?? {}
|
|
@@ -44,6 +74,10 @@ export const createTree = <T>(
|
|
|
44
74
|
(acc, feature) => feature.getDefaultConfig?.(acc, treeInstance) ?? acc,
|
|
45
75
|
initialConfig
|
|
46
76
|
) as TreeConfig<T>;
|
|
77
|
+
const stateHandlerNames = additionalFeatures.reduce(
|
|
78
|
+
(acc, feature) => ({ ...acc, ...feature.stateHandlerNames }),
|
|
79
|
+
{} as Record<string, string>
|
|
80
|
+
);
|
|
47
81
|
|
|
48
82
|
let treeElement: HTMLElement | undefined | null;
|
|
49
83
|
const treeDataRef: { current: any } = { current: {} };
|
|
@@ -109,10 +143,14 @@ export const createTree = <T>(
|
|
|
109
143
|
...prev,
|
|
110
144
|
getState: () => state,
|
|
111
145
|
setState: (updater) => {
|
|
112
|
-
|
|
146
|
+
// Not necessary, since I think the subupdate below keeps the state fresh anyways?
|
|
147
|
+
// state = typeof updater === "function" ? updater(state) : updater;
|
|
113
148
|
config.setState?.(state);
|
|
114
|
-
|
|
115
|
-
|
|
149
|
+
},
|
|
150
|
+
applySubStateUpdate: (stateName, updater) => {
|
|
151
|
+
state[stateName] =
|
|
152
|
+
typeof updater === "function" ? updater(state[stateName]) : updater;
|
|
153
|
+
config[stateHandlerNames[stateName]]!(state[stateName]);
|
|
116
154
|
},
|
|
117
155
|
rebuildTree: () => {
|
|
118
156
|
rebuildItemMeta(mainFeature);
|
|
@@ -124,11 +162,7 @@ export const createTree = <T>(
|
|
|
124
162
|
|
|
125
163
|
if (config.state) {
|
|
126
164
|
state = { ...state, ...config.state };
|
|
127
|
-
eachFeature((feature) => feature.setState?.(treeInstance));
|
|
128
165
|
}
|
|
129
|
-
|
|
130
|
-
eachFeature((feature) => feature.onConfigChange?.(treeInstance));
|
|
131
|
-
eachFeature((feature) => feature.onStateOrConfigChange?.(treeInstance));
|
|
132
166
|
},
|
|
133
167
|
getItemInstance: (itemId) => itemInstancesMap[itemId],
|
|
134
168
|
getItems: () => itemInstances,
|
|
@@ -178,7 +212,6 @@ export const createTree = <T>(
|
|
|
178
212
|
}),
|
|
179
213
|
};
|
|
180
214
|
|
|
181
|
-
// todo sort features
|
|
182
215
|
const features = [mainFeature, ...additionalFeatures];
|
|
183
216
|
|
|
184
217
|
for (const feature of features) {
|
|
@@ -10,7 +10,6 @@ export const asyncDataLoaderFeature: FeatureImplementation<
|
|
|
10
10
|
MainFeatureDef | TreeFeatureDef<any> | AsyncDataLoaderFeatureDef<any>
|
|
11
11
|
> = {
|
|
12
12
|
key: "async-data-loader",
|
|
13
|
-
dependingFeatures: ["main"],
|
|
14
13
|
|
|
15
14
|
getInitialState: (initialState) => ({
|
|
16
15
|
loadingItems: [],
|
|
@@ -22,6 +21,10 @@ export const asyncDataLoaderFeature: FeatureImplementation<
|
|
|
22
21
|
...defaultConfig,
|
|
23
22
|
}),
|
|
24
23
|
|
|
24
|
+
stateHandlerNames: {
|
|
25
|
+
loadingItems: "setLoadingItems",
|
|
26
|
+
},
|
|
27
|
+
|
|
25
28
|
createTreeInstance: (prev, instance) => ({
|
|
26
29
|
...prev,
|
|
27
30
|
|
|
@@ -36,11 +39,14 @@ export const asyncDataLoaderFeature: FeatureImplementation<
|
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
if (!instance.getState().loadingItems.includes(itemId)) {
|
|
39
|
-
|
|
42
|
+
instance.applySubStateUpdate("loadingItems", (loadingItems) => [
|
|
43
|
+
...loadingItems,
|
|
44
|
+
itemId,
|
|
45
|
+
]);
|
|
40
46
|
config.asyncDataLoader?.getItem(itemId).then((item) => {
|
|
41
47
|
dataRef.current.itemData[itemId] = item;
|
|
42
48
|
config.onLoadedItem?.(itemId, item);
|
|
43
|
-
|
|
49
|
+
instance.applySubStateUpdate("loadingItems", (loadingItems) =>
|
|
44
50
|
loadingItems.filter((id) => id !== itemId)
|
|
45
51
|
);
|
|
46
52
|
});
|
|
@@ -62,7 +68,10 @@ export const asyncDataLoaderFeature: FeatureImplementation<
|
|
|
62
68
|
return [];
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
|
|
71
|
+
instance.applySubStateUpdate("loadingItems", (loadingItems) => [
|
|
72
|
+
...loadingItems,
|
|
73
|
+
itemId,
|
|
74
|
+
]);
|
|
66
75
|
|
|
67
76
|
if (config.asyncDataLoader?.getChildrenWithData) {
|
|
68
77
|
config.asyncDataLoader?.getChildrenWithData(itemId).then((children) => {
|
|
@@ -73,7 +82,7 @@ export const asyncDataLoaderFeature: FeatureImplementation<
|
|
|
73
82
|
const childrenIds = children.map(({ id }) => id);
|
|
74
83
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
75
84
|
config.onLoadedChildren?.(itemId, childrenIds);
|
|
76
|
-
|
|
85
|
+
instance.applySubStateUpdate("loadingItems", (loadingItems) =>
|
|
77
86
|
loadingItems.filter((id) => id !== itemId)
|
|
78
87
|
);
|
|
79
88
|
instance.rebuildTree();
|
|
@@ -82,7 +91,7 @@ export const asyncDataLoaderFeature: FeatureImplementation<
|
|
|
82
91
|
config.asyncDataLoader?.getChildren(itemId).then((childrenIds) => {
|
|
83
92
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
84
93
|
config.onLoadedChildren?.(itemId, childrenIds);
|
|
85
|
-
|
|
94
|
+
instance.applySubStateUpdate("loadingItems", (loadingItems) =>
|
|
86
95
|
loadingItems.filter((id) => id !== itemId)
|
|
87
96
|
);
|
|
88
97
|
instance.rebuildTree();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FeatureDefs, FeatureImplementation } from "../../types/core";
|
|
2
|
-
import { DndDataRef, DragAndDropFeatureDef } from "./types";
|
|
2
|
+
import { DndDataRef, DragAndDropFeatureDef, DragLineData } from "./types";
|
|
3
3
|
import { canDrop, getDragCode, getDropTarget } from "./utils";
|
|
4
4
|
import { makeStateUpdater } from "../../utils";
|
|
5
5
|
|
|
@@ -9,20 +9,64 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
9
9
|
FeatureDefs<any>
|
|
10
10
|
> = {
|
|
11
11
|
key: "dragAndDrop",
|
|
12
|
-
|
|
12
|
+
deps: ["selection"],
|
|
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
|
|
|
23
28
|
getDropTarget: () => {
|
|
24
29
|
return tree.getState().dnd?.dragTarget ?? null;
|
|
25
30
|
},
|
|
31
|
+
|
|
32
|
+
getDragLineData: (): DragLineData | null => {
|
|
33
|
+
const target = tree.getDropTarget();
|
|
34
|
+
const intend = (target?.item.getItemMeta().level ?? 0) + 1;
|
|
35
|
+
|
|
36
|
+
if (!target || target.childIndex === null) return null;
|
|
37
|
+
|
|
38
|
+
const children = target.item.getChildren();
|
|
39
|
+
|
|
40
|
+
if (target.childIndex === children.length) {
|
|
41
|
+
const bb = children[target.childIndex - 1]
|
|
42
|
+
?.getElement()
|
|
43
|
+
?.getBoundingClientRect();
|
|
44
|
+
|
|
45
|
+
if (bb) {
|
|
46
|
+
return {
|
|
47
|
+
intend,
|
|
48
|
+
top: bb.bottom,
|
|
49
|
+
left: bb.left,
|
|
50
|
+
right: bb.right,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const bb = children[target.childIndex]
|
|
56
|
+
?.getElement()
|
|
57
|
+
?.getBoundingClientRect();
|
|
58
|
+
|
|
59
|
+
if (bb) {
|
|
60
|
+
return {
|
|
61
|
+
intend,
|
|
62
|
+
top: bb.top,
|
|
63
|
+
left: bb.left,
|
|
64
|
+
right: bb.right,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
},
|
|
26
70
|
}),
|
|
27
71
|
|
|
28
72
|
createItemInstance: (prev, item, tree) => ({
|
|
@@ -52,7 +96,7 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
52
96
|
e.dataTransfer?.setData(format, data);
|
|
53
97
|
}
|
|
54
98
|
|
|
55
|
-
tree.
|
|
99
|
+
tree.applySubStateUpdate("dnd", {
|
|
56
100
|
draggedItems: items,
|
|
57
101
|
draggingOverItem: tree.getFocusedItem(),
|
|
58
102
|
});
|
|
@@ -82,7 +126,7 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
82
126
|
|
|
83
127
|
dataRef.current.lastDragCode = nextDragCode;
|
|
84
128
|
|
|
85
|
-
tree.
|
|
129
|
+
tree.applySubStateUpdate("dnd", (state) => ({
|
|
86
130
|
...state,
|
|
87
131
|
dragTarget: target,
|
|
88
132
|
draggingOverItem: item,
|
|
@@ -92,13 +136,24 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
92
136
|
onDragLeave: item.getMemoizedProp("dnd/onDragLeave", () => () => {
|
|
93
137
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
94
138
|
dataRef.current.lastDragCode = "no-drag";
|
|
95
|
-
tree.
|
|
139
|
+
tree.applySubStateUpdate("dnd", (state) => ({
|
|
96
140
|
...state,
|
|
97
141
|
draggingOverItem: undefined,
|
|
98
142
|
dragTarget: undefined,
|
|
99
143
|
}));
|
|
100
144
|
}),
|
|
101
145
|
|
|
146
|
+
onDragEnd: item.getMemoizedProp("dnd/onDragEnd", () => (e) => {
|
|
147
|
+
const draggedItems = tree.getState().dnd?.draggedItems;
|
|
148
|
+
tree.applySubStateUpdate("dnd", null);
|
|
149
|
+
|
|
150
|
+
if (e.dataTransfer.dropEffect === "none" || !draggedItems) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
tree.getConfig().onCompleteForeignDrop?.(draggedItems);
|
|
155
|
+
}),
|
|
156
|
+
|
|
102
157
|
onDrop: item.getMemoizedProp("dnd/onDrop", () => (e) => {
|
|
103
158
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
104
159
|
const target = getDropTarget(e, item, tree);
|
|
@@ -112,7 +167,7 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
112
167
|
const draggedItems = tree.getState().dnd?.draggedItems;
|
|
113
168
|
|
|
114
169
|
dataRef.current.lastDragCode = undefined;
|
|
115
|
-
tree.
|
|
170
|
+
tree.applySubStateUpdate("dnd", null);
|
|
116
171
|
|
|
117
172
|
if (draggedItems) {
|
|
118
173
|
config.onDrop?.(draggedItems, target);
|
|
@@ -131,19 +186,25 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
131
186
|
isDropTargetAbove: () => {
|
|
132
187
|
const target = tree.getDropTarget();
|
|
133
188
|
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
189
|
+
if (
|
|
190
|
+
!target ||
|
|
191
|
+
target.childIndex === null ||
|
|
192
|
+
target.item !== item.getParent()
|
|
193
|
+
)
|
|
194
|
+
return false;
|
|
195
|
+
return target.childIndex === item.getItemMeta().posInSet;
|
|
138
196
|
},
|
|
139
197
|
|
|
140
198
|
isDropTargetBelow: () => {
|
|
141
199
|
const target = tree.getDropTarget();
|
|
142
200
|
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
201
|
+
if (
|
|
202
|
+
!target ||
|
|
203
|
+
target.childIndex === null ||
|
|
204
|
+
target.item !== item.getParent()
|
|
205
|
+
)
|
|
206
|
+
return false;
|
|
207
|
+
return target.childIndex - 1 === item.getItemMeta().posInSet;
|
|
147
208
|
},
|
|
148
209
|
|
|
149
210
|
isDraggingOver: () => {
|