@headless-tree/core 0.0.0-20250509160455 → 0.0.0-20250509212452
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 +1 -1
- package/lib/cjs/core/create-tree.js +9 -0
- package/lib/cjs/features/async-data-loader/feature.js +19 -6
- package/lib/cjs/features/async-data-loader/types.d.ts +4 -0
- package/lib/cjs/features/checkboxes/feature.d.ts +2 -0
- package/lib/cjs/features/checkboxes/feature.js +128 -0
- package/lib/cjs/features/checkboxes/types.d.ts +26 -0
- package/lib/cjs/features/checkboxes/types.js +9 -0
- package/lib/cjs/features/main/types.d.ts +2 -0
- package/lib/cjs/features/sync-data-loader/feature.js +2 -0
- package/lib/cjs/features/sync-data-loader/types.d.ts +2 -2
- package/lib/cjs/index.d.ts +2 -0
- package/lib/cjs/index.js +2 -0
- package/lib/cjs/types/core.d.ts +2 -1
- package/lib/esm/core/create-tree.js +9 -0
- package/lib/esm/features/async-data-loader/feature.js +19 -6
- package/lib/esm/features/async-data-loader/types.d.ts +4 -0
- package/lib/esm/features/checkboxes/feature.d.ts +2 -0
- package/lib/esm/features/checkboxes/feature.js +125 -0
- package/lib/esm/features/checkboxes/types.d.ts +26 -0
- package/lib/esm/features/checkboxes/types.js +6 -0
- package/lib/esm/features/main/types.d.ts +2 -0
- package/lib/esm/features/sync-data-loader/feature.js +2 -0
- package/lib/esm/features/sync-data-loader/types.d.ts +2 -2
- package/lib/esm/index.d.ts +2 -0
- package/lib/esm/index.js +2 -0
- package/lib/esm/types/core.d.ts +2 -1
- package/package.json +1 -1
- package/src/core/create-tree.ts +13 -0
- package/src/features/async-data-loader/feature.ts +25 -6
- package/src/features/async-data-loader/types.ts +4 -0
- package/src/features/checkboxes/feature.ts +150 -0
- package/src/features/checkboxes/types.ts +28 -0
- package/src/features/main/types.ts +2 -0
- package/src/features/sync-data-loader/feature.ts +3 -0
- package/src/features/sync-data-loader/types.ts +2 -2
- package/src/index.ts +2 -0
- package/src/types/core.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -100,6 +100,15 @@ const createTree = (initialConfig) => {
|
|
|
100
100
|
const externalStateSetter = config[stateHandlerNames[stateName]];
|
|
101
101
|
externalStateSetter === null || externalStateSetter === void 0 ? void 0 : externalStateSetter(state[stateName]);
|
|
102
102
|
},
|
|
103
|
+
buildItemInstance: ({}, itemId) => {
|
|
104
|
+
const [instance, finalizeInstance] = buildInstance(features, "itemInstance", (instance) => ({
|
|
105
|
+
item: instance,
|
|
106
|
+
tree: treeInstance,
|
|
107
|
+
itemId,
|
|
108
|
+
}));
|
|
109
|
+
finalizeInstance();
|
|
110
|
+
return instance;
|
|
111
|
+
},
|
|
103
112
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
104
113
|
rebuildTree: () => {
|
|
105
114
|
var _a;
|
|
@@ -29,14 +29,17 @@ const loadItemData = (tree, itemId) => __awaiter(void 0, void 0, void 0, functio
|
|
|
29
29
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => id !== itemId));
|
|
30
30
|
(_b = dataRef.current.awaitingItemDataLoading) === null || _b === void 0 ? void 0 : _b[itemId].forEach((cb) => cb());
|
|
31
31
|
(_c = dataRef.current.awaitingItemDataLoading) === null || _c === void 0 ? true : delete _c[itemId];
|
|
32
|
+
return item;
|
|
32
33
|
});
|
|
33
34
|
const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
35
|
var _a, _b, _c, _d, _e;
|
|
35
36
|
const config = tree.getConfig();
|
|
36
37
|
const dataRef = getDataRef(tree);
|
|
38
|
+
let childrenIds;
|
|
39
|
+
// TODO is folder check?
|
|
37
40
|
if ("getChildrenWithData" in config.dataLoader) {
|
|
38
41
|
const children = yield config.dataLoader.getChildrenWithData(itemId);
|
|
39
|
-
|
|
42
|
+
childrenIds = children.map((c) => c.id);
|
|
40
43
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
41
44
|
children.forEach(({ id, data }) => {
|
|
42
45
|
var _a, _b, _c;
|
|
@@ -50,7 +53,7 @@ const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, func
|
|
|
50
53
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => !childrenIds.includes(id)));
|
|
51
54
|
}
|
|
52
55
|
else {
|
|
53
|
-
|
|
56
|
+
childrenIds = yield config.dataLoader.getChildren(itemId);
|
|
54
57
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
55
58
|
(_b = config.onLoadedChildren) === null || _b === void 0 ? void 0 : _b.call(config, itemId, childrenIds);
|
|
56
59
|
tree.rebuildTree();
|
|
@@ -58,6 +61,7 @@ const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, func
|
|
|
58
61
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId));
|
|
59
62
|
(_d = (_c = dataRef.current.awaitingItemChildrensLoading) === null || _c === void 0 ? void 0 : _c[itemId]) === null || _d === void 0 ? void 0 : _d.forEach((cb) => cb());
|
|
60
63
|
(_e = dataRef.current.awaitingItemChildrensLoading) === null || _e === void 0 ? true : delete _e[itemId];
|
|
64
|
+
return childrenIds;
|
|
61
65
|
});
|
|
62
66
|
exports.asyncDataLoaderFeature = {
|
|
63
67
|
key: "async-data-loader",
|
|
@@ -83,6 +87,7 @@ exports.asyncDataLoaderFeature = {
|
|
|
83
87
|
});
|
|
84
88
|
}),
|
|
85
89
|
waitForItemChildrenLoaded: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
90
|
+
// TODO replace inner implementation with load() fns
|
|
86
91
|
tree.retrieveChildrenIds(itemId);
|
|
87
92
|
if (!tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
88
93
|
return;
|
|
@@ -96,14 +101,22 @@ exports.asyncDataLoaderFeature = {
|
|
|
96
101
|
dataRef.current.awaitingItemChildrensLoading[itemId].push(resolve);
|
|
97
102
|
});
|
|
98
103
|
}),
|
|
99
|
-
|
|
104
|
+
loadItemData: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
105
|
+
var _b;
|
|
106
|
+
return ((_b = getDataRef(tree).current.itemData[itemId]) !== null && _b !== void 0 ? _b : (yield loadItemData(tree, itemId)));
|
|
107
|
+
}),
|
|
108
|
+
loadChildrenIds: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
109
|
+
var _b;
|
|
110
|
+
return ((_b = getDataRef(tree).current.childrenIds[itemId]) !== null && _b !== void 0 ? _b : (yield loadChildrenIds(tree, itemId)));
|
|
111
|
+
}),
|
|
112
|
+
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
100
113
|
var _a, _b;
|
|
101
114
|
const config = tree.getConfig();
|
|
102
115
|
const dataRef = getDataRef(tree);
|
|
103
116
|
if (dataRef.current.itemData[itemId]) {
|
|
104
117
|
return dataRef.current.itemData[itemId];
|
|
105
118
|
}
|
|
106
|
-
if (!tree.getState().loadingItemData.includes(itemId)) {
|
|
119
|
+
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
107
120
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
108
121
|
...loadingItemData,
|
|
109
122
|
itemId,
|
|
@@ -112,12 +125,12 @@ exports.asyncDataLoaderFeature = {
|
|
|
112
125
|
}
|
|
113
126
|
return (_b = (_a = config.createLoadingItemData) === null || _a === void 0 ? void 0 : _a.call(config)) !== null && _b !== void 0 ? _b : null;
|
|
114
127
|
},
|
|
115
|
-
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
128
|
+
retrieveChildrenIds: ({ tree }, itemId, skipFetch = false) => {
|
|
116
129
|
const dataRef = getDataRef(tree);
|
|
117
130
|
if (dataRef.current.childrenIds[itemId]) {
|
|
118
131
|
return dataRef.current.childrenIds[itemId];
|
|
119
132
|
}
|
|
120
|
-
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
133
|
+
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
121
134
|
return [];
|
|
122
135
|
}
|
|
123
136
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [...loadingItemChildrens, itemId]);
|
|
@@ -27,8 +27,12 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
27
27
|
onLoadedChildren?: (itemId: string, childrenIds: string[]) => void;
|
|
28
28
|
};
|
|
29
29
|
treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"] & {
|
|
30
|
+
/** @deprecated use loadItemData instead */
|
|
30
31
|
waitForItemDataLoaded: (itemId: string) => Promise<void>;
|
|
32
|
+
/** @deprecated use loadChildrenIds instead */
|
|
31
33
|
waitForItemChildrenLoaded: (itemId: string) => Promise<void>;
|
|
34
|
+
loadItemData: (itemId: string) => Promise<T>;
|
|
35
|
+
loadChildrenIds: (itemId: string) => Promise<string[]>;
|
|
32
36
|
};
|
|
33
37
|
itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
|
|
34
38
|
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.checkboxesFeature = void 0;
|
|
13
|
+
const utils_1 = require("../../utils");
|
|
14
|
+
const types_1 = require("./types");
|
|
15
|
+
/*
|
|
16
|
+
* Cases for checking:
|
|
17
|
+
* - Check an unchecked item in an unchecked or indeterminate folder
|
|
18
|
+
* - Check an explicitly unchecked item in a checked folder
|
|
19
|
+
* - Check an unchecked folder in an unchecked or indeterminate folder
|
|
20
|
+
*
|
|
21
|
+
* Cases for unchecking:
|
|
22
|
+
* - Uncheck a checked item in an indeterminate folder
|
|
23
|
+
* - Uncheck an explicitly unchecked item in an checked folder
|
|
24
|
+
*/
|
|
25
|
+
const fetchAllDescendants = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
26
|
+
const children = yield tree.loadChildrenIds(itemId);
|
|
27
|
+
return [
|
|
28
|
+
itemId,
|
|
29
|
+
...(yield Promise.all(children.map((child) => fetchAllDescendants(tree, child)))).flat(),
|
|
30
|
+
];
|
|
31
|
+
});
|
|
32
|
+
const getAllLoadedDescendants = (tree, itemId) => {
|
|
33
|
+
const children = tree.retrieveChildrenIds(itemId, true);
|
|
34
|
+
return [
|
|
35
|
+
itemId,
|
|
36
|
+
...children.map((child) => getAllLoadedDescendants(tree, child)).flat(),
|
|
37
|
+
];
|
|
38
|
+
};
|
|
39
|
+
exports.checkboxesFeature = {
|
|
40
|
+
key: "checkboxes",
|
|
41
|
+
overwrites: ["selection"],
|
|
42
|
+
getInitialState: (initialState) => (Object.assign({ checkedItems: [] }, initialState)),
|
|
43
|
+
getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setCheckedItems: (0, utils_1.makeStateUpdater)("checkedItems", tree) }, defaultConfig)),
|
|
44
|
+
stateHandlerNames: {
|
|
45
|
+
checkedItems: "setCheckedItems",
|
|
46
|
+
},
|
|
47
|
+
treeInstance: {
|
|
48
|
+
setCheckedItems: ({ tree }, checkedItems) => {
|
|
49
|
+
tree.applySubStateUpdate("checkedItems", checkedItems);
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
itemInstance: {
|
|
53
|
+
getCheckboxProps: ({ item, itemId }) => {
|
|
54
|
+
const checkedState = item.getCheckedState();
|
|
55
|
+
// console.log("prop", itemId, checkedState);
|
|
56
|
+
return {
|
|
57
|
+
onChange: item.toggleCheckedState,
|
|
58
|
+
checked: checkedState === types_1.CheckedState.Checked,
|
|
59
|
+
ref: (r) => {
|
|
60
|
+
if (r) {
|
|
61
|
+
// console.log("ref", itemId, checkedState);
|
|
62
|
+
r.indeterminate = checkedState === types_1.CheckedState.Indeterminate;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
toggleCheckedState: (_a) => __awaiter(void 0, [_a], void 0, function* ({ item }) {
|
|
68
|
+
if (item.getCheckedState() === types_1.CheckedState.Checked) {
|
|
69
|
+
yield item.setUnchecked();
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
yield item.setChecked();
|
|
73
|
+
}
|
|
74
|
+
}),
|
|
75
|
+
getCheckedState: ({ item, tree, itemId }) => {
|
|
76
|
+
// TODO checkedcache
|
|
77
|
+
const { checkedItems } = tree.getState();
|
|
78
|
+
if (checkedItems.includes(itemId)) {
|
|
79
|
+
return types_1.CheckedState.Checked;
|
|
80
|
+
}
|
|
81
|
+
if (item.isFolder()) {
|
|
82
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
83
|
+
console.log("descendants of ", itemId, descendants);
|
|
84
|
+
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
85
|
+
return types_1.CheckedState.Checked;
|
|
86
|
+
}
|
|
87
|
+
if (descendants.some((d) => checkedItems.includes(d))) {
|
|
88
|
+
return types_1.CheckedState.Indeterminate;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// if (
|
|
92
|
+
// item.isFolder() &&
|
|
93
|
+
// checkedItems.some((checkedItem) =>
|
|
94
|
+
// tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
|
|
95
|
+
// )
|
|
96
|
+
// ) {
|
|
97
|
+
// // TODO for every descendent, not every checked item
|
|
98
|
+
// return checkedItems.every((checkedItem) =>
|
|
99
|
+
// tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
|
|
100
|
+
// )
|
|
101
|
+
// ? CheckedState.Checked
|
|
102
|
+
// : CheckedState.Indeterminate;
|
|
103
|
+
// }
|
|
104
|
+
return types_1.CheckedState.Unchecked;
|
|
105
|
+
},
|
|
106
|
+
setChecked: (_a) => __awaiter(void 0, [_a], void 0, function* ({ item, tree, itemId }) {
|
|
107
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
108
|
+
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const descendants = yield fetchAllDescendants(tree, itemId);
|
|
112
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
113
|
+
...items,
|
|
114
|
+
...descendants,
|
|
115
|
+
]);
|
|
116
|
+
}
|
|
117
|
+
}),
|
|
118
|
+
setUnchecked: (_a) => __awaiter(void 0, [_a], void 0, function* ({ item, tree, itemId }) {
|
|
119
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
120
|
+
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
yield tree.loadChildrenIds(itemId);
|
|
124
|
+
item.getChildren().forEach((item) => item.setUnchecked());
|
|
125
|
+
}
|
|
126
|
+
}),
|
|
127
|
+
},
|
|
128
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SetStateFn } from "../../types/core";
|
|
2
|
+
export declare enum CheckedState {
|
|
3
|
+
Checked = "checked",
|
|
4
|
+
Unchecked = "unchecked",
|
|
5
|
+
Indeterminate = "indeterminate"
|
|
6
|
+
}
|
|
7
|
+
export type CheckboxesFeatureDef<T> = {
|
|
8
|
+
state: {
|
|
9
|
+
checkedItems: string[];
|
|
10
|
+
};
|
|
11
|
+
config: {
|
|
12
|
+
setCheckedItems?: SetStateFn<string[]>;
|
|
13
|
+
canCheckFolders?: boolean;
|
|
14
|
+
};
|
|
15
|
+
treeInstance: {
|
|
16
|
+
setCheckedItems: (checkedItems: string[]) => void;
|
|
17
|
+
};
|
|
18
|
+
itemInstance: {
|
|
19
|
+
setChecked: () => Promise<void>;
|
|
20
|
+
setUnchecked: () => Promise<void>;
|
|
21
|
+
toggleCheckedState: () => Promise<void>;
|
|
22
|
+
getCheckedState: () => CheckedState;
|
|
23
|
+
getCheckboxProps: () => Record<string, any>;
|
|
24
|
+
};
|
|
25
|
+
hotkeys: never;
|
|
26
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CheckedState = void 0;
|
|
4
|
+
var CheckedState;
|
|
5
|
+
(function (CheckedState) {
|
|
6
|
+
CheckedState["Checked"] = "checked";
|
|
7
|
+
CheckedState["Unchecked"] = "unchecked";
|
|
8
|
+
CheckedState["Indeterminate"] = "indeterminate";
|
|
9
|
+
})(CheckedState || (exports.CheckedState = CheckedState = {}));
|
|
@@ -17,6 +17,8 @@ export type MainFeatureDef<T = any> = {
|
|
|
17
17
|
treeInstance: {
|
|
18
18
|
/** @internal */
|
|
19
19
|
applySubStateUpdate: <K extends keyof TreeState<any>>(stateName: K, updater: Updater<TreeState<T>[K]>) => void;
|
|
20
|
+
/** @internal */
|
|
21
|
+
buildItemInstance: (itemId: string) => ItemInstance<T>;
|
|
20
22
|
setState: SetStateFn<TreeState<T>>;
|
|
21
23
|
getState: () => TreeState<T>;
|
|
22
24
|
setConfig: SetStateFn<TreeConfig<T>>;
|
|
@@ -40,6 +40,8 @@ exports.syncDataLoaderFeature = {
|
|
|
40
40
|
}
|
|
41
41
|
return unpromise(dataLoader.getChildrenWithData(itemId)).map((c) => c.data);
|
|
42
42
|
},
|
|
43
|
+
loadItemData: ({ tree }, itemId) => tree.retrieveItemData(itemId),
|
|
44
|
+
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId),
|
|
43
45
|
},
|
|
44
46
|
itemInstance: {
|
|
45
47
|
isLoading: () => false,
|
|
@@ -18,8 +18,8 @@ export type SyncDataLoaderFeatureDef<T> = {
|
|
|
18
18
|
dataLoader: TreeDataLoader<T>;
|
|
19
19
|
};
|
|
20
20
|
treeInstance: {
|
|
21
|
-
retrieveItemData: (itemId: string) => T;
|
|
22
|
-
retrieveChildrenIds: (itemId: string) => string[];
|
|
21
|
+
retrieveItemData: (itemId: string, skipFetch?: boolean) => T;
|
|
22
|
+
retrieveChildrenIds: (itemId: string, skipFetch?: boolean) => string[];
|
|
23
23
|
};
|
|
24
24
|
itemInstance: {
|
|
25
25
|
isLoading: () => boolean;
|
package/lib/cjs/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
|
|
|
5
5
|
export * from "./features/drag-and-drop/types";
|
|
6
6
|
export * from "./features/keyboard-drag-and-drop/types";
|
|
7
7
|
export * from "./features/selection/types";
|
|
8
|
+
export * from "./features/checkboxes/types";
|
|
8
9
|
export * from "./features/async-data-loader/types";
|
|
9
10
|
export * from "./features/sync-data-loader/types";
|
|
10
11
|
export * from "./features/hotkeys-core/types";
|
|
@@ -13,6 +14,7 @@ export * from "./features/renaming/types";
|
|
|
13
14
|
export * from "./features/expand-all/types";
|
|
14
15
|
export * from "./features/prop-memoization/types";
|
|
15
16
|
export * from "./features/selection/feature";
|
|
17
|
+
export * from "./features/checkboxes/feature";
|
|
16
18
|
export * from "./features/hotkeys-core/feature";
|
|
17
19
|
export * from "./features/async-data-loader/feature";
|
|
18
20
|
export * from "./features/sync-data-loader/feature";
|
package/lib/cjs/index.js
CHANGED
|
@@ -20,6 +20,7 @@ __exportStar(require("./features/tree/types"), exports);
|
|
|
20
20
|
__exportStar(require("./features/drag-and-drop/types"), exports);
|
|
21
21
|
__exportStar(require("./features/keyboard-drag-and-drop/types"), exports);
|
|
22
22
|
__exportStar(require("./features/selection/types"), exports);
|
|
23
|
+
__exportStar(require("./features/checkboxes/types"), exports);
|
|
23
24
|
__exportStar(require("./features/async-data-loader/types"), exports);
|
|
24
25
|
__exportStar(require("./features/sync-data-loader/types"), exports);
|
|
25
26
|
__exportStar(require("./features/hotkeys-core/types"), exports);
|
|
@@ -28,6 +29,7 @@ __exportStar(require("./features/renaming/types"), exports);
|
|
|
28
29
|
__exportStar(require("./features/expand-all/types"), exports);
|
|
29
30
|
__exportStar(require("./features/prop-memoization/types"), exports);
|
|
30
31
|
__exportStar(require("./features/selection/feature"), exports);
|
|
32
|
+
__exportStar(require("./features/checkboxes/feature"), exports);
|
|
31
33
|
__exportStar(require("./features/hotkeys-core/feature"), exports);
|
|
32
34
|
__exportStar(require("./features/async-data-loader/feature"), exports);
|
|
33
35
|
__exportStar(require("./features/sync-data-loader/feature"), exports);
|
package/lib/cjs/types/core.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { RenamingFeatureDef } from "../features/renaming/types";
|
|
|
10
10
|
import { ExpandAllFeatureDef } from "../features/expand-all/types";
|
|
11
11
|
import { PropMemoizationFeatureDef } from "../features/prop-memoization/types";
|
|
12
12
|
import { KeyboardDragAndDropFeatureDef } from "../features/keyboard-drag-and-drop/types";
|
|
13
|
+
import { CheckboxesFeatureDef } from "../features/checkboxes/types";
|
|
13
14
|
export type Updater<T> = T | ((old: T) => T);
|
|
14
15
|
export type SetStateFn<T> = (updaterOrValue: Updater<T>) => void;
|
|
15
16
|
export type FeatureDef = {
|
|
@@ -34,7 +35,7 @@ type MergedFeatures<F extends FeatureDef> = {
|
|
|
34
35
|
itemInstance: UnionToIntersection<F["itemInstance"]>;
|
|
35
36
|
hotkeys: F["hotkeys"];
|
|
36
37
|
};
|
|
37
|
-
export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | DragAndDropFeatureDef<T> | KeyboardDragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
|
|
38
|
+
export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | CheckboxesFeatureDef<T> | DragAndDropFeatureDef<T> | KeyboardDragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
|
|
38
39
|
type TreeStateType<T> = MergedFeatures<RegisteredFeatures<T>>["state"];
|
|
39
40
|
export interface TreeState<T> extends TreeStateType<T> {
|
|
40
41
|
}
|
|
@@ -97,6 +97,15 @@ export const createTree = (initialConfig) => {
|
|
|
97
97
|
const externalStateSetter = config[stateHandlerNames[stateName]];
|
|
98
98
|
externalStateSetter === null || externalStateSetter === void 0 ? void 0 : externalStateSetter(state[stateName]);
|
|
99
99
|
},
|
|
100
|
+
buildItemInstance: ({}, itemId) => {
|
|
101
|
+
const [instance, finalizeInstance] = buildInstance(features, "itemInstance", (instance) => ({
|
|
102
|
+
item: instance,
|
|
103
|
+
tree: treeInstance,
|
|
104
|
+
itemId,
|
|
105
|
+
}));
|
|
106
|
+
finalizeInstance();
|
|
107
|
+
return instance;
|
|
108
|
+
},
|
|
100
109
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
101
110
|
rebuildTree: () => {
|
|
102
111
|
var _a;
|
|
@@ -26,14 +26,17 @@ const loadItemData = (tree, itemId) => __awaiter(void 0, void 0, void 0, functio
|
|
|
26
26
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => id !== itemId));
|
|
27
27
|
(_b = dataRef.current.awaitingItemDataLoading) === null || _b === void 0 ? void 0 : _b[itemId].forEach((cb) => cb());
|
|
28
28
|
(_c = dataRef.current.awaitingItemDataLoading) === null || _c === void 0 ? true : delete _c[itemId];
|
|
29
|
+
return item;
|
|
29
30
|
});
|
|
30
31
|
const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
31
32
|
var _a, _b, _c, _d, _e;
|
|
32
33
|
const config = tree.getConfig();
|
|
33
34
|
const dataRef = getDataRef(tree);
|
|
35
|
+
let childrenIds;
|
|
36
|
+
// TODO is folder check?
|
|
34
37
|
if ("getChildrenWithData" in config.dataLoader) {
|
|
35
38
|
const children = yield config.dataLoader.getChildrenWithData(itemId);
|
|
36
|
-
|
|
39
|
+
childrenIds = children.map((c) => c.id);
|
|
37
40
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
38
41
|
children.forEach(({ id, data }) => {
|
|
39
42
|
var _a, _b, _c;
|
|
@@ -47,7 +50,7 @@ const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, func
|
|
|
47
50
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => !childrenIds.includes(id)));
|
|
48
51
|
}
|
|
49
52
|
else {
|
|
50
|
-
|
|
53
|
+
childrenIds = yield config.dataLoader.getChildren(itemId);
|
|
51
54
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
52
55
|
(_b = config.onLoadedChildren) === null || _b === void 0 ? void 0 : _b.call(config, itemId, childrenIds);
|
|
53
56
|
tree.rebuildTree();
|
|
@@ -55,6 +58,7 @@ const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, func
|
|
|
55
58
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId));
|
|
56
59
|
(_d = (_c = dataRef.current.awaitingItemChildrensLoading) === null || _c === void 0 ? void 0 : _c[itemId]) === null || _d === void 0 ? void 0 : _d.forEach((cb) => cb());
|
|
57
60
|
(_e = dataRef.current.awaitingItemChildrensLoading) === null || _e === void 0 ? true : delete _e[itemId];
|
|
61
|
+
return childrenIds;
|
|
58
62
|
});
|
|
59
63
|
export const asyncDataLoaderFeature = {
|
|
60
64
|
key: "async-data-loader",
|
|
@@ -80,6 +84,7 @@ export const asyncDataLoaderFeature = {
|
|
|
80
84
|
});
|
|
81
85
|
}),
|
|
82
86
|
waitForItemChildrenLoaded: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
87
|
+
// TODO replace inner implementation with load() fns
|
|
83
88
|
tree.retrieveChildrenIds(itemId);
|
|
84
89
|
if (!tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
85
90
|
return;
|
|
@@ -93,14 +98,22 @@ export const asyncDataLoaderFeature = {
|
|
|
93
98
|
dataRef.current.awaitingItemChildrensLoading[itemId].push(resolve);
|
|
94
99
|
});
|
|
95
100
|
}),
|
|
96
|
-
|
|
101
|
+
loadItemData: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
102
|
+
var _b;
|
|
103
|
+
return ((_b = getDataRef(tree).current.itemData[itemId]) !== null && _b !== void 0 ? _b : (yield loadItemData(tree, itemId)));
|
|
104
|
+
}),
|
|
105
|
+
loadChildrenIds: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
106
|
+
var _b;
|
|
107
|
+
return ((_b = getDataRef(tree).current.childrenIds[itemId]) !== null && _b !== void 0 ? _b : (yield loadChildrenIds(tree, itemId)));
|
|
108
|
+
}),
|
|
109
|
+
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
97
110
|
var _a, _b;
|
|
98
111
|
const config = tree.getConfig();
|
|
99
112
|
const dataRef = getDataRef(tree);
|
|
100
113
|
if (dataRef.current.itemData[itemId]) {
|
|
101
114
|
return dataRef.current.itemData[itemId];
|
|
102
115
|
}
|
|
103
|
-
if (!tree.getState().loadingItemData.includes(itemId)) {
|
|
116
|
+
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
104
117
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
105
118
|
...loadingItemData,
|
|
106
119
|
itemId,
|
|
@@ -109,12 +122,12 @@ export const asyncDataLoaderFeature = {
|
|
|
109
122
|
}
|
|
110
123
|
return (_b = (_a = config.createLoadingItemData) === null || _a === void 0 ? void 0 : _a.call(config)) !== null && _b !== void 0 ? _b : null;
|
|
111
124
|
},
|
|
112
|
-
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
125
|
+
retrieveChildrenIds: ({ tree }, itemId, skipFetch = false) => {
|
|
113
126
|
const dataRef = getDataRef(tree);
|
|
114
127
|
if (dataRef.current.childrenIds[itemId]) {
|
|
115
128
|
return dataRef.current.childrenIds[itemId];
|
|
116
129
|
}
|
|
117
|
-
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
130
|
+
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
118
131
|
return [];
|
|
119
132
|
}
|
|
120
133
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [...loadingItemChildrens, itemId]);
|
|
@@ -27,8 +27,12 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
27
27
|
onLoadedChildren?: (itemId: string, childrenIds: string[]) => void;
|
|
28
28
|
};
|
|
29
29
|
treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"] & {
|
|
30
|
+
/** @deprecated use loadItemData instead */
|
|
30
31
|
waitForItemDataLoaded: (itemId: string) => Promise<void>;
|
|
32
|
+
/** @deprecated use loadChildrenIds instead */
|
|
31
33
|
waitForItemChildrenLoaded: (itemId: string) => Promise<void>;
|
|
34
|
+
loadItemData: (itemId: string) => Promise<T>;
|
|
35
|
+
loadChildrenIds: (itemId: string) => Promise<string[]>;
|
|
32
36
|
};
|
|
33
37
|
itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
|
|
34
38
|
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
import { makeStateUpdater } from "../../utils";
|
|
11
|
+
import { CheckedState } from "./types";
|
|
12
|
+
/*
|
|
13
|
+
* Cases for checking:
|
|
14
|
+
* - Check an unchecked item in an unchecked or indeterminate folder
|
|
15
|
+
* - Check an explicitly unchecked item in a checked folder
|
|
16
|
+
* - Check an unchecked folder in an unchecked or indeterminate folder
|
|
17
|
+
*
|
|
18
|
+
* Cases for unchecking:
|
|
19
|
+
* - Uncheck a checked item in an indeterminate folder
|
|
20
|
+
* - Uncheck an explicitly unchecked item in an checked folder
|
|
21
|
+
*/
|
|
22
|
+
const fetchAllDescendants = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
23
|
+
const children = yield tree.loadChildrenIds(itemId);
|
|
24
|
+
return [
|
|
25
|
+
itemId,
|
|
26
|
+
...(yield Promise.all(children.map((child) => fetchAllDescendants(tree, child)))).flat(),
|
|
27
|
+
];
|
|
28
|
+
});
|
|
29
|
+
const getAllLoadedDescendants = (tree, itemId) => {
|
|
30
|
+
const children = tree.retrieveChildrenIds(itemId, true);
|
|
31
|
+
return [
|
|
32
|
+
itemId,
|
|
33
|
+
...children.map((child) => getAllLoadedDescendants(tree, child)).flat(),
|
|
34
|
+
];
|
|
35
|
+
};
|
|
36
|
+
export const checkboxesFeature = {
|
|
37
|
+
key: "checkboxes",
|
|
38
|
+
overwrites: ["selection"],
|
|
39
|
+
getInitialState: (initialState) => (Object.assign({ checkedItems: [] }, initialState)),
|
|
40
|
+
getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setCheckedItems: makeStateUpdater("checkedItems", tree) }, defaultConfig)),
|
|
41
|
+
stateHandlerNames: {
|
|
42
|
+
checkedItems: "setCheckedItems",
|
|
43
|
+
},
|
|
44
|
+
treeInstance: {
|
|
45
|
+
setCheckedItems: ({ tree }, checkedItems) => {
|
|
46
|
+
tree.applySubStateUpdate("checkedItems", checkedItems);
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
itemInstance: {
|
|
50
|
+
getCheckboxProps: ({ item, itemId }) => {
|
|
51
|
+
const checkedState = item.getCheckedState();
|
|
52
|
+
// console.log("prop", itemId, checkedState);
|
|
53
|
+
return {
|
|
54
|
+
onChange: item.toggleCheckedState,
|
|
55
|
+
checked: checkedState === CheckedState.Checked,
|
|
56
|
+
ref: (r) => {
|
|
57
|
+
if (r) {
|
|
58
|
+
// console.log("ref", itemId, checkedState);
|
|
59
|
+
r.indeterminate = checkedState === CheckedState.Indeterminate;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
toggleCheckedState: (_a) => __awaiter(void 0, [_a], void 0, function* ({ item }) {
|
|
65
|
+
if (item.getCheckedState() === CheckedState.Checked) {
|
|
66
|
+
yield item.setUnchecked();
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
yield item.setChecked();
|
|
70
|
+
}
|
|
71
|
+
}),
|
|
72
|
+
getCheckedState: ({ item, tree, itemId }) => {
|
|
73
|
+
// TODO checkedcache
|
|
74
|
+
const { checkedItems } = tree.getState();
|
|
75
|
+
if (checkedItems.includes(itemId)) {
|
|
76
|
+
return CheckedState.Checked;
|
|
77
|
+
}
|
|
78
|
+
if (item.isFolder()) {
|
|
79
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
80
|
+
console.log("descendants of ", itemId, descendants);
|
|
81
|
+
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
82
|
+
return CheckedState.Checked;
|
|
83
|
+
}
|
|
84
|
+
if (descendants.some((d) => checkedItems.includes(d))) {
|
|
85
|
+
return CheckedState.Indeterminate;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// if (
|
|
89
|
+
// item.isFolder() &&
|
|
90
|
+
// checkedItems.some((checkedItem) =>
|
|
91
|
+
// tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
|
|
92
|
+
// )
|
|
93
|
+
// ) {
|
|
94
|
+
// // TODO for every descendent, not every checked item
|
|
95
|
+
// return checkedItems.every((checkedItem) =>
|
|
96
|
+
// tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
|
|
97
|
+
// )
|
|
98
|
+
// ? CheckedState.Checked
|
|
99
|
+
// : CheckedState.Indeterminate;
|
|
100
|
+
// }
|
|
101
|
+
return CheckedState.Unchecked;
|
|
102
|
+
},
|
|
103
|
+
setChecked: (_a) => __awaiter(void 0, [_a], void 0, function* ({ item, tree, itemId }) {
|
|
104
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
105
|
+
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const descendants = yield fetchAllDescendants(tree, itemId);
|
|
109
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
110
|
+
...items,
|
|
111
|
+
...descendants,
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
}),
|
|
115
|
+
setUnchecked: (_a) => __awaiter(void 0, [_a], void 0, function* ({ item, tree, itemId }) {
|
|
116
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
117
|
+
tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
yield tree.loadChildrenIds(itemId);
|
|
121
|
+
item.getChildren().forEach((item) => item.setUnchecked());
|
|
122
|
+
}
|
|
123
|
+
}),
|
|
124
|
+
},
|
|
125
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SetStateFn } from "../../types/core";
|
|
2
|
+
export declare enum CheckedState {
|
|
3
|
+
Checked = "checked",
|
|
4
|
+
Unchecked = "unchecked",
|
|
5
|
+
Indeterminate = "indeterminate"
|
|
6
|
+
}
|
|
7
|
+
export type CheckboxesFeatureDef<T> = {
|
|
8
|
+
state: {
|
|
9
|
+
checkedItems: string[];
|
|
10
|
+
};
|
|
11
|
+
config: {
|
|
12
|
+
setCheckedItems?: SetStateFn<string[]>;
|
|
13
|
+
canCheckFolders?: boolean;
|
|
14
|
+
};
|
|
15
|
+
treeInstance: {
|
|
16
|
+
setCheckedItems: (checkedItems: string[]) => void;
|
|
17
|
+
};
|
|
18
|
+
itemInstance: {
|
|
19
|
+
setChecked: () => Promise<void>;
|
|
20
|
+
setUnchecked: () => Promise<void>;
|
|
21
|
+
toggleCheckedState: () => Promise<void>;
|
|
22
|
+
getCheckedState: () => CheckedState;
|
|
23
|
+
getCheckboxProps: () => Record<string, any>;
|
|
24
|
+
};
|
|
25
|
+
hotkeys: never;
|
|
26
|
+
};
|
|
@@ -17,6 +17,8 @@ export type MainFeatureDef<T = any> = {
|
|
|
17
17
|
treeInstance: {
|
|
18
18
|
/** @internal */
|
|
19
19
|
applySubStateUpdate: <K extends keyof TreeState<any>>(stateName: K, updater: Updater<TreeState<T>[K]>) => void;
|
|
20
|
+
/** @internal */
|
|
21
|
+
buildItemInstance: (itemId: string) => ItemInstance<T>;
|
|
20
22
|
setState: SetStateFn<TreeState<T>>;
|
|
21
23
|
getState: () => TreeState<T>;
|
|
22
24
|
setConfig: SetStateFn<TreeConfig<T>>;
|
|
@@ -37,6 +37,8 @@ export const syncDataLoaderFeature = {
|
|
|
37
37
|
}
|
|
38
38
|
return unpromise(dataLoader.getChildrenWithData(itemId)).map((c) => c.data);
|
|
39
39
|
},
|
|
40
|
+
loadItemData: ({ tree }, itemId) => tree.retrieveItemData(itemId),
|
|
41
|
+
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId),
|
|
40
42
|
},
|
|
41
43
|
itemInstance: {
|
|
42
44
|
isLoading: () => false,
|
|
@@ -18,8 +18,8 @@ export type SyncDataLoaderFeatureDef<T> = {
|
|
|
18
18
|
dataLoader: TreeDataLoader<T>;
|
|
19
19
|
};
|
|
20
20
|
treeInstance: {
|
|
21
|
-
retrieveItemData: (itemId: string) => T;
|
|
22
|
-
retrieveChildrenIds: (itemId: string) => string[];
|
|
21
|
+
retrieveItemData: (itemId: string, skipFetch?: boolean) => T;
|
|
22
|
+
retrieveChildrenIds: (itemId: string, skipFetch?: boolean) => string[];
|
|
23
23
|
};
|
|
24
24
|
itemInstance: {
|
|
25
25
|
isLoading: () => boolean;
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
|
|
|
5
5
|
export * from "./features/drag-and-drop/types";
|
|
6
6
|
export * from "./features/keyboard-drag-and-drop/types";
|
|
7
7
|
export * from "./features/selection/types";
|
|
8
|
+
export * from "./features/checkboxes/types";
|
|
8
9
|
export * from "./features/async-data-loader/types";
|
|
9
10
|
export * from "./features/sync-data-loader/types";
|
|
10
11
|
export * from "./features/hotkeys-core/types";
|
|
@@ -13,6 +14,7 @@ export * from "./features/renaming/types";
|
|
|
13
14
|
export * from "./features/expand-all/types";
|
|
14
15
|
export * from "./features/prop-memoization/types";
|
|
15
16
|
export * from "./features/selection/feature";
|
|
17
|
+
export * from "./features/checkboxes/feature";
|
|
16
18
|
export * from "./features/hotkeys-core/feature";
|
|
17
19
|
export * from "./features/async-data-loader/feature";
|
|
18
20
|
export * from "./features/sync-data-loader/feature";
|
package/lib/esm/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./features/tree/types";
|
|
|
4
4
|
export * from "./features/drag-and-drop/types";
|
|
5
5
|
export * from "./features/keyboard-drag-and-drop/types";
|
|
6
6
|
export * from "./features/selection/types";
|
|
7
|
+
export * from "./features/checkboxes/types";
|
|
7
8
|
export * from "./features/async-data-loader/types";
|
|
8
9
|
export * from "./features/sync-data-loader/types";
|
|
9
10
|
export * from "./features/hotkeys-core/types";
|
|
@@ -12,6 +13,7 @@ export * from "./features/renaming/types";
|
|
|
12
13
|
export * from "./features/expand-all/types";
|
|
13
14
|
export * from "./features/prop-memoization/types";
|
|
14
15
|
export * from "./features/selection/feature";
|
|
16
|
+
export * from "./features/checkboxes/feature";
|
|
15
17
|
export * from "./features/hotkeys-core/feature";
|
|
16
18
|
export * from "./features/async-data-loader/feature";
|
|
17
19
|
export * from "./features/sync-data-loader/feature";
|
package/lib/esm/types/core.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { RenamingFeatureDef } from "../features/renaming/types";
|
|
|
10
10
|
import { ExpandAllFeatureDef } from "../features/expand-all/types";
|
|
11
11
|
import { PropMemoizationFeatureDef } from "../features/prop-memoization/types";
|
|
12
12
|
import { KeyboardDragAndDropFeatureDef } from "../features/keyboard-drag-and-drop/types";
|
|
13
|
+
import { CheckboxesFeatureDef } from "../features/checkboxes/types";
|
|
13
14
|
export type Updater<T> = T | ((old: T) => T);
|
|
14
15
|
export type SetStateFn<T> = (updaterOrValue: Updater<T>) => void;
|
|
15
16
|
export type FeatureDef = {
|
|
@@ -34,7 +35,7 @@ type MergedFeatures<F extends FeatureDef> = {
|
|
|
34
35
|
itemInstance: UnionToIntersection<F["itemInstance"]>;
|
|
35
36
|
hotkeys: F["hotkeys"];
|
|
36
37
|
};
|
|
37
|
-
export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | DragAndDropFeatureDef<T> | KeyboardDragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
|
|
38
|
+
export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | CheckboxesFeatureDef<T> | DragAndDropFeatureDef<T> | KeyboardDragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
|
|
38
39
|
type TreeStateType<T> = MergedFeatures<RegisteredFeatures<T>>["state"];
|
|
39
40
|
export interface TreeState<T> extends TreeStateType<T> {
|
|
40
41
|
}
|
package/package.json
CHANGED
package/src/core/create-tree.ts
CHANGED
|
@@ -151,6 +151,19 @@ export const createTree = <T>(
|
|
|
151
151
|
] as Function;
|
|
152
152
|
externalStateSetter?.(state[stateName]);
|
|
153
153
|
},
|
|
154
|
+
buildItemInstance: ({}, itemId) => {
|
|
155
|
+
const [instance, finalizeInstance] = buildInstance(
|
|
156
|
+
features,
|
|
157
|
+
"itemInstance",
|
|
158
|
+
(instance) => ({
|
|
159
|
+
item: instance,
|
|
160
|
+
tree: treeInstance,
|
|
161
|
+
itemId,
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
finalizeInstance();
|
|
165
|
+
return instance;
|
|
166
|
+
},
|
|
154
167
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
155
168
|
rebuildTree: () => {
|
|
156
169
|
rebuildItemMeta();
|
|
@@ -22,15 +22,19 @@ const loadItemData = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
|
22
22
|
|
|
23
23
|
dataRef.current.awaitingItemDataLoading?.[itemId].forEach((cb) => cb());
|
|
24
24
|
delete dataRef.current.awaitingItemDataLoading?.[itemId];
|
|
25
|
+
return item;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
const loadChildrenIds = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
28
29
|
const config = tree.getConfig();
|
|
29
30
|
const dataRef = getDataRef(tree);
|
|
31
|
+
let childrenIds: string[];
|
|
32
|
+
|
|
33
|
+
// TODO is folder check?
|
|
30
34
|
|
|
31
35
|
if ("getChildrenWithData" in config.dataLoader) {
|
|
32
36
|
const children = await config.dataLoader.getChildrenWithData(itemId);
|
|
33
|
-
|
|
37
|
+
childrenIds = children.map((c) => c.id);
|
|
34
38
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
35
39
|
children.forEach(({ id, data }) => {
|
|
36
40
|
dataRef.current.itemData[id] = data;
|
|
@@ -45,7 +49,7 @@ const loadChildrenIds = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
|
45
49
|
loadingItemData.filter((id) => !childrenIds.includes(id)),
|
|
46
50
|
);
|
|
47
51
|
} else {
|
|
48
|
-
|
|
52
|
+
childrenIds = await config.dataLoader.getChildren(itemId);
|
|
49
53
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
50
54
|
config.onLoadedChildren?.(itemId, childrenIds);
|
|
51
55
|
tree.rebuildTree();
|
|
@@ -57,6 +61,7 @@ const loadChildrenIds = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
|
57
61
|
|
|
58
62
|
dataRef.current.awaitingItemChildrensLoading?.[itemId]?.forEach((cb) => cb());
|
|
59
63
|
delete dataRef.current.awaitingItemChildrensLoading?.[itemId];
|
|
64
|
+
return childrenIds;
|
|
60
65
|
};
|
|
61
66
|
|
|
62
67
|
export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
@@ -94,6 +99,7 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
94
99
|
},
|
|
95
100
|
|
|
96
101
|
waitForItemChildrenLoaded: async ({ tree }, itemId) => {
|
|
102
|
+
// TODO replace inner implementation with load() fns
|
|
97
103
|
tree.retrieveChildrenIds(itemId);
|
|
98
104
|
if (!tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
99
105
|
return;
|
|
@@ -106,7 +112,20 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
106
112
|
});
|
|
107
113
|
},
|
|
108
114
|
|
|
109
|
-
|
|
115
|
+
loadItemData: async ({ tree }, itemId) => {
|
|
116
|
+
return (
|
|
117
|
+
getDataRef(tree).current.itemData[itemId] ??
|
|
118
|
+
(await loadItemData(tree, itemId))
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
loadChildrenIds: async ({ tree }, itemId) => {
|
|
122
|
+
return (
|
|
123
|
+
getDataRef(tree).current.childrenIds[itemId] ??
|
|
124
|
+
(await loadChildrenIds(tree, itemId))
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
|
|
110
129
|
const config = tree.getConfig();
|
|
111
130
|
const dataRef = getDataRef(tree);
|
|
112
131
|
|
|
@@ -114,7 +133,7 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
114
133
|
return dataRef.current.itemData[itemId];
|
|
115
134
|
}
|
|
116
135
|
|
|
117
|
-
if (!tree.getState().loadingItemData.includes(itemId)) {
|
|
136
|
+
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
118
137
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
119
138
|
...loadingItemData,
|
|
120
139
|
itemId,
|
|
@@ -126,13 +145,13 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
126
145
|
return config.createLoadingItemData?.() ?? null;
|
|
127
146
|
},
|
|
128
147
|
|
|
129
|
-
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
148
|
+
retrieveChildrenIds: ({ tree }, itemId, skipFetch = false) => {
|
|
130
149
|
const dataRef = getDataRef(tree);
|
|
131
150
|
if (dataRef.current.childrenIds[itemId]) {
|
|
132
151
|
return dataRef.current.childrenIds[itemId];
|
|
133
152
|
}
|
|
134
153
|
|
|
135
|
-
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
154
|
+
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
136
155
|
return [];
|
|
137
156
|
}
|
|
138
157
|
|
|
@@ -32,8 +32,12 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
32
32
|
onLoadedChildren?: (itemId: string, childrenIds: string[]) => void;
|
|
33
33
|
};
|
|
34
34
|
treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"] & {
|
|
35
|
+
/** @deprecated use loadItemData instead */
|
|
35
36
|
waitForItemDataLoaded: (itemId: string) => Promise<void>;
|
|
37
|
+
/** @deprecated use loadChildrenIds instead */
|
|
36
38
|
waitForItemChildrenLoaded: (itemId: string) => Promise<void>;
|
|
39
|
+
loadItemData: (itemId: string) => Promise<T>;
|
|
40
|
+
loadChildrenIds: (itemId: string) => Promise<string[]>;
|
|
37
41
|
};
|
|
38
42
|
itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
|
|
39
43
|
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { FeatureImplementation, TreeInstance } from "../../types/core";
|
|
2
|
+
import { makeStateUpdater } from "../../utils";
|
|
3
|
+
import { CheckedState } from "./types";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* Cases for checking:
|
|
7
|
+
* - Check an unchecked item in an unchecked or indeterminate folder
|
|
8
|
+
* - Check an explicitly unchecked item in a checked folder
|
|
9
|
+
* - Check an unchecked folder in an unchecked or indeterminate folder
|
|
10
|
+
*
|
|
11
|
+
* Cases for unchecking:
|
|
12
|
+
* - Uncheck a checked item in an indeterminate folder
|
|
13
|
+
* - Uncheck an explicitly unchecked item in an checked folder
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fetchAllDescendants = async <T>(
|
|
17
|
+
tree: TreeInstance<T>,
|
|
18
|
+
itemId: string,
|
|
19
|
+
): Promise<string[]> => {
|
|
20
|
+
const children = await tree.loadChildrenIds(itemId);
|
|
21
|
+
return [
|
|
22
|
+
itemId,
|
|
23
|
+
...(
|
|
24
|
+
await Promise.all(
|
|
25
|
+
children.map((child) => fetchAllDescendants(tree, child)),
|
|
26
|
+
)
|
|
27
|
+
).flat(),
|
|
28
|
+
];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getAllLoadedDescendants = <T>(
|
|
32
|
+
tree: TreeInstance<T>,
|
|
33
|
+
itemId: string,
|
|
34
|
+
): string[] => {
|
|
35
|
+
const children = tree.retrieveChildrenIds(itemId, true);
|
|
36
|
+
return [
|
|
37
|
+
itemId,
|
|
38
|
+
...children.map((child) => getAllLoadedDescendants(tree, child)).flat(),
|
|
39
|
+
];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const checkboxesFeature: FeatureImplementation = {
|
|
43
|
+
key: "checkboxes",
|
|
44
|
+
|
|
45
|
+
overwrites: ["selection"],
|
|
46
|
+
|
|
47
|
+
getInitialState: (initialState) => ({
|
|
48
|
+
checkedItems: [],
|
|
49
|
+
...initialState,
|
|
50
|
+
}),
|
|
51
|
+
|
|
52
|
+
getDefaultConfig: (defaultConfig, tree) => ({
|
|
53
|
+
setCheckedItems: makeStateUpdater("checkedItems", tree),
|
|
54
|
+
...defaultConfig,
|
|
55
|
+
}),
|
|
56
|
+
|
|
57
|
+
stateHandlerNames: {
|
|
58
|
+
checkedItems: "setCheckedItems",
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
treeInstance: {
|
|
62
|
+
setCheckedItems: ({ tree }, checkedItems) => {
|
|
63
|
+
tree.applySubStateUpdate("checkedItems", checkedItems);
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
itemInstance: {
|
|
68
|
+
getCheckboxProps: ({ item, itemId }) => {
|
|
69
|
+
const checkedState = item.getCheckedState();
|
|
70
|
+
// console.log("prop", itemId, checkedState);
|
|
71
|
+
return {
|
|
72
|
+
onChange: item.toggleCheckedState,
|
|
73
|
+
checked: checkedState === CheckedState.Checked,
|
|
74
|
+
ref: (r: any) => {
|
|
75
|
+
if (r) {
|
|
76
|
+
// console.log("ref", itemId, checkedState);
|
|
77
|
+
r.indeterminate = checkedState === CheckedState.Indeterminate;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
toggleCheckedState: async ({ item }) => {
|
|
84
|
+
if (item.getCheckedState() === CheckedState.Checked) {
|
|
85
|
+
await item.setUnchecked();
|
|
86
|
+
} else {
|
|
87
|
+
await item.setChecked();
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
getCheckedState: ({ item, tree, itemId }) => {
|
|
92
|
+
// TODO checkedcache
|
|
93
|
+
const { checkedItems } = tree.getState();
|
|
94
|
+
|
|
95
|
+
if (checkedItems.includes(itemId)) {
|
|
96
|
+
return CheckedState.Checked;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (item.isFolder()) {
|
|
100
|
+
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
101
|
+
console.log("descendants of ", itemId, descendants);
|
|
102
|
+
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
103
|
+
return CheckedState.Checked;
|
|
104
|
+
}
|
|
105
|
+
if (descendants.some((d) => checkedItems.includes(d))) {
|
|
106
|
+
return CheckedState.Indeterminate;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// if (
|
|
111
|
+
// item.isFolder() &&
|
|
112
|
+
// checkedItems.some((checkedItem) =>
|
|
113
|
+
// tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
|
|
114
|
+
// )
|
|
115
|
+
// ) {
|
|
116
|
+
// // TODO for every descendent, not every checked item
|
|
117
|
+
// return checkedItems.every((checkedItem) =>
|
|
118
|
+
// tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
|
|
119
|
+
// )
|
|
120
|
+
// ? CheckedState.Checked
|
|
121
|
+
// : CheckedState.Indeterminate;
|
|
122
|
+
// }
|
|
123
|
+
|
|
124
|
+
return CheckedState.Unchecked;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
setChecked: async ({ item, tree, itemId }) => {
|
|
128
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
129
|
+
tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
|
|
130
|
+
} else {
|
|
131
|
+
const descendants = await fetchAllDescendants(tree, itemId);
|
|
132
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
133
|
+
...items,
|
|
134
|
+
...descendants,
|
|
135
|
+
]);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
setUnchecked: async ({ item, tree, itemId }) => {
|
|
140
|
+
if (!item.isFolder() || tree.getConfig().canCheckFolders) {
|
|
141
|
+
tree.applySubStateUpdate("checkedItems", (items) =>
|
|
142
|
+
items.filter((id) => id !== itemId),
|
|
143
|
+
);
|
|
144
|
+
} else {
|
|
145
|
+
await tree.loadChildrenIds(itemId);
|
|
146
|
+
item.getChildren().forEach((item) => item.setUnchecked());
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SetStateFn } from "../../types/core";
|
|
2
|
+
|
|
3
|
+
export enum CheckedState {
|
|
4
|
+
Checked = "checked",
|
|
5
|
+
Unchecked = "unchecked",
|
|
6
|
+
Indeterminate = "indeterminate",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type CheckboxesFeatureDef<T> = {
|
|
10
|
+
state: {
|
|
11
|
+
checkedItems: string[];
|
|
12
|
+
};
|
|
13
|
+
config: {
|
|
14
|
+
setCheckedItems?: SetStateFn<string[]>;
|
|
15
|
+
canCheckFolders?: boolean;
|
|
16
|
+
};
|
|
17
|
+
treeInstance: {
|
|
18
|
+
setCheckedItems: (checkedItems: string[]) => void;
|
|
19
|
+
};
|
|
20
|
+
itemInstance: {
|
|
21
|
+
setChecked: () => Promise<void>;
|
|
22
|
+
setUnchecked: () => Promise<void>;
|
|
23
|
+
toggleCheckedState: () => Promise<void>;
|
|
24
|
+
getCheckedState: () => CheckedState;
|
|
25
|
+
getCheckboxProps: () => Record<string, any>;
|
|
26
|
+
};
|
|
27
|
+
hotkeys: never;
|
|
28
|
+
};
|
|
@@ -36,6 +36,8 @@ export type MainFeatureDef<T = any> = {
|
|
|
36
36
|
stateName: K,
|
|
37
37
|
updater: Updater<TreeState<T>[K]>,
|
|
38
38
|
) => void;
|
|
39
|
+
/** @internal */
|
|
40
|
+
buildItemInstance: (itemId: string) => ItemInstance<T>;
|
|
39
41
|
setState: SetStateFn<TreeState<T>>;
|
|
40
42
|
getState: () => TreeState<T>;
|
|
41
43
|
setConfig: SetStateFn<TreeConfig<T>>;
|
|
@@ -47,6 +47,9 @@ export const syncDataLoaderFeature: FeatureImplementation = {
|
|
|
47
47
|
(c) => c.data,
|
|
48
48
|
);
|
|
49
49
|
},
|
|
50
|
+
|
|
51
|
+
loadItemData: ({ tree }, itemId) => tree.retrieveItemData(itemId),
|
|
52
|
+
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId),
|
|
50
53
|
},
|
|
51
54
|
|
|
52
55
|
itemInstance: {
|
|
@@ -17,8 +17,8 @@ export type SyncDataLoaderFeatureDef<T> = {
|
|
|
17
17
|
dataLoader: TreeDataLoader<T>;
|
|
18
18
|
};
|
|
19
19
|
treeInstance: {
|
|
20
|
-
retrieveItemData: (itemId: string) => T;
|
|
21
|
-
retrieveChildrenIds: (itemId: string) => string[];
|
|
20
|
+
retrieveItemData: (itemId: string, skipFetch?: boolean) => T;
|
|
21
|
+
retrieveChildrenIds: (itemId: string, skipFetch?: boolean) => string[];
|
|
22
22
|
};
|
|
23
23
|
itemInstance: {
|
|
24
24
|
isLoading: () => boolean;
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
|
|
|
6
6
|
export * from "./features/drag-and-drop/types";
|
|
7
7
|
export * from "./features/keyboard-drag-and-drop/types";
|
|
8
8
|
export * from "./features/selection/types";
|
|
9
|
+
export * from "./features/checkboxes/types";
|
|
9
10
|
export * from "./features/async-data-loader/types";
|
|
10
11
|
export * from "./features/sync-data-loader/types";
|
|
11
12
|
export * from "./features/hotkeys-core/types";
|
|
@@ -15,6 +16,7 @@ export * from "./features/expand-all/types";
|
|
|
15
16
|
export * from "./features/prop-memoization/types";
|
|
16
17
|
|
|
17
18
|
export * from "./features/selection/feature";
|
|
19
|
+
export * from "./features/checkboxes/feature";
|
|
18
20
|
export * from "./features/hotkeys-core/feature";
|
|
19
21
|
export * from "./features/async-data-loader/feature";
|
|
20
22
|
export * from "./features/sync-data-loader/feature";
|
package/src/types/core.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { RenamingFeatureDef } from "../features/renaming/types";
|
|
|
13
13
|
import { ExpandAllFeatureDef } from "../features/expand-all/types";
|
|
14
14
|
import { PropMemoizationFeatureDef } from "../features/prop-memoization/types";
|
|
15
15
|
import { KeyboardDragAndDropFeatureDef } from "../features/keyboard-drag-and-drop/types";
|
|
16
|
+
import { CheckboxesFeatureDef } from "../features/checkboxes/types";
|
|
16
17
|
|
|
17
18
|
export type Updater<T> = T | ((old: T) => T);
|
|
18
19
|
export type SetStateFn<T> = (updaterOrValue: Updater<T>) => void;
|
|
@@ -53,6 +54,7 @@ export type RegisteredFeatures<T> =
|
|
|
53
54
|
| MainFeatureDef<T>
|
|
54
55
|
| TreeFeatureDef<T>
|
|
55
56
|
| SelectionFeatureDef<T>
|
|
57
|
+
| CheckboxesFeatureDef<T>
|
|
56
58
|
| DragAndDropFeatureDef<T>
|
|
57
59
|
| KeyboardDragAndDropFeatureDef<T>
|
|
58
60
|
| HotkeysCoreFeatureDef<T>
|