@headless-tree/core 0.0.14 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/lib/cjs/core/create-tree.js +13 -4
- package/lib/cjs/features/async-data-loader/feature.js +73 -48
- package/lib/cjs/features/async-data-loader/types.d.ts +17 -14
- package/lib/cjs/features/drag-and-drop/feature.js +98 -93
- package/lib/cjs/features/drag-and-drop/types.d.ts +17 -29
- package/lib/cjs/features/drag-and-drop/types.js +7 -7
- package/lib/cjs/features/drag-and-drop/utils.d.ts +25 -3
- package/lib/cjs/features/drag-and-drop/utils.js +51 -51
- package/lib/cjs/features/expand-all/feature.js +26 -3
- package/lib/cjs/features/expand-all/types.d.ts +3 -1
- package/lib/cjs/features/hotkeys-core/feature.js +7 -3
- package/lib/cjs/features/hotkeys-core/types.d.ts +4 -5
- package/lib/cjs/features/keyboard-drag-and-drop/feature.d.ts +2 -0
- package/lib/cjs/features/keyboard-drag-and-drop/feature.js +206 -0
- package/lib/cjs/features/keyboard-drag-and-drop/types.d.ts +27 -0
- package/lib/cjs/features/keyboard-drag-and-drop/types.js +11 -0
- package/lib/cjs/features/prop-memoization/feature.js +33 -11
- package/lib/cjs/features/prop-memoization/types.d.ts +8 -3
- package/lib/cjs/features/renaming/feature.js +1 -1
- package/lib/cjs/features/search/feature.js +2 -0
- package/lib/cjs/features/search/types.d.ts +2 -2
- package/lib/cjs/features/selection/feature.js +4 -4
- package/lib/cjs/features/selection/types.d.ts +1 -1
- package/lib/cjs/features/sync-data-loader/feature.js +31 -5
- package/lib/cjs/features/sync-data-loader/types.d.ts +5 -5
- package/lib/cjs/features/tree/feature.js +4 -9
- package/lib/cjs/features/tree/types.d.ts +7 -5
- package/lib/cjs/index.d.ts +2 -0
- package/lib/cjs/index.js +2 -0
- package/lib/cjs/mddocs-entry.d.ts +10 -0
- package/lib/cjs/test-utils/test-tree-do.d.ts +2 -2
- package/lib/cjs/test-utils/test-tree-do.js +19 -6
- package/lib/cjs/test-utils/test-tree-expect.d.ts +5 -3
- package/lib/cjs/test-utils/test-tree-expect.js +3 -0
- package/lib/cjs/test-utils/test-tree.d.ts +2 -1
- package/lib/cjs/test-utils/test-tree.js +24 -21
- package/lib/cjs/types/core.d.ts +2 -1
- package/lib/cjs/utilities/create-on-drop-handler.d.ts +2 -2
- package/lib/cjs/utilities/create-on-drop-handler.js +13 -4
- package/lib/cjs/utilities/insert-items-at-target.d.ts +2 -2
- package/lib/cjs/utilities/insert-items-at-target.js +21 -12
- package/lib/cjs/utilities/remove-items-from-parents.d.ts +1 -1
- package/lib/cjs/utilities/remove-items-from-parents.js +12 -3
- package/lib/esm/core/create-tree.js +13 -4
- package/lib/esm/features/async-data-loader/feature.js +73 -48
- package/lib/esm/features/async-data-loader/types.d.ts +17 -14
- package/lib/esm/features/drag-and-drop/feature.js +99 -94
- package/lib/esm/features/drag-and-drop/types.d.ts +17 -29
- package/lib/esm/features/drag-and-drop/types.js +6 -6
- package/lib/esm/features/drag-and-drop/utils.d.ts +25 -3
- package/lib/esm/features/drag-and-drop/utils.js +45 -49
- package/lib/esm/features/expand-all/feature.js +26 -3
- package/lib/esm/features/expand-all/types.d.ts +3 -1
- package/lib/esm/features/hotkeys-core/feature.js +7 -3
- package/lib/esm/features/hotkeys-core/types.d.ts +4 -5
- package/lib/esm/features/keyboard-drag-and-drop/feature.d.ts +2 -0
- package/lib/esm/features/keyboard-drag-and-drop/feature.js +203 -0
- package/lib/esm/features/keyboard-drag-and-drop/types.d.ts +27 -0
- package/lib/esm/features/keyboard-drag-and-drop/types.js +8 -0
- package/lib/esm/features/prop-memoization/feature.js +33 -11
- package/lib/esm/features/prop-memoization/types.d.ts +8 -3
- package/lib/esm/features/renaming/feature.js +1 -1
- package/lib/esm/features/search/feature.js +2 -0
- package/lib/esm/features/search/types.d.ts +2 -2
- package/lib/esm/features/selection/feature.js +4 -4
- package/lib/esm/features/selection/types.d.ts +1 -1
- package/lib/esm/features/sync-data-loader/feature.js +31 -5
- package/lib/esm/features/sync-data-loader/types.d.ts +5 -5
- package/lib/esm/features/tree/feature.js +4 -9
- package/lib/esm/features/tree/types.d.ts +7 -5
- package/lib/esm/index.d.ts +2 -0
- package/lib/esm/index.js +2 -0
- package/lib/esm/mddocs-entry.d.ts +10 -0
- package/lib/esm/test-utils/test-tree-do.d.ts +2 -2
- package/lib/esm/test-utils/test-tree-do.js +19 -6
- package/lib/esm/test-utils/test-tree-expect.d.ts +5 -3
- package/lib/esm/test-utils/test-tree-expect.js +3 -0
- package/lib/esm/test-utils/test-tree.d.ts +2 -1
- package/lib/esm/test-utils/test-tree.js +24 -21
- package/lib/esm/types/core.d.ts +2 -1
- package/lib/esm/utilities/create-on-drop-handler.d.ts +2 -2
- package/lib/esm/utilities/create-on-drop-handler.js +13 -4
- package/lib/esm/utilities/insert-items-at-target.d.ts +2 -2
- package/lib/esm/utilities/insert-items-at-target.js +21 -12
- package/lib/esm/utilities/remove-items-from-parents.d.ts +1 -1
- package/lib/esm/utilities/remove-items-from-parents.js +12 -3
- package/package.json +2 -2
- package/src/core/core.spec.ts +31 -0
- package/src/core/create-tree.ts +15 -5
- package/src/features/async-data-loader/async-data-loader.spec.ts +10 -6
- package/src/features/async-data-loader/feature.ts +76 -48
- package/src/features/async-data-loader/types.ts +18 -11
- package/src/features/drag-and-drop/drag-and-drop.spec.ts +75 -89
- package/src/features/drag-and-drop/feature.ts +26 -22
- package/src/features/drag-and-drop/types.ts +23 -35
- package/src/features/drag-and-drop/utils.ts +70 -57
- package/src/features/expand-all/feature.ts +29 -5
- package/src/features/expand-all/types.ts +3 -1
- package/src/features/hotkeys-core/feature.ts +4 -0
- package/src/features/hotkeys-core/types.ts +4 -13
- package/src/features/keyboard-drag-and-drop/feature.ts +255 -0
- package/src/features/keyboard-drag-and-drop/keyboard-drag-and-drop.spec.ts +402 -0
- package/src/features/keyboard-drag-and-drop/types.ts +30 -0
- package/src/features/prop-memoization/feature.ts +27 -8
- package/src/features/prop-memoization/prop-memoization.spec.ts +2 -2
- package/src/features/prop-memoization/types.ts +8 -3
- package/src/features/renaming/feature.ts +8 -2
- package/src/features/search/feature.ts +2 -0
- package/src/features/search/types.ts +2 -2
- package/src/features/selection/feature.ts +4 -4
- package/src/features/selection/types.ts +1 -1
- package/src/features/sync-data-loader/feature.ts +26 -7
- package/src/features/sync-data-loader/types.ts +5 -5
- package/src/features/tree/feature.ts +8 -13
- package/src/features/tree/types.ts +7 -5
- package/src/index.ts +2 -0
- package/src/mddocs-entry.ts +16 -0
- package/src/test-utils/test-tree-do.ts +3 -3
- package/src/test-utils/test-tree-expect.ts +7 -2
- package/src/test-utils/test-tree.ts +26 -22
- package/src/types/core.ts +2 -0
- package/src/utilities/create-on-drop-handler.ts +4 -4
- package/src/utilities/insert-items-at-target.ts +18 -14
- package/src/utilities/remove-items-from-parents.ts +6 -3
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { ItemInstance } from "../types/core";
|
|
2
|
-
import {
|
|
3
|
-
export declare const insertItemsAtTarget: <T>(itemIds: string[], target:
|
|
2
|
+
import { DragTarget } from "../features/drag-and-drop/types";
|
|
3
|
+
export declare const insertItemsAtTarget: <T>(itemIds: string[], target: DragTarget<T>, onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => Promise<void> | void) => Promise<void>;
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
export const insertItemsAtTarget = (itemIds, target, onChangeChildren) => __awaiter(void 0, void 0, void 0, function* () {
|
|
11
|
+
yield target.item.getTree().waitForItemChildrenLoaded(target.item.getId());
|
|
12
|
+
const oldChildrenIds = target.item
|
|
13
|
+
.getTree()
|
|
14
|
+
.retrieveChildrenIds(target.item.getId());
|
|
2
15
|
// add moved items to new common parent, if dropped onto parent
|
|
3
|
-
if (
|
|
4
|
-
const newChildren = [
|
|
5
|
-
|
|
6
|
-
...itemIds,
|
|
7
|
-
];
|
|
8
|
-
onChangeChildren(target.item, newChildren);
|
|
16
|
+
if (!("childIndex" in target)) {
|
|
17
|
+
const newChildren = [...oldChildrenIds, ...itemIds];
|
|
18
|
+
yield onChangeChildren(target.item, newChildren);
|
|
9
19
|
if (target.item && "updateCachedChildrenIds" in target.item) {
|
|
10
20
|
target.item.updateCachedChildrenIds(newChildren);
|
|
11
21
|
}
|
|
@@ -13,15 +23,14 @@ export const insertItemsAtTarget = (itemIds, target, onChangeChildren) => {
|
|
|
13
23
|
return;
|
|
14
24
|
}
|
|
15
25
|
// add moved items to new common parent, if dropped between siblings
|
|
16
|
-
const oldChildren = target.item.getChildren();
|
|
17
26
|
const newChildren = [
|
|
18
|
-
...
|
|
27
|
+
...oldChildrenIds.slice(0, target.insertionIndex),
|
|
19
28
|
...itemIds,
|
|
20
|
-
...
|
|
29
|
+
...oldChildrenIds.slice(target.insertionIndex),
|
|
21
30
|
];
|
|
22
|
-
onChangeChildren(target.item, newChildren);
|
|
31
|
+
yield onChangeChildren(target.item, newChildren);
|
|
23
32
|
if (target.item && "updateCachedChildrenIds" in target.item) {
|
|
24
33
|
target.item.updateCachedChildrenIds(newChildren);
|
|
25
34
|
}
|
|
26
35
|
target.item.getTree().rebuildTree();
|
|
27
|
-
};
|
|
36
|
+
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { ItemInstance } from "../types/core";
|
|
2
|
-
export declare const removeItemsFromParents: <T>(movedItems: ItemInstance<T>[], onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => void) => void
|
|
2
|
+
export declare const removeItemsFromParents: <T>(movedItems: ItemInstance<T>[], onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => void | Promise<void>) => Promise<void>;
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
export const removeItemsFromParents = (movedItems, onChangeChildren) => __awaiter(void 0, void 0, void 0, function* () {
|
|
2
11
|
const movedItemsIds = movedItems.map((item) => item.getId());
|
|
3
12
|
const uniqueParents = [
|
|
4
13
|
...new Set(movedItems.map((item) => item.getParent())),
|
|
@@ -9,11 +18,11 @@ export const removeItemsFromParents = (movedItems, onChangeChildren) => {
|
|
|
9
18
|
const newChildren = siblings
|
|
10
19
|
.filter((sibling) => !movedItemsIds.includes(sibling.getId()))
|
|
11
20
|
.map((i) => i.getId());
|
|
12
|
-
onChangeChildren(parent, newChildren);
|
|
21
|
+
yield onChangeChildren(parent, newChildren);
|
|
13
22
|
if (parent && "updateCachedChildrenIds" in parent) {
|
|
14
23
|
parent === null || parent === void 0 ? void 0 : parent.updateCachedChildrenIds(newChildren);
|
|
15
24
|
}
|
|
16
25
|
}
|
|
17
26
|
}
|
|
18
27
|
movedItems[0].getTree().rebuildTree();
|
|
19
|
-
};
|
|
28
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@headless-tree/core",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/cjs/index.js",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"jsdom": "^26.0.0",
|
|
24
|
-
"typescript": "^5.7.
|
|
24
|
+
"typescript": "^5.7.2",
|
|
25
25
|
"vitest": "^3.0.3"
|
|
26
26
|
}
|
|
27
27
|
}
|
package/src/core/core.spec.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { TestTree } from "../test-utils/test-tree";
|
|
3
|
+
import { buildStaticInstance } from "./build-static-instance";
|
|
3
4
|
|
|
4
5
|
declare module "../types/core" {
|
|
5
6
|
export interface TreeInstance<T> {
|
|
@@ -17,6 +18,36 @@ const factory = TestTree.default({});
|
|
|
17
18
|
|
|
18
19
|
describe("core-feature/prop-memoization", () => {
|
|
19
20
|
factory.forSuits((tree) => {
|
|
21
|
+
describe("rebuilds item instance", () => {
|
|
22
|
+
it("rebuilds when explicitly invoked", async () => {
|
|
23
|
+
const instanceBuilder = vi.fn().mockImplementation(buildStaticInstance);
|
|
24
|
+
const testTree = await tree
|
|
25
|
+
.with({ instanceBuilder })
|
|
26
|
+
.createTestCaseTree();
|
|
27
|
+
expect(instanceBuilder).toBeCalled();
|
|
28
|
+
instanceBuilder.mockClear();
|
|
29
|
+
testTree.instance.rebuildTree();
|
|
30
|
+
// if tree structure doesnt mutate, only root tree item is rebuilt actually
|
|
31
|
+
expect(instanceBuilder).toBeCalled();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("rebuilds when config changes with new expanded items", async () => {
|
|
35
|
+
const instanceBuilder = vi.fn().mockImplementation(buildStaticInstance);
|
|
36
|
+
const testTree = await tree
|
|
37
|
+
.with({ instanceBuilder })
|
|
38
|
+
.createTestCaseTree();
|
|
39
|
+
expect(instanceBuilder).toBeCalled();
|
|
40
|
+
instanceBuilder.mockClear();
|
|
41
|
+
testTree.instance.setConfig((oldCfg) => ({
|
|
42
|
+
...oldCfg,
|
|
43
|
+
state: {
|
|
44
|
+
expandedItems: ["x4"],
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
expect(instanceBuilder).toBeCalled();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
20
51
|
describe("calls prev in correct order", () => {
|
|
21
52
|
it("tree instance with overwrite marks, order 1", async () => {
|
|
22
53
|
const testTree = await tree
|
package/src/core/create-tree.ts
CHANGED
|
@@ -137,6 +137,7 @@ export const createTree = <T>(
|
|
|
137
137
|
// Not necessary, since I think the subupdate below keeps the state fresh anyways?
|
|
138
138
|
// state = typeof updater === "function" ? updater(state) : updater;
|
|
139
139
|
config.setState?.(state); // TODO this cant be right... This doesnt allow external state updates
|
|
140
|
+
// TODO this is never used, remove
|
|
140
141
|
},
|
|
141
142
|
applySubStateUpdate: <K extends keyof TreeState<any>>(
|
|
142
143
|
{},
|
|
@@ -157,10 +158,20 @@ export const createTree = <T>(
|
|
|
157
158
|
},
|
|
158
159
|
getConfig: () => config,
|
|
159
160
|
setConfig: (_, updater) => {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
const newConfig =
|
|
162
|
+
typeof updater === "function" ? updater(config) : updater;
|
|
163
|
+
const hasChangedExpandedItems =
|
|
164
|
+
newConfig.state?.expandedItems &&
|
|
165
|
+
newConfig.state?.expandedItems !== state.expandedItems;
|
|
166
|
+
config = newConfig;
|
|
167
|
+
|
|
168
|
+
if (newConfig.state) {
|
|
169
|
+
state = { ...state, ...newConfig.state };
|
|
170
|
+
}
|
|
171
|
+
if (hasChangedExpandedItems) {
|
|
172
|
+
// if expanded items where changed from the outside
|
|
173
|
+
rebuildItemMeta();
|
|
174
|
+
config.setState?.(state);
|
|
164
175
|
}
|
|
165
176
|
},
|
|
166
177
|
getItemInstance: ({}, itemId) => itemInstancesMap[itemId],
|
|
@@ -186,7 +197,6 @@ export const createTree = <T>(
|
|
|
186
197
|
getHotkeyPresets: () => hotkeyPresets,
|
|
187
198
|
},
|
|
188
199
|
itemInstance: {
|
|
189
|
-
// TODO just change to a getRef method that memoizes, maybe as part of getProps
|
|
190
200
|
registerElement: ({ itemId, item }, element) => {
|
|
191
201
|
if (itemElementsMap[itemId] === element) {
|
|
192
202
|
return;
|
|
@@ -41,11 +41,17 @@ describe("core-feature/selections", () => {
|
|
|
41
41
|
|
|
42
42
|
describe("calls handlers", () => {
|
|
43
43
|
it("updates setLoadingItems", async () => {
|
|
44
|
-
const
|
|
44
|
+
const setLoadingItemChildrens = tree.mockedHandler(
|
|
45
|
+
"setLoadingItemChildrens",
|
|
46
|
+
);
|
|
47
|
+
const setLoadingItemData = tree.mockedHandler("setLoadingItemData");
|
|
45
48
|
tree.do.selectItem("x12");
|
|
46
|
-
expect(
|
|
49
|
+
expect(setLoadingItemChildrens).toHaveBeenCalledWith(["x12"]);
|
|
50
|
+
expect(setLoadingItemData).not.toHaveBeenCalled();
|
|
47
51
|
await tree.resolveAsyncVisibleItems();
|
|
48
|
-
expect(
|
|
52
|
+
expect(setLoadingItemChildrens).toHaveBeenCalledWith([]);
|
|
53
|
+
expect(setLoadingItemData).toHaveBeenCalledWith(["x121"]);
|
|
54
|
+
expect(setLoadingItemData).toHaveBeenCalledWith([]);
|
|
49
55
|
});
|
|
50
56
|
|
|
51
57
|
it("calls onLoadedItem", async () => {
|
|
@@ -76,7 +82,7 @@ describe("core-feature/selections", () => {
|
|
|
76
82
|
`${id}3`,
|
|
77
83
|
`${id}4`,
|
|
78
84
|
]);
|
|
79
|
-
const suiteTree = tree.with({
|
|
85
|
+
const suiteTree = tree.with({ dataLoader: { getItem, getChildren } });
|
|
80
86
|
suiteTree.resetBeforeEach();
|
|
81
87
|
|
|
82
88
|
it("invalidates item data on item instance", async () => {
|
|
@@ -119,6 +125,4 @@ describe("core-feature/selections", () => {
|
|
|
119
125
|
expect(getChildren).toHaveBeenCalledTimes(1);
|
|
120
126
|
});
|
|
121
127
|
});
|
|
122
|
-
|
|
123
|
-
describe.todo("getChildrenWithData");
|
|
124
128
|
});
|
|
@@ -1,28 +1,57 @@
|
|
|
1
1
|
import { FeatureImplementation } from "../../types/core";
|
|
2
|
-
import {
|
|
2
|
+
import { AsyncDataLoaderDataRef } from "./types";
|
|
3
3
|
import { makeStateUpdater } from "../../utils";
|
|
4
4
|
|
|
5
5
|
export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
6
6
|
key: "async-data-loader",
|
|
7
7
|
|
|
8
8
|
getInitialState: (initialState) => ({
|
|
9
|
-
|
|
9
|
+
loadingItemData: [],
|
|
10
|
+
loadingItemChildrens: [],
|
|
10
11
|
...initialState,
|
|
11
12
|
}),
|
|
12
13
|
|
|
13
14
|
getDefaultConfig: (defaultConfig, tree) => ({
|
|
14
|
-
|
|
15
|
+
setLoadingItemData: makeStateUpdater("loadingItemData", tree),
|
|
16
|
+
setLoadingItemChildrens: makeStateUpdater("loadingItemChildrens", tree),
|
|
15
17
|
...defaultConfig,
|
|
16
18
|
}),
|
|
17
19
|
|
|
18
20
|
stateHandlerNames: {
|
|
19
|
-
|
|
21
|
+
loadingItemData: "setLoadingItemData",
|
|
22
|
+
loadingItemChildrens: "setLoadingItemChildrens",
|
|
20
23
|
},
|
|
21
24
|
|
|
22
25
|
treeInstance: {
|
|
26
|
+
waitForItemDataLoaded: async ({ tree }, itemId) => {
|
|
27
|
+
tree.retrieveItemData(itemId);
|
|
28
|
+
if (!tree.getState().loadingItemData.includes(itemId)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
await new Promise<void>((resolve) => {
|
|
32
|
+
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
33
|
+
dataRef.current.awaitingItemDataLoading ??= {};
|
|
34
|
+
dataRef.current.awaitingItemDataLoading[itemId] ??= [];
|
|
35
|
+
dataRef.current.awaitingItemDataLoading[itemId].push(resolve);
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
waitForItemChildrenLoaded: async ({ tree }, itemId) => {
|
|
40
|
+
tree.retrieveChildrenIds(itemId);
|
|
41
|
+
if (!tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
await new Promise<void>((resolve) => {
|
|
45
|
+
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
46
|
+
dataRef.current.awaitingItemChildrensLoading ??= {};
|
|
47
|
+
dataRef.current.awaitingItemChildrensLoading[itemId] ??= [];
|
|
48
|
+
dataRef.current.awaitingItemChildrensLoading[itemId].push(resolve);
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
|
|
23
52
|
retrieveItemData: ({ tree }, itemId) => {
|
|
24
53
|
const config = tree.getConfig();
|
|
25
|
-
const dataRef = tree.getDataRef<
|
|
54
|
+
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
26
55
|
dataRef.current.itemData ??= {};
|
|
27
56
|
dataRef.current.childrenIds ??= {};
|
|
28
57
|
|
|
@@ -30,18 +59,25 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
30
59
|
return dataRef.current.itemData[itemId];
|
|
31
60
|
}
|
|
32
61
|
|
|
33
|
-
if (!tree.getState().
|
|
34
|
-
tree.applySubStateUpdate("
|
|
35
|
-
...
|
|
62
|
+
if (!tree.getState().loadingItemData.includes(itemId)) {
|
|
63
|
+
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
64
|
+
...loadingItemData,
|
|
36
65
|
itemId,
|
|
37
66
|
]);
|
|
38
|
-
|
|
67
|
+
|
|
68
|
+
(async () => {
|
|
69
|
+
const item = await config.dataLoader.getItem(itemId);
|
|
39
70
|
dataRef.current.itemData[itemId] = item;
|
|
40
71
|
config.onLoadedItem?.(itemId, item);
|
|
41
|
-
tree.applySubStateUpdate("
|
|
42
|
-
|
|
72
|
+
tree.applySubStateUpdate("loadingItemData", (loadingItemData) =>
|
|
73
|
+
loadingItemData.filter((id) => id !== itemId),
|
|
43
74
|
);
|
|
44
|
-
|
|
75
|
+
|
|
76
|
+
dataRef.current.awaitingItemDataLoading?.[itemId].forEach((cb) =>
|
|
77
|
+
cb(),
|
|
78
|
+
);
|
|
79
|
+
delete dataRef.current.awaitingItemDataLoading?.[itemId];
|
|
80
|
+
})();
|
|
45
81
|
}
|
|
46
82
|
|
|
47
83
|
return config.createLoadingItemData?.() ?? null;
|
|
@@ -49,46 +85,37 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
49
85
|
|
|
50
86
|
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
51
87
|
const config = tree.getConfig();
|
|
52
|
-
const dataRef = tree.getDataRef<
|
|
53
|
-
dataRef.current.itemData ??= {};
|
|
88
|
+
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
54
89
|
dataRef.current.childrenIds ??= {};
|
|
55
90
|
if (dataRef.current.childrenIds[itemId]) {
|
|
56
91
|
return dataRef.current.childrenIds[itemId];
|
|
57
92
|
}
|
|
58
93
|
|
|
59
|
-
if (tree.getState().
|
|
94
|
+
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
60
95
|
return [];
|
|
61
96
|
}
|
|
62
97
|
|
|
63
|
-
tree.applySubStateUpdate(
|
|
64
|
-
|
|
65
|
-
itemId,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
config.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
85
|
-
config.onLoadedChildren?.(itemId, childrenIds);
|
|
86
|
-
tree.applySubStateUpdate("loadingItems", (loadingItems) =>
|
|
87
|
-
loadingItems.filter((id) => id !== itemId),
|
|
88
|
-
);
|
|
89
|
-
tree.rebuildTree();
|
|
90
|
-
});
|
|
91
|
-
}
|
|
98
|
+
tree.applySubStateUpdate(
|
|
99
|
+
"loadingItemChildrens",
|
|
100
|
+
(loadingItemChildrens) => [...loadingItemChildrens, itemId],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
(async () => {
|
|
104
|
+
const childrenIds = await config.dataLoader.getChildren(itemId);
|
|
105
|
+
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
106
|
+
config.onLoadedChildren?.(itemId, childrenIds);
|
|
107
|
+
tree.applySubStateUpdate(
|
|
108
|
+
"loadingItemChildrens",
|
|
109
|
+
(loadingItemChildrens) =>
|
|
110
|
+
loadingItemChildrens.filter((id) => id !== itemId),
|
|
111
|
+
);
|
|
112
|
+
tree.rebuildTree();
|
|
113
|
+
|
|
114
|
+
dataRef.current.awaitingItemChildrensLoading?.[itemId]?.forEach((cb) =>
|
|
115
|
+
cb(),
|
|
116
|
+
);
|
|
117
|
+
delete dataRef.current.awaitingItemChildrensLoading?.[itemId];
|
|
118
|
+
})();
|
|
92
119
|
|
|
93
120
|
return [];
|
|
94
121
|
},
|
|
@@ -96,19 +123,20 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
96
123
|
|
|
97
124
|
itemInstance: {
|
|
98
125
|
isLoading: ({ tree, item }) =>
|
|
99
|
-
tree.getState().
|
|
126
|
+
tree.getState().loadingItemData.includes(item.getItemMeta().itemId) ||
|
|
127
|
+
tree.getState().loadingItemChildrens.includes(item.getItemMeta().itemId),
|
|
100
128
|
invalidateItemData: ({ tree, itemId }) => {
|
|
101
|
-
const dataRef = tree.getDataRef<
|
|
129
|
+
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
102
130
|
delete dataRef.current.itemData?.[itemId];
|
|
103
131
|
tree.retrieveItemData(itemId);
|
|
104
132
|
},
|
|
105
133
|
invalidateChildrenIds: ({ tree, itemId }) => {
|
|
106
|
-
const dataRef = tree.getDataRef<
|
|
134
|
+
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
107
135
|
delete dataRef.current.childrenIds?.[itemId];
|
|
108
136
|
tree.retrieveChildrenIds(itemId);
|
|
109
137
|
},
|
|
110
138
|
updateCachedChildrenIds: ({ tree, itemId }, childrenIds) => {
|
|
111
|
-
const dataRef = tree.getDataRef<
|
|
139
|
+
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
112
140
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
113
141
|
tree.rebuildTree();
|
|
114
142
|
},
|
|
@@ -1,33 +1,40 @@
|
|
|
1
1
|
import { SetStateFn } from "../../types/core";
|
|
2
2
|
import { SyncDataLoaderFeatureDef } from "../sync-data-loader/types";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
getItem: (itemId: string) => Promise<T>;
|
|
6
|
-
getChildren: (itemId: string) => Promise<string[]>;
|
|
7
|
-
getChildrenWithData?: (itemId: string) => Promise<{ id: string; data: T }[]>;
|
|
8
|
-
};
|
|
4
|
+
type AwaitingLoaderCallbacks = Record<string, (() => void)[]>;
|
|
9
5
|
|
|
10
|
-
export
|
|
6
|
+
export interface AsyncDataLoaderDataRef<T = any> {
|
|
11
7
|
itemData: Record<string, T>;
|
|
12
8
|
childrenIds: Record<string, string[]>;
|
|
13
|
-
|
|
9
|
+
awaitingItemDataLoading: AwaitingLoaderCallbacks;
|
|
10
|
+
awaitingItemChildrensLoading: AwaitingLoaderCallbacks;
|
|
11
|
+
}
|
|
14
12
|
|
|
15
13
|
/**
|
|
16
14
|
* @category Async Data Loader/General
|
|
17
15
|
* */
|
|
18
16
|
export type AsyncDataLoaderFeatureDef<T> = {
|
|
19
17
|
state: {
|
|
20
|
-
|
|
18
|
+
loadingItemData: string[];
|
|
19
|
+
loadingItemChildrens: string[];
|
|
21
20
|
};
|
|
22
21
|
config: {
|
|
23
22
|
rootItemId: string;
|
|
23
|
+
|
|
24
|
+
/** Will be called when HT retrieves item data for an item whose item data is asynchronously being loaded.
|
|
25
|
+
* Can be used to create placeholder data to use for rendering the tree item while it is loaded. If not defined,
|
|
26
|
+
* the tree item data will be null. */
|
|
24
27
|
createLoadingItemData?: () => T;
|
|
25
|
-
|
|
28
|
+
|
|
29
|
+
setLoadingItemData?: SetStateFn<string[]>;
|
|
30
|
+
setLoadingItemChildrens?: SetStateFn<string[]>;
|
|
26
31
|
onLoadedItem?: (itemId: string, item: T) => void;
|
|
27
32
|
onLoadedChildren?: (itemId: string, childrenIds: string[]) => void;
|
|
28
|
-
asyncDataLoader?: AsyncTreeDataLoader<T>;
|
|
29
33
|
};
|
|
30
|
-
treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"]
|
|
34
|
+
treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"] & {
|
|
35
|
+
waitForItemDataLoaded: (itemId: string) => Promise<void>;
|
|
36
|
+
waitForItemChildrenLoaded: (itemId: string) => Promise<void>;
|
|
37
|
+
};
|
|
31
38
|
itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
|
|
32
39
|
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible */
|
|
33
40
|
invalidateItemData: () => void;
|