@headless-tree/core 0.0.0-20250820225709 → 0.0.0-20250828222543
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 +4 -1
- package/dist/index.d.mts +14 -4
- package/dist/index.d.ts +14 -4
- package/dist/index.js +98 -62
- package/dist/index.mjs +98 -62
- package/package.json +1 -1
- package/src/features/async-data-loader/feature.ts +14 -18
- package/src/features/async-data-loader/types.ts +2 -1
- package/src/features/checkboxes/checkboxes.spec.ts +111 -122
- package/src/features/checkboxes/feature.ts +89 -40
- package/src/features/checkboxes/types.ts +16 -3
- package/src/test-utils/test-tree.ts +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# @headless-tree/core
|
|
2
2
|
|
|
3
|
-
## 0.0.0-
|
|
3
|
+
## 0.0.0-20250828222543
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
7
|
- 215ab4b: add a new symbol that can be used in hotkey configurations "metaorcontrol" that will trigger if either any windows control key or mac meta key is pressed (#141)
|
|
8
|
+
- cf845d7: Added new state variable `loadingCheckPropagationItems` to indicate if, in async trees with checkboxes and state propagation enabled, data loading operations are currently loading due to a checkbox propagation taking place
|
|
9
|
+
- 597faad: Checkbox propagation is now supported for trees with async data loaders!
|
|
10
|
+
- b0ee382: triggering a data refetch will now always set the loadingItemData/loadingItemChildrens state variable to the associated items if they where not apart of the cache before
|
|
8
11
|
|
|
9
12
|
## 1.4.0
|
|
10
13
|
|
package/dist/index.d.mts
CHANGED
|
@@ -312,7 +312,8 @@ type AsyncDataLoaderFeatureDef<T> = {
|
|
|
312
312
|
* @param optimistic If true, the item will not trigger a state update on `loadingItemChildrens`, and
|
|
313
313
|
* the tree will continue to display the old data until the new data has loaded. */
|
|
314
314
|
invalidateChildrenIds: (optimistic?: boolean) => Promise<void>;
|
|
315
|
-
|
|
315
|
+
/** Set to undefined to clear cache without triggering automatic refetch. Use @invalidateItemData to clear and triggering refetch. */
|
|
316
|
+
updateCachedData: (data: T | undefined) => void;
|
|
316
317
|
updateCachedChildrenIds: (childrenIds: string[]) => void;
|
|
317
318
|
isLoading: () => boolean;
|
|
318
319
|
};
|
|
@@ -448,9 +449,11 @@ declare enum CheckedState {
|
|
|
448
449
|
type CheckboxesFeatureDef<T> = {
|
|
449
450
|
state: {
|
|
450
451
|
checkedItems: string[];
|
|
452
|
+
loadingCheckPropagationItems: string[];
|
|
451
453
|
};
|
|
452
454
|
config: {
|
|
453
455
|
setCheckedItems?: SetStateFn<string[]>;
|
|
456
|
+
setLoadingCheckPropagationItems?: SetStateFn<string[]>;
|
|
454
457
|
canCheckFolders?: boolean;
|
|
455
458
|
propagateCheckedState?: boolean;
|
|
456
459
|
};
|
|
@@ -458,11 +461,18 @@ type CheckboxesFeatureDef<T> = {
|
|
|
458
461
|
setCheckedItems: (checkedItems: string[]) => void;
|
|
459
462
|
};
|
|
460
463
|
itemInstance: {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
+
/** Will recursively load descendants if propagateCheckedState=true and async data loader is used. If not,
|
|
465
|
+
* this will return immediately. */
|
|
466
|
+
setChecked: () => Promise<void>;
|
|
467
|
+
/** Will recursively load descendants if propagateCheckedState=true and async data loader is used. If not,
|
|
468
|
+
* this will return immediately. */
|
|
469
|
+
setUnchecked: () => Promise<void>;
|
|
470
|
+
/** Will recursively load descendants if propagateCheckedState=true and async data loader is used. If not,
|
|
471
|
+
* this will return immediately. */
|
|
472
|
+
toggleCheckedState: () => Promise<void>;
|
|
464
473
|
getCheckedState: () => CheckedState;
|
|
465
474
|
getCheckboxProps: () => Record<string, any>;
|
|
475
|
+
isLoadingCheckPropagation: () => boolean;
|
|
466
476
|
};
|
|
467
477
|
hotkeys: never;
|
|
468
478
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -312,7 +312,8 @@ type AsyncDataLoaderFeatureDef<T> = {
|
|
|
312
312
|
* @param optimistic If true, the item will not trigger a state update on `loadingItemChildrens`, and
|
|
313
313
|
* the tree will continue to display the old data until the new data has loaded. */
|
|
314
314
|
invalidateChildrenIds: (optimistic?: boolean) => Promise<void>;
|
|
315
|
-
|
|
315
|
+
/** Set to undefined to clear cache without triggering automatic refetch. Use @invalidateItemData to clear and triggering refetch. */
|
|
316
|
+
updateCachedData: (data: T | undefined) => void;
|
|
316
317
|
updateCachedChildrenIds: (childrenIds: string[]) => void;
|
|
317
318
|
isLoading: () => boolean;
|
|
318
319
|
};
|
|
@@ -448,9 +449,11 @@ declare enum CheckedState {
|
|
|
448
449
|
type CheckboxesFeatureDef<T> = {
|
|
449
450
|
state: {
|
|
450
451
|
checkedItems: string[];
|
|
452
|
+
loadingCheckPropagationItems: string[];
|
|
451
453
|
};
|
|
452
454
|
config: {
|
|
453
455
|
setCheckedItems?: SetStateFn<string[]>;
|
|
456
|
+
setLoadingCheckPropagationItems?: SetStateFn<string[]>;
|
|
454
457
|
canCheckFolders?: boolean;
|
|
455
458
|
propagateCheckedState?: boolean;
|
|
456
459
|
};
|
|
@@ -458,11 +461,18 @@ type CheckboxesFeatureDef<T> = {
|
|
|
458
461
|
setCheckedItems: (checkedItems: string[]) => void;
|
|
459
462
|
};
|
|
460
463
|
itemInstance: {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
+
/** Will recursively load descendants if propagateCheckedState=true and async data loader is used. If not,
|
|
465
|
+
* this will return immediately. */
|
|
466
|
+
setChecked: () => Promise<void>;
|
|
467
|
+
/** Will recursively load descendants if propagateCheckedState=true and async data loader is used. If not,
|
|
468
|
+
* this will return immediately. */
|
|
469
|
+
setUnchecked: () => Promise<void>;
|
|
470
|
+
/** Will recursively load descendants if propagateCheckedState=true and async data loader is used. If not,
|
|
471
|
+
* this will return immediately. */
|
|
472
|
+
toggleCheckedState: () => Promise<void>;
|
|
464
473
|
getCheckedState: () => CheckedState;
|
|
465
474
|
getCheckboxProps: () => Record<string, any>;
|
|
475
|
+
isLoadingCheckPropagation: () => boolean;
|
|
466
476
|
};
|
|
467
477
|
hotkeys: never;
|
|
468
478
|
};
|
package/dist/index.js
CHANGED
|
@@ -817,33 +817,60 @@ var getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
|
817
817
|
if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) {
|
|
818
818
|
return [itemId];
|
|
819
819
|
}
|
|
820
|
-
const descendants = tree.retrieveChildrenIds(itemId).map((child) => getAllLoadedDescendants(tree, child, includeFolders)).flat();
|
|
820
|
+
const descendants = tree.retrieveChildrenIds(itemId, true).map((child) => getAllLoadedDescendants(tree, child, includeFolders)).flat();
|
|
821
821
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
822
822
|
};
|
|
823
|
+
var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null, null, function* () {
|
|
824
|
+
yield tree.loadItemData(itemId);
|
|
825
|
+
if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) {
|
|
826
|
+
return [itemId];
|
|
827
|
+
}
|
|
828
|
+
const childrenIds = yield tree.loadChildrenIds(itemId);
|
|
829
|
+
const descendants = (yield Promise.all(
|
|
830
|
+
childrenIds.map(
|
|
831
|
+
(child) => getAllDescendants(tree, child, includeFolders)
|
|
832
|
+
)
|
|
833
|
+
)).flat();
|
|
834
|
+
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
835
|
+
});
|
|
836
|
+
var withLoadingState = (tree, itemId, callback) => __async(null, null, function* () {
|
|
837
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) => [
|
|
838
|
+
...items,
|
|
839
|
+
itemId
|
|
840
|
+
]);
|
|
841
|
+
try {
|
|
842
|
+
yield callback();
|
|
843
|
+
} finally {
|
|
844
|
+
tree.applySubStateUpdate(
|
|
845
|
+
"loadingCheckPropagationItems",
|
|
846
|
+
(items) => items.filter((id) => id !== itemId)
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
});
|
|
823
850
|
var checkboxesFeature = {
|
|
824
851
|
key: "checkboxes",
|
|
825
852
|
overwrites: ["selection"],
|
|
826
853
|
getInitialState: (initialState) => __spreadValues({
|
|
827
|
-
checkedItems: []
|
|
854
|
+
checkedItems: [],
|
|
855
|
+
loadingCheckPropagationItems: []
|
|
828
856
|
}, initialState),
|
|
829
857
|
getDefaultConfig: (defaultConfig, tree) => {
|
|
830
|
-
var _a, _b
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
);
|
|
834
|
-
if (hasAsyncLoader && defaultConfig.propagateCheckedState) {
|
|
835
|
-
throwError(`propagateCheckedState not supported with async trees`);
|
|
836
|
-
}
|
|
837
|
-
const propagateCheckedState = (_b = defaultConfig.propagateCheckedState) != null ? _b : !hasAsyncLoader;
|
|
838
|
-
const canCheckFolders = (_c = defaultConfig.canCheckFolders) != null ? _c : !propagateCheckedState;
|
|
858
|
+
var _a, _b;
|
|
859
|
+
const propagateCheckedState = (_a = defaultConfig.propagateCheckedState) != null ? _a : true;
|
|
860
|
+
const canCheckFolders = (_b = defaultConfig.canCheckFolders) != null ? _b : !propagateCheckedState;
|
|
839
861
|
return __spreadValues({
|
|
840
862
|
setCheckedItems: makeStateUpdater("checkedItems", tree),
|
|
863
|
+
setLoadingCheckPropagationItems: makeStateUpdater(
|
|
864
|
+
"loadingCheckPropagationItems",
|
|
865
|
+
tree
|
|
866
|
+
),
|
|
841
867
|
propagateCheckedState,
|
|
842
868
|
canCheckFolders
|
|
843
869
|
}, defaultConfig);
|
|
844
870
|
},
|
|
845
871
|
stateHandlerNames: {
|
|
846
|
-
checkedItems: "setCheckedItems"
|
|
872
|
+
checkedItems: "setCheckedItems",
|
|
873
|
+
loadingCheckPropagationItems: "setLoadingCheckPropagationItems"
|
|
847
874
|
},
|
|
848
875
|
treeInstance: {
|
|
849
876
|
setCheckedItems: ({ tree }, checkedItems) => {
|
|
@@ -863,13 +890,13 @@ var checkboxesFeature = {
|
|
|
863
890
|
}
|
|
864
891
|
};
|
|
865
892
|
},
|
|
866
|
-
toggleCheckedState: ({ item })
|
|
893
|
+
toggleCheckedState: (_0) => __async(null, [_0], function* ({ item }) {
|
|
867
894
|
if (item.getCheckedState() === "checked" /* Checked */) {
|
|
868
|
-
item.setUnchecked();
|
|
895
|
+
yield item.setUnchecked();
|
|
869
896
|
} else {
|
|
870
|
-
item.setChecked();
|
|
897
|
+
yield item.setChecked();
|
|
871
898
|
}
|
|
872
|
-
},
|
|
899
|
+
}),
|
|
873
900
|
getCheckedState: ({ item, tree }) => {
|
|
874
901
|
const { checkedItems } = tree.getState();
|
|
875
902
|
const { propagateCheckedState } = tree.getConfig();
|
|
@@ -879,6 +906,7 @@ var checkboxesFeature = {
|
|
|
879
906
|
}
|
|
880
907
|
if (item.isFolder() && propagateCheckedState) {
|
|
881
908
|
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
909
|
+
if (descendants.length === 0) return "unchecked" /* Unchecked */;
|
|
882
910
|
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
883
911
|
return "checked" /* Checked */;
|
|
884
912
|
}
|
|
@@ -888,36 +916,48 @@ var checkboxesFeature = {
|
|
|
888
916
|
}
|
|
889
917
|
return "unchecked" /* Unchecked */;
|
|
890
918
|
},
|
|
891
|
-
setChecked: ({ item, tree, itemId })
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
919
|
+
setChecked: (_0) => __async(null, [_0], function* ({ item, tree, itemId }) {
|
|
920
|
+
yield withLoadingState(tree, itemId, () => __async(null, null, function* () {
|
|
921
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
922
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
923
|
+
const descendants = yield getAllDescendants(
|
|
924
|
+
tree,
|
|
925
|
+
itemId,
|
|
926
|
+
canCheckFolders
|
|
927
|
+
);
|
|
928
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
929
|
+
...items,
|
|
930
|
+
...descendants
|
|
931
|
+
]);
|
|
932
|
+
} else if (!item.isFolder() || canCheckFolders) {
|
|
933
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
934
|
+
...items,
|
|
935
|
+
itemId
|
|
936
|
+
]);
|
|
937
|
+
}
|
|
938
|
+
}));
|
|
939
|
+
}),
|
|
940
|
+
setUnchecked: (_0) => __async(null, [_0], function* ({ item, tree, itemId }) {
|
|
941
|
+
yield withLoadingState(tree, itemId, () => __async(null, null, function* () {
|
|
942
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
943
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
944
|
+
const descendants = yield getAllDescendants(
|
|
945
|
+
tree,
|
|
946
|
+
itemId,
|
|
947
|
+
canCheckFolders
|
|
948
|
+
);
|
|
949
|
+
tree.applySubStateUpdate(
|
|
950
|
+
"checkedItems",
|
|
951
|
+
(items) => items.filter((id) => !descendants.includes(id) && id !== itemId)
|
|
952
|
+
);
|
|
953
|
+
} else {
|
|
954
|
+
tree.applySubStateUpdate(
|
|
955
|
+
"checkedItems",
|
|
956
|
+
(items) => items.filter((id) => id !== itemId)
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
}));
|
|
960
|
+
})
|
|
921
961
|
}
|
|
922
962
|
};
|
|
923
963
|
|
|
@@ -1035,6 +1075,12 @@ var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
|
1035
1075
|
var _a;
|
|
1036
1076
|
const config = tree.getConfig();
|
|
1037
1077
|
const dataRef = getDataRef(tree);
|
|
1078
|
+
if (!dataRef.current.itemData[itemId]) {
|
|
1079
|
+
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1080
|
+
...loadingItemData,
|
|
1081
|
+
itemId
|
|
1082
|
+
]);
|
|
1083
|
+
}
|
|
1038
1084
|
const item = yield config.dataLoader.getItem(itemId);
|
|
1039
1085
|
dataRef.current.itemData[itemId] = item;
|
|
1040
1086
|
(_a = config.onLoadedItem) == null ? void 0 : _a.call(config, itemId, item);
|
|
@@ -1049,6 +1095,12 @@ var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
|
1049
1095
|
const config = tree.getConfig();
|
|
1050
1096
|
const dataRef = getDataRef(tree);
|
|
1051
1097
|
let childrenIds;
|
|
1098
|
+
if (!dataRef.current.childrenIds[itemId]) {
|
|
1099
|
+
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [
|
|
1100
|
+
...loadingItemChildrens,
|
|
1101
|
+
itemId
|
|
1102
|
+
]);
|
|
1103
|
+
}
|
|
1052
1104
|
if ("getChildrenWithData" in config.dataLoader) {
|
|
1053
1105
|
const children = yield config.dataLoader.getChildrenWithData(itemId);
|
|
1054
1106
|
childrenIds = children.map((c) => c.id);
|
|
@@ -1109,10 +1161,6 @@ var asyncDataLoaderFeature = {
|
|
|
1109
1161
|
return dataRef.current.itemData[itemId];
|
|
1110
1162
|
}
|
|
1111
1163
|
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
1112
|
-
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1113
|
-
...loadingItemData,
|
|
1114
|
-
itemId
|
|
1115
|
-
]);
|
|
1116
1164
|
loadItemData(tree, itemId);
|
|
1117
1165
|
}
|
|
1118
1166
|
return (_b = (_a = config.createLoadingItemData) == null ? void 0 : _a.call(config)) != null ? _b : null;
|
|
@@ -1125,10 +1173,6 @@ var asyncDataLoaderFeature = {
|
|
|
1125
1173
|
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
1126
1174
|
return [];
|
|
1127
1175
|
}
|
|
1128
|
-
tree.applySubStateUpdate(
|
|
1129
|
-
"loadingItemChildrens",
|
|
1130
|
-
(loadingItemChildrens) => [...loadingItemChildrens, itemId]
|
|
1131
|
-
);
|
|
1132
1176
|
loadChildrenIds(tree, itemId);
|
|
1133
1177
|
return [];
|
|
1134
1178
|
}
|
|
@@ -1139,10 +1183,6 @@ var asyncDataLoaderFeature = {
|
|
|
1139
1183
|
var _a;
|
|
1140
1184
|
if (!optimistic) {
|
|
1141
1185
|
(_a = getDataRef(tree).current.itemData) == null ? true : delete _a[itemId];
|
|
1142
|
-
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1143
|
-
...loadingItemData,
|
|
1144
|
-
itemId
|
|
1145
|
-
]);
|
|
1146
1186
|
}
|
|
1147
1187
|
yield loadItemData(tree, itemId);
|
|
1148
1188
|
}),
|
|
@@ -1150,10 +1190,6 @@ var asyncDataLoaderFeature = {
|
|
|
1150
1190
|
var _a;
|
|
1151
1191
|
if (!optimistic) {
|
|
1152
1192
|
(_a = getDataRef(tree).current.childrenIds) == null ? true : delete _a[itemId];
|
|
1153
|
-
tree.applySubStateUpdate(
|
|
1154
|
-
"loadingItemChildrens",
|
|
1155
|
-
(loadingItemChildrens) => [...loadingItemChildrens, itemId]
|
|
1156
|
-
);
|
|
1157
1193
|
}
|
|
1158
1194
|
yield loadChildrenIds(tree, itemId);
|
|
1159
1195
|
}),
|
package/dist/index.mjs
CHANGED
|
@@ -773,33 +773,60 @@ var getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
|
773
773
|
if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) {
|
|
774
774
|
return [itemId];
|
|
775
775
|
}
|
|
776
|
-
const descendants = tree.retrieveChildrenIds(itemId).map((child) => getAllLoadedDescendants(tree, child, includeFolders)).flat();
|
|
776
|
+
const descendants = tree.retrieveChildrenIds(itemId, true).map((child) => getAllLoadedDescendants(tree, child, includeFolders)).flat();
|
|
777
777
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
778
778
|
};
|
|
779
|
+
var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null, null, function* () {
|
|
780
|
+
yield tree.loadItemData(itemId);
|
|
781
|
+
if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) {
|
|
782
|
+
return [itemId];
|
|
783
|
+
}
|
|
784
|
+
const childrenIds = yield tree.loadChildrenIds(itemId);
|
|
785
|
+
const descendants = (yield Promise.all(
|
|
786
|
+
childrenIds.map(
|
|
787
|
+
(child) => getAllDescendants(tree, child, includeFolders)
|
|
788
|
+
)
|
|
789
|
+
)).flat();
|
|
790
|
+
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
791
|
+
});
|
|
792
|
+
var withLoadingState = (tree, itemId, callback) => __async(null, null, function* () {
|
|
793
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) => [
|
|
794
|
+
...items,
|
|
795
|
+
itemId
|
|
796
|
+
]);
|
|
797
|
+
try {
|
|
798
|
+
yield callback();
|
|
799
|
+
} finally {
|
|
800
|
+
tree.applySubStateUpdate(
|
|
801
|
+
"loadingCheckPropagationItems",
|
|
802
|
+
(items) => items.filter((id) => id !== itemId)
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
});
|
|
779
806
|
var checkboxesFeature = {
|
|
780
807
|
key: "checkboxes",
|
|
781
808
|
overwrites: ["selection"],
|
|
782
809
|
getInitialState: (initialState) => __spreadValues({
|
|
783
|
-
checkedItems: []
|
|
810
|
+
checkedItems: [],
|
|
811
|
+
loadingCheckPropagationItems: []
|
|
784
812
|
}, initialState),
|
|
785
813
|
getDefaultConfig: (defaultConfig, tree) => {
|
|
786
|
-
var _a, _b
|
|
787
|
-
const
|
|
788
|
-
|
|
789
|
-
);
|
|
790
|
-
if (hasAsyncLoader && defaultConfig.propagateCheckedState) {
|
|
791
|
-
throwError(`propagateCheckedState not supported with async trees`);
|
|
792
|
-
}
|
|
793
|
-
const propagateCheckedState = (_b = defaultConfig.propagateCheckedState) != null ? _b : !hasAsyncLoader;
|
|
794
|
-
const canCheckFolders = (_c = defaultConfig.canCheckFolders) != null ? _c : !propagateCheckedState;
|
|
814
|
+
var _a, _b;
|
|
815
|
+
const propagateCheckedState = (_a = defaultConfig.propagateCheckedState) != null ? _a : true;
|
|
816
|
+
const canCheckFolders = (_b = defaultConfig.canCheckFolders) != null ? _b : !propagateCheckedState;
|
|
795
817
|
return __spreadValues({
|
|
796
818
|
setCheckedItems: makeStateUpdater("checkedItems", tree),
|
|
819
|
+
setLoadingCheckPropagationItems: makeStateUpdater(
|
|
820
|
+
"loadingCheckPropagationItems",
|
|
821
|
+
tree
|
|
822
|
+
),
|
|
797
823
|
propagateCheckedState,
|
|
798
824
|
canCheckFolders
|
|
799
825
|
}, defaultConfig);
|
|
800
826
|
},
|
|
801
827
|
stateHandlerNames: {
|
|
802
|
-
checkedItems: "setCheckedItems"
|
|
828
|
+
checkedItems: "setCheckedItems",
|
|
829
|
+
loadingCheckPropagationItems: "setLoadingCheckPropagationItems"
|
|
803
830
|
},
|
|
804
831
|
treeInstance: {
|
|
805
832
|
setCheckedItems: ({ tree }, checkedItems) => {
|
|
@@ -819,13 +846,13 @@ var checkboxesFeature = {
|
|
|
819
846
|
}
|
|
820
847
|
};
|
|
821
848
|
},
|
|
822
|
-
toggleCheckedState: ({ item })
|
|
849
|
+
toggleCheckedState: (_0) => __async(null, [_0], function* ({ item }) {
|
|
823
850
|
if (item.getCheckedState() === "checked" /* Checked */) {
|
|
824
|
-
item.setUnchecked();
|
|
851
|
+
yield item.setUnchecked();
|
|
825
852
|
} else {
|
|
826
|
-
item.setChecked();
|
|
853
|
+
yield item.setChecked();
|
|
827
854
|
}
|
|
828
|
-
},
|
|
855
|
+
}),
|
|
829
856
|
getCheckedState: ({ item, tree }) => {
|
|
830
857
|
const { checkedItems } = tree.getState();
|
|
831
858
|
const { propagateCheckedState } = tree.getConfig();
|
|
@@ -835,6 +862,7 @@ var checkboxesFeature = {
|
|
|
835
862
|
}
|
|
836
863
|
if (item.isFolder() && propagateCheckedState) {
|
|
837
864
|
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
865
|
+
if (descendants.length === 0) return "unchecked" /* Unchecked */;
|
|
838
866
|
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
839
867
|
return "checked" /* Checked */;
|
|
840
868
|
}
|
|
@@ -844,36 +872,48 @@ var checkboxesFeature = {
|
|
|
844
872
|
}
|
|
845
873
|
return "unchecked" /* Unchecked */;
|
|
846
874
|
},
|
|
847
|
-
setChecked: ({ item, tree, itemId })
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
875
|
+
setChecked: (_0) => __async(null, [_0], function* ({ item, tree, itemId }) {
|
|
876
|
+
yield withLoadingState(tree, itemId, () => __async(null, null, function* () {
|
|
877
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
878
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
879
|
+
const descendants = yield getAllDescendants(
|
|
880
|
+
tree,
|
|
881
|
+
itemId,
|
|
882
|
+
canCheckFolders
|
|
883
|
+
);
|
|
884
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
885
|
+
...items,
|
|
886
|
+
...descendants
|
|
887
|
+
]);
|
|
888
|
+
} else if (!item.isFolder() || canCheckFolders) {
|
|
889
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
890
|
+
...items,
|
|
891
|
+
itemId
|
|
892
|
+
]);
|
|
893
|
+
}
|
|
894
|
+
}));
|
|
895
|
+
}),
|
|
896
|
+
setUnchecked: (_0) => __async(null, [_0], function* ({ item, tree, itemId }) {
|
|
897
|
+
yield withLoadingState(tree, itemId, () => __async(null, null, function* () {
|
|
898
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
899
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
900
|
+
const descendants = yield getAllDescendants(
|
|
901
|
+
tree,
|
|
902
|
+
itemId,
|
|
903
|
+
canCheckFolders
|
|
904
|
+
);
|
|
905
|
+
tree.applySubStateUpdate(
|
|
906
|
+
"checkedItems",
|
|
907
|
+
(items) => items.filter((id) => !descendants.includes(id) && id !== itemId)
|
|
908
|
+
);
|
|
909
|
+
} else {
|
|
910
|
+
tree.applySubStateUpdate(
|
|
911
|
+
"checkedItems",
|
|
912
|
+
(items) => items.filter((id) => id !== itemId)
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
}));
|
|
916
|
+
})
|
|
877
917
|
}
|
|
878
918
|
};
|
|
879
919
|
|
|
@@ -991,6 +1031,12 @@ var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
|
991
1031
|
var _a;
|
|
992
1032
|
const config = tree.getConfig();
|
|
993
1033
|
const dataRef = getDataRef(tree);
|
|
1034
|
+
if (!dataRef.current.itemData[itemId]) {
|
|
1035
|
+
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1036
|
+
...loadingItemData,
|
|
1037
|
+
itemId
|
|
1038
|
+
]);
|
|
1039
|
+
}
|
|
994
1040
|
const item = yield config.dataLoader.getItem(itemId);
|
|
995
1041
|
dataRef.current.itemData[itemId] = item;
|
|
996
1042
|
(_a = config.onLoadedItem) == null ? void 0 : _a.call(config, itemId, item);
|
|
@@ -1005,6 +1051,12 @@ var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
|
1005
1051
|
const config = tree.getConfig();
|
|
1006
1052
|
const dataRef = getDataRef(tree);
|
|
1007
1053
|
let childrenIds;
|
|
1054
|
+
if (!dataRef.current.childrenIds[itemId]) {
|
|
1055
|
+
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [
|
|
1056
|
+
...loadingItemChildrens,
|
|
1057
|
+
itemId
|
|
1058
|
+
]);
|
|
1059
|
+
}
|
|
1008
1060
|
if ("getChildrenWithData" in config.dataLoader) {
|
|
1009
1061
|
const children = yield config.dataLoader.getChildrenWithData(itemId);
|
|
1010
1062
|
childrenIds = children.map((c) => c.id);
|
|
@@ -1065,10 +1117,6 @@ var asyncDataLoaderFeature = {
|
|
|
1065
1117
|
return dataRef.current.itemData[itemId];
|
|
1066
1118
|
}
|
|
1067
1119
|
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
1068
|
-
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1069
|
-
...loadingItemData,
|
|
1070
|
-
itemId
|
|
1071
|
-
]);
|
|
1072
1120
|
loadItemData(tree, itemId);
|
|
1073
1121
|
}
|
|
1074
1122
|
return (_b = (_a = config.createLoadingItemData) == null ? void 0 : _a.call(config)) != null ? _b : null;
|
|
@@ -1081,10 +1129,6 @@ var asyncDataLoaderFeature = {
|
|
|
1081
1129
|
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
1082
1130
|
return [];
|
|
1083
1131
|
}
|
|
1084
|
-
tree.applySubStateUpdate(
|
|
1085
|
-
"loadingItemChildrens",
|
|
1086
|
-
(loadingItemChildrens) => [...loadingItemChildrens, itemId]
|
|
1087
|
-
);
|
|
1088
1132
|
loadChildrenIds(tree, itemId);
|
|
1089
1133
|
return [];
|
|
1090
1134
|
}
|
|
@@ -1095,10 +1139,6 @@ var asyncDataLoaderFeature = {
|
|
|
1095
1139
|
var _a;
|
|
1096
1140
|
if (!optimistic) {
|
|
1097
1141
|
(_a = getDataRef(tree).current.itemData) == null ? true : delete _a[itemId];
|
|
1098
|
-
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1099
|
-
...loadingItemData,
|
|
1100
|
-
itemId
|
|
1101
|
-
]);
|
|
1102
1142
|
}
|
|
1103
1143
|
yield loadItemData(tree, itemId);
|
|
1104
1144
|
}),
|
|
@@ -1106,10 +1146,6 @@ var asyncDataLoaderFeature = {
|
|
|
1106
1146
|
var _a;
|
|
1107
1147
|
if (!optimistic) {
|
|
1108
1148
|
(_a = getDataRef(tree).current.childrenIds) == null ? true : delete _a[itemId];
|
|
1109
|
-
tree.applySubStateUpdate(
|
|
1110
|
-
"loadingItemChildrens",
|
|
1111
|
-
(loadingItemChildrens) => [...loadingItemChildrens, itemId]
|
|
1112
|
-
);
|
|
1113
1149
|
}
|
|
1114
1150
|
yield loadChildrenIds(tree, itemId);
|
|
1115
1151
|
}),
|
package/package.json
CHANGED
|
@@ -13,6 +13,13 @@ const loadItemData = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
|
13
13
|
const config = tree.getConfig();
|
|
14
14
|
const dataRef = getDataRef(tree);
|
|
15
15
|
|
|
16
|
+
if (!dataRef.current.itemData[itemId]) {
|
|
17
|
+
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
18
|
+
...loadingItemData,
|
|
19
|
+
itemId,
|
|
20
|
+
]);
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
const item = await config.dataLoader.getItem(itemId);
|
|
17
24
|
dataRef.current.itemData[itemId] = item;
|
|
18
25
|
config.onLoadedItem?.(itemId, item);
|
|
@@ -28,6 +35,13 @@ const loadChildrenIds = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
|
28
35
|
const dataRef = getDataRef(tree);
|
|
29
36
|
let childrenIds: string[];
|
|
30
37
|
|
|
38
|
+
if (!dataRef.current.childrenIds[itemId]) {
|
|
39
|
+
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [
|
|
40
|
+
...loadingItemChildrens,
|
|
41
|
+
itemId,
|
|
42
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
if ("getChildrenWithData" in config.dataLoader) {
|
|
32
46
|
const children = await config.dataLoader.getChildrenWithData(itemId);
|
|
33
47
|
childrenIds = children.map((c) => c.id);
|
|
@@ -104,11 +118,6 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
104
118
|
}
|
|
105
119
|
|
|
106
120
|
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
107
|
-
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
108
|
-
...loadingItemData,
|
|
109
|
-
itemId,
|
|
110
|
-
]);
|
|
111
|
-
|
|
112
121
|
loadItemData(tree, itemId);
|
|
113
122
|
}
|
|
114
123
|
|
|
@@ -125,11 +134,6 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
125
134
|
return [];
|
|
126
135
|
}
|
|
127
136
|
|
|
128
|
-
tree.applySubStateUpdate(
|
|
129
|
-
"loadingItemChildrens",
|
|
130
|
-
(loadingItemChildrens) => [...loadingItemChildrens, itemId],
|
|
131
|
-
);
|
|
132
|
-
|
|
133
137
|
loadChildrenIds(tree, itemId);
|
|
134
138
|
|
|
135
139
|
return [];
|
|
@@ -143,20 +147,12 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
143
147
|
invalidateItemData: async ({ tree, itemId }, optimistic) => {
|
|
144
148
|
if (!optimistic) {
|
|
145
149
|
delete getDataRef(tree).current.itemData?.[itemId];
|
|
146
|
-
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
147
|
-
...loadingItemData,
|
|
148
|
-
itemId,
|
|
149
|
-
]);
|
|
150
150
|
}
|
|
151
151
|
await loadItemData(tree, itemId);
|
|
152
152
|
},
|
|
153
153
|
invalidateChildrenIds: async ({ tree, itemId }, optimistic) => {
|
|
154
154
|
if (!optimistic) {
|
|
155
155
|
delete getDataRef(tree).current.childrenIds?.[itemId];
|
|
156
|
-
tree.applySubStateUpdate(
|
|
157
|
-
"loadingItemChildrens",
|
|
158
|
-
(loadingItemChildrens) => [...loadingItemChildrens, itemId],
|
|
159
|
-
);
|
|
160
156
|
}
|
|
161
157
|
await loadChildrenIds(tree, itemId);
|
|
162
158
|
},
|
|
@@ -47,7 +47,8 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
47
47
|
* the tree will continue to display the old data until the new data has loaded. */
|
|
48
48
|
invalidateChildrenIds: (optimistic?: boolean) => Promise<void>;
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
/** Set to undefined to clear cache without triggering automatic refetch. Use @invalidateItemData to clear and triggering refetch. */
|
|
51
|
+
updateCachedData: (data: T | undefined) => void;
|
|
51
52
|
updateCachedChildrenIds: (childrenIds: string[]) => void;
|
|
52
53
|
isLoading: () => boolean;
|
|
53
54
|
};
|
|
@@ -3,147 +3,136 @@ import { TestTree } from "../../test-utils/test-tree";
|
|
|
3
3
|
import { checkboxesFeature } from "./feature";
|
|
4
4
|
import { CheckedState } from "./types";
|
|
5
5
|
|
|
6
|
-
const factory = TestTree.default({
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const factory = TestTree.default({
|
|
7
|
+
propagateCheckedState: true,
|
|
8
|
+
canCheckFolders: false,
|
|
9
|
+
}).withFeatures(checkboxesFeature);
|
|
9
10
|
|
|
10
11
|
describe("core-feature/checkboxes", () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
it("should check items", async () => {
|
|
17
|
-
const tree = await factory.createTestCaseTree();
|
|
18
|
-
tree.item("x111").setChecked();
|
|
19
|
-
tree.item("x112").setChecked();
|
|
20
|
-
expect(tree.instance.getState().checkedItems).toEqual(["x111", "x112"]);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("should uncheck an item", async () => {
|
|
24
|
-
const tree = await factory
|
|
25
|
-
.with({ state: { checkedItems: ["x111"] } })
|
|
26
|
-
.createTestCaseTree();
|
|
27
|
-
tree.item("x111").setUnchecked();
|
|
28
|
-
expect(tree.instance.getState().checkedItems).not.toContain("x111");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("should toggle checked state", async () => {
|
|
32
|
-
const tree = await factory.createTestCaseTree();
|
|
33
|
-
const item = tree.item("x111");
|
|
12
|
+
factory.forSuits((tree) => {
|
|
13
|
+
it("should initialize with no checked items", async () => {
|
|
14
|
+
expect(tree.instance.getState().checkedItems).toEqual([]);
|
|
15
|
+
});
|
|
34
16
|
|
|
35
|
-
|
|
36
|
-
|
|
17
|
+
it("should check items", async () => {
|
|
18
|
+
await tree.item("x111").setChecked();
|
|
19
|
+
await tree.item("x112").setChecked();
|
|
20
|
+
expect(tree.instance.getState().checkedItems).toEqual(["x111", "x112"]);
|
|
21
|
+
});
|
|
37
22
|
|
|
38
|
-
item
|
|
39
|
-
|
|
40
|
-
|
|
23
|
+
it("should uncheck an item", async () => {
|
|
24
|
+
await tree.item("x111").setChecked();
|
|
25
|
+
await tree.item("x111").setUnchecked();
|
|
26
|
+
expect(tree.instance.getState().checkedItems).not.toContain("x111");
|
|
27
|
+
});
|
|
41
28
|
|
|
42
|
-
describe("props", () => {
|
|
43
29
|
it("should toggle checked state", async () => {
|
|
44
|
-
const tree = await factory.createTestCaseTree();
|
|
45
30
|
const item = tree.item("x111");
|
|
46
|
-
|
|
47
|
-
item.getCheckboxProps().onChange();
|
|
31
|
+
await item.toggleCheckedState();
|
|
48
32
|
expect(tree.instance.getState().checkedItems).toContain("x111");
|
|
49
|
-
|
|
50
|
-
item.getCheckboxProps().onChange();
|
|
33
|
+
await item.toggleCheckedState();
|
|
51
34
|
expect(tree.instance.getState().checkedItems).not.toContain("x111");
|
|
52
35
|
});
|
|
53
36
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
37
|
+
describe("props", () => {
|
|
38
|
+
it("should toggle checked state", async () => {
|
|
39
|
+
const item = tree.item("x111");
|
|
40
|
+
item.getCheckboxProps().onChange();
|
|
41
|
+
expect(tree.instance.getState().checkedItems).toContain("x111");
|
|
42
|
+
item.getCheckboxProps().onChange();
|
|
43
|
+
expect(tree.instance.getState().checkedItems).not.toContain("x111");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should return checked state in props", async () => {
|
|
47
|
+
tree.item("x111").setChecked();
|
|
48
|
+
expect(tree.item("x111").getCheckboxProps().checked).toBe(true);
|
|
49
|
+
expect(tree.item("x112").getCheckboxProps().checked).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should create indeterminate state", async () => {
|
|
53
|
+
await tree.item("x111").setChecked();
|
|
54
|
+
const refObject = { indeterminate: undefined };
|
|
55
|
+
tree.item("x11").getCheckboxProps().ref(refObject);
|
|
56
|
+
expect(refObject.indeterminate).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should not create indeterminate state", async () => {
|
|
60
|
+
const refObject = { indeterminate: undefined };
|
|
61
|
+
tree.item("x11").getCheckboxProps().ref(refObject);
|
|
62
|
+
expect(refObject.indeterminate).toBe(false);
|
|
63
|
+
});
|
|
59
64
|
});
|
|
60
65
|
|
|
61
|
-
it("should
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
expect(
|
|
66
|
+
it("should handle folder checking", async () => {
|
|
67
|
+
const testTree = await tree
|
|
68
|
+
.with({ canCheckFolders: true, propagateCheckedState: false })
|
|
69
|
+
.createTestCaseTree();
|
|
70
|
+
testTree.item("x11").setChecked();
|
|
71
|
+
expect(testTree.instance.getState().checkedItems).toContain("x11");
|
|
67
72
|
});
|
|
68
73
|
|
|
69
|
-
it("should not
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
it("should not check folders if disabled", async () => {
|
|
75
|
+
const testTree = await tree
|
|
76
|
+
.with({ canCheckFolders: false, propagateCheckedState: false })
|
|
77
|
+
.createTestCaseTree();
|
|
78
|
+
testTree.item("x11").setChecked();
|
|
79
|
+
expect(testTree.instance.getState().checkedItems.length).toBe(0);
|
|
74
80
|
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("should handle folder checking", async () => {
|
|
78
|
-
const tree = await factory
|
|
79
|
-
.with({ canCheckFolders: true, propagateCheckedState: false })
|
|
80
|
-
.createTestCaseTree();
|
|
81
|
-
|
|
82
|
-
tree.item("x11").setChecked();
|
|
83
|
-
expect(tree.instance.getState().checkedItems).toContain("x11");
|
|
84
|
-
});
|
|
85
81
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
it("should propagate checked state", async () => {
|
|
96
|
-
const tree = await factory
|
|
97
|
-
.with({ propagateCheckedState: true })
|
|
98
|
-
.createTestCaseTree();
|
|
99
|
-
|
|
100
|
-
tree.item("x11").setChecked();
|
|
101
|
-
expect(tree.instance.getState().checkedItems).toEqual(
|
|
102
|
-
expect.arrayContaining(["x111", "x112", "x113", "x114"]),
|
|
103
|
-
);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("should turn folder indeterminate", async () => {
|
|
107
|
-
const tree = await factory
|
|
108
|
-
.with({ propagateCheckedState: true })
|
|
109
|
-
.createTestCaseTree();
|
|
110
|
-
|
|
111
|
-
tree.item("x111").setChecked();
|
|
112
|
-
expect(tree.item("x11").getCheckedState()).toBe(CheckedState.Indeterminate);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("should turn folder checked if all children are checked", async () => {
|
|
116
|
-
const tree = await factory
|
|
117
|
-
.with({
|
|
118
|
-
isItemFolder: (item) => item.getItemData().length < 4,
|
|
119
|
-
propagateCheckedState: true,
|
|
120
|
-
canCheckFolders: false,
|
|
121
|
-
})
|
|
122
|
-
.createTestCaseTree();
|
|
123
|
-
|
|
124
|
-
tree.item("x11").setChecked();
|
|
125
|
-
tree.item("x12").setChecked();
|
|
126
|
-
tree.item("x13").setChecked();
|
|
127
|
-
expect(tree.item("x1").getCheckedState()).toBe(CheckedState.Indeterminate);
|
|
128
|
-
tree.do.selectItem("x14");
|
|
129
|
-
tree.item("x141").setChecked();
|
|
130
|
-
tree.item("x142").setChecked();
|
|
131
|
-
tree.item("x143").setChecked();
|
|
132
|
-
expect(tree.item("x1").getCheckedState()).toBe(CheckedState.Indeterminate);
|
|
133
|
-
tree.item("x144").setChecked();
|
|
134
|
-
expect(tree.item("x1").getCheckedState()).toBe(CheckedState.Checked);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it("should return correct checked state for items", async () => {
|
|
138
|
-
const tree = await factory.createTestCaseTree();
|
|
139
|
-
const item = tree.instance.getItemInstance("x111");
|
|
82
|
+
it("should propagate checked state", async () => {
|
|
83
|
+
const testTree = await tree
|
|
84
|
+
.with({ propagateCheckedState: true })
|
|
85
|
+
.createTestCaseTree();
|
|
86
|
+
await testTree.item("x11").setChecked();
|
|
87
|
+
expect(testTree.instance.getState().checkedItems).toEqual(
|
|
88
|
+
expect.arrayContaining(["x111", "x112", "x113", "x114"]),
|
|
89
|
+
);
|
|
90
|
+
});
|
|
140
91
|
|
|
141
|
-
|
|
92
|
+
it("should turn folder indeterminate", async () => {
|
|
93
|
+
const testTree = await tree
|
|
94
|
+
.with({ propagateCheckedState: true })
|
|
95
|
+
.createTestCaseTree();
|
|
96
|
+
testTree.item("x111").setChecked();
|
|
97
|
+
expect(testTree.item("x11").getCheckedState()).toBe(
|
|
98
|
+
CheckedState.Indeterminate,
|
|
99
|
+
);
|
|
100
|
+
});
|
|
142
101
|
|
|
143
|
-
|
|
144
|
-
|
|
102
|
+
it("should turn folder checked if all children are checked", async () => {
|
|
103
|
+
const testTree = await tree
|
|
104
|
+
.with({
|
|
105
|
+
isItemFolder: (item: any) => item.getItemData().length < 4,
|
|
106
|
+
propagateCheckedState: true,
|
|
107
|
+
canCheckFolders: false,
|
|
108
|
+
})
|
|
109
|
+
.createTestCaseTree();
|
|
110
|
+
testTree.do.selectItem("x14"); // all leafs must be loaded initially, checkpropagation check only respects visibly loaded items
|
|
111
|
+
// TODO ^ might be a restriction we want to avoid
|
|
112
|
+
await testTree.resolveAsyncVisibleItems();
|
|
113
|
+
await testTree.runWhileResolvingItems(testTree.item("x11").setChecked);
|
|
114
|
+
await testTree.runWhileResolvingItems(testTree.item("x12").setChecked);
|
|
115
|
+
await testTree.runWhileResolvingItems(testTree.item("x13").setChecked);
|
|
116
|
+
expect(testTree.item("x1").getCheckedState()).toBe(
|
|
117
|
+
CheckedState.Indeterminate,
|
|
118
|
+
);
|
|
119
|
+
await testTree.runWhileResolvingItems(testTree.item("x141").setChecked);
|
|
120
|
+
await testTree.runWhileResolvingItems(testTree.item("x142").setChecked);
|
|
121
|
+
await testTree.runWhileResolvingItems(testTree.item("x143").setChecked);
|
|
122
|
+
expect(testTree.item("x1").getCheckedState()).toBe(
|
|
123
|
+
CheckedState.Indeterminate,
|
|
124
|
+
);
|
|
125
|
+
await testTree.runWhileResolvingItems(testTree.item("x144").setChecked);
|
|
126
|
+
expect(testTree.item("x1").getCheckedState()).toBe(CheckedState.Checked);
|
|
127
|
+
});
|
|
145
128
|
|
|
146
|
-
|
|
147
|
-
|
|
129
|
+
it("should return correct checked state for items", async () => {
|
|
130
|
+
const item = tree.instance.getItemInstance("x111");
|
|
131
|
+
expect(item.getCheckedState()).toBe(CheckedState.Unchecked);
|
|
132
|
+
item.setChecked();
|
|
133
|
+
expect(item.getCheckedState()).toBe(CheckedState.Checked);
|
|
134
|
+
item.setUnchecked();
|
|
135
|
+
expect(item.getCheckedState()).toBe(CheckedState.Unchecked);
|
|
136
|
+
});
|
|
148
137
|
});
|
|
149
138
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { FeatureImplementation, TreeInstance } from "../../types/core";
|
|
1
|
+
import { type FeatureImplementation, TreeInstance } from "../../types/core";
|
|
2
2
|
import { makeStateUpdater } from "../../utils";
|
|
3
3
|
import { CheckedState } from "./types";
|
|
4
|
-
import { throwError } from "../../utilities/errors";
|
|
5
4
|
|
|
6
5
|
const getAllLoadedDescendants = <T>(
|
|
7
6
|
tree: TreeInstance<T>,
|
|
@@ -12,12 +11,50 @@ const getAllLoadedDescendants = <T>(
|
|
|
12
11
|
return [itemId];
|
|
13
12
|
}
|
|
14
13
|
const descendants = tree
|
|
15
|
-
.retrieveChildrenIds(itemId)
|
|
14
|
+
.retrieveChildrenIds(itemId, true)
|
|
16
15
|
.map((child) => getAllLoadedDescendants(tree, child, includeFolders))
|
|
17
16
|
.flat();
|
|
18
17
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
19
18
|
};
|
|
20
19
|
|
|
20
|
+
const getAllDescendants = async <T>(
|
|
21
|
+
tree: TreeInstance<T>,
|
|
22
|
+
itemId: string,
|
|
23
|
+
includeFolders = false,
|
|
24
|
+
): Promise<string[]> => {
|
|
25
|
+
await tree.loadItemData(itemId);
|
|
26
|
+
if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) {
|
|
27
|
+
return [itemId];
|
|
28
|
+
}
|
|
29
|
+
const childrenIds = await tree.loadChildrenIds(itemId);
|
|
30
|
+
const descendants = (
|
|
31
|
+
await Promise.all(
|
|
32
|
+
childrenIds.map((child) =>
|
|
33
|
+
getAllDescendants(tree, child, includeFolders),
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
).flat();
|
|
37
|
+
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const withLoadingState = async <T>(
|
|
41
|
+
tree: TreeInstance<T>,
|
|
42
|
+
itemId: string,
|
|
43
|
+
callback: () => Promise<void>,
|
|
44
|
+
) => {
|
|
45
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) => [
|
|
46
|
+
...items,
|
|
47
|
+
itemId,
|
|
48
|
+
]);
|
|
49
|
+
try {
|
|
50
|
+
await callback();
|
|
51
|
+
} finally {
|
|
52
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) =>
|
|
53
|
+
items.filter((id) => id !== itemId),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
21
58
|
export const checkboxesFeature: FeatureImplementation = {
|
|
22
59
|
key: "checkboxes",
|
|
23
60
|
|
|
@@ -25,22 +62,20 @@ export const checkboxesFeature: FeatureImplementation = {
|
|
|
25
62
|
|
|
26
63
|
getInitialState: (initialState) => ({
|
|
27
64
|
checkedItems: [],
|
|
65
|
+
loadingCheckPropagationItems: [],
|
|
28
66
|
...initialState,
|
|
29
67
|
}),
|
|
30
68
|
|
|
31
69
|
getDefaultConfig: (defaultConfig, tree) => {
|
|
32
|
-
const
|
|
33
|
-
(f) => f.key === "async-data-loader",
|
|
34
|
-
);
|
|
35
|
-
if (hasAsyncLoader && defaultConfig.propagateCheckedState) {
|
|
36
|
-
throwError(`propagateCheckedState not supported with async trees`);
|
|
37
|
-
}
|
|
38
|
-
const propagateCheckedState =
|
|
39
|
-
defaultConfig.propagateCheckedState ?? !hasAsyncLoader;
|
|
70
|
+
const propagateCheckedState = defaultConfig.propagateCheckedState ?? true;
|
|
40
71
|
const canCheckFolders =
|
|
41
72
|
defaultConfig.canCheckFolders ?? !propagateCheckedState;
|
|
42
73
|
return {
|
|
43
74
|
setCheckedItems: makeStateUpdater("checkedItems", tree),
|
|
75
|
+
setLoadingCheckPropagationItems: makeStateUpdater(
|
|
76
|
+
"loadingCheckPropagationItems",
|
|
77
|
+
tree,
|
|
78
|
+
),
|
|
44
79
|
propagateCheckedState,
|
|
45
80
|
canCheckFolders,
|
|
46
81
|
...defaultConfig,
|
|
@@ -49,6 +84,7 @@ export const checkboxesFeature: FeatureImplementation = {
|
|
|
49
84
|
|
|
50
85
|
stateHandlerNames: {
|
|
51
86
|
checkedItems: "setCheckedItems",
|
|
87
|
+
loadingCheckPropagationItems: "setLoadingCheckPropagationItems",
|
|
52
88
|
},
|
|
53
89
|
|
|
54
90
|
treeInstance: {
|
|
@@ -71,11 +107,11 @@ export const checkboxesFeature: FeatureImplementation = {
|
|
|
71
107
|
};
|
|
72
108
|
},
|
|
73
109
|
|
|
74
|
-
toggleCheckedState: ({ item }) => {
|
|
110
|
+
toggleCheckedState: async ({ item }) => {
|
|
75
111
|
if (item.getCheckedState() === CheckedState.Checked) {
|
|
76
|
-
item.setUnchecked();
|
|
112
|
+
await item.setUnchecked();
|
|
77
113
|
} else {
|
|
78
|
-
item.setChecked();
|
|
114
|
+
await item.setChecked();
|
|
79
115
|
}
|
|
80
116
|
},
|
|
81
117
|
|
|
@@ -90,6 +126,7 @@ export const checkboxesFeature: FeatureImplementation = {
|
|
|
90
126
|
|
|
91
127
|
if (item.isFolder() && propagateCheckedState) {
|
|
92
128
|
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
129
|
+
if (descendants.length === 0) return CheckedState.Unchecked;
|
|
93
130
|
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
94
131
|
return CheckedState.Checked;
|
|
95
132
|
}
|
|
@@ -101,34 +138,46 @@ export const checkboxesFeature: FeatureImplementation = {
|
|
|
101
138
|
return CheckedState.Unchecked;
|
|
102
139
|
},
|
|
103
140
|
|
|
104
|
-
setChecked: ({ item, tree, itemId }) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
141
|
+
setChecked: async ({ item, tree, itemId }) => {
|
|
142
|
+
await withLoadingState(tree, itemId, async () => {
|
|
143
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
144
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
145
|
+
const descendants = await getAllDescendants(
|
|
146
|
+
tree,
|
|
147
|
+
itemId,
|
|
148
|
+
canCheckFolders,
|
|
149
|
+
);
|
|
150
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
151
|
+
...items,
|
|
152
|
+
...descendants,
|
|
153
|
+
]);
|
|
154
|
+
} else if (!item.isFolder() || canCheckFolders) {
|
|
155
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
156
|
+
...items,
|
|
157
|
+
itemId,
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
114
161
|
},
|
|
115
162
|
|
|
116
|
-
setUnchecked: ({ item, tree, itemId }) => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
163
|
+
setUnchecked: async ({ item, tree, itemId }) => {
|
|
164
|
+
await withLoadingState(tree, itemId, async () => {
|
|
165
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
166
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
167
|
+
const descendants = await getAllDescendants(
|
|
168
|
+
tree,
|
|
169
|
+
itemId,
|
|
170
|
+
canCheckFolders,
|
|
171
|
+
);
|
|
172
|
+
tree.applySubStateUpdate("checkedItems", (items) =>
|
|
173
|
+
items.filter((id) => !descendants.includes(id) && id !== itemId),
|
|
174
|
+
);
|
|
175
|
+
} else {
|
|
176
|
+
tree.applySubStateUpdate("checkedItems", (items) =>
|
|
177
|
+
items.filter((id) => id !== itemId),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
132
181
|
},
|
|
133
182
|
},
|
|
134
183
|
};
|
|
@@ -9,9 +9,11 @@ export enum CheckedState {
|
|
|
9
9
|
export type CheckboxesFeatureDef<T> = {
|
|
10
10
|
state: {
|
|
11
11
|
checkedItems: string[];
|
|
12
|
+
loadingCheckPropagationItems: string[];
|
|
12
13
|
};
|
|
13
14
|
config: {
|
|
14
15
|
setCheckedItems?: SetStateFn<string[]>;
|
|
16
|
+
setLoadingCheckPropagationItems?: SetStateFn<string[]>;
|
|
15
17
|
canCheckFolders?: boolean;
|
|
16
18
|
propagateCheckedState?: boolean;
|
|
17
19
|
};
|
|
@@ -19,11 +21,22 @@ export type CheckboxesFeatureDef<T> = {
|
|
|
19
21
|
setCheckedItems: (checkedItems: string[]) => void;
|
|
20
22
|
};
|
|
21
23
|
itemInstance: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
/** Will recursively load descendants if propagateCheckedState=true and async data loader is used. If not,
|
|
25
|
+
* this will return immediately. */
|
|
26
|
+
setChecked: () => Promise<void>;
|
|
27
|
+
|
|
28
|
+
/** Will recursively load descendants if propagateCheckedState=true and async data loader is used. If not,
|
|
29
|
+
* this will return immediately. */
|
|
30
|
+
setUnchecked: () => Promise<void>;
|
|
31
|
+
|
|
32
|
+
/** Will recursively load descendants if propagateCheckedState=true and async data loader is used. If not,
|
|
33
|
+
* this will return immediately. */
|
|
34
|
+
toggleCheckedState: () => Promise<void>;
|
|
35
|
+
|
|
25
36
|
getCheckedState: () => CheckedState;
|
|
26
37
|
getCheckboxProps: () => Record<string, any>;
|
|
38
|
+
|
|
39
|
+
isLoadingCheckPropagation: () => boolean;
|
|
27
40
|
};
|
|
28
41
|
hotkeys: never;
|
|
29
42
|
};
|
|
@@ -101,6 +101,17 @@ export class TestTree<T = string> {
|
|
|
101
101
|
await TestTree.resolveAsyncLoaders();
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
async runWhileResolvingItems(cb: () => Promise<void>) {
|
|
105
|
+
const interval = setInterval(() => {
|
|
106
|
+
TestTree.resolveAsyncLoaders();
|
|
107
|
+
}, 5);
|
|
108
|
+
try {
|
|
109
|
+
await cb();
|
|
110
|
+
} finally {
|
|
111
|
+
clearInterval(interval);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
104
115
|
static default(config: Partial<TreeConfig<string>>) {
|
|
105
116
|
return new TestTree({
|
|
106
117
|
rootItemId: "x",
|