@headless-tree/core 0.0.0-20250928101953 → 0.0.0-20251208190918
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 +21 -1
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +63 -20
- package/dist/index.mjs +63 -20
- package/package.json +1 -1
- package/src/features/async-data-loader/feature.ts +28 -0
- package/src/features/async-data-loader/types.ts +6 -0
- package/src/features/checkboxes/feature.ts +24 -12
- package/src/features/hotkeys-core/feature.ts +1 -0
- package/src/features/selection/feature.ts +12 -2
- package/src/features/selection/types.ts +4 -0
- package/src/features/sync-data-loader/feature.ts +1 -0
- package/src/features/sync-data-loader/types.ts +3 -0
- package/src/features/tree/feature.ts +2 -2
- package/src/features/tree/tree.spec.ts +37 -4
- package/src/test-utils/test-tree.ts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
# @headless-tree/core
|
|
2
2
|
|
|
3
|
-
## 0.0.0-
|
|
3
|
+
## 0.0.0-20251208190918
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 297b575: The anchor for shift-selecting (`item.selectUpTo()`) is now the last item that was clicked while not holding shift, or alternatively the focused item if that didn't exist. The previous behavior was always using the focused item as anchor, which doesn't match common multi-select behaviors in similar applications (#176)
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 7158afe: Improve rerendering behavior by skipping changes to the item loading state when items are already loading, and skipping changes to loading state in case of checkbox click propagation if items are loaded immediately (#173)
|
|
12
|
+
|
|
13
|
+
## 1.5.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 08f10f1: Fixed an issue where `isFolder` returns incorrect values for leafs if they are not visibly rendered (#166)
|
|
18
|
+
|
|
19
|
+
## 1.5.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
|
6
22
|
|
|
@@ -17,6 +33,10 @@
|
|
|
17
33
|
- 597faad: Checkbox propagation is now supported for trees with async data loaders!
|
|
18
34
|
- 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
|
|
19
35
|
|
|
36
|
+
### Sponsorship appreciation
|
|
37
|
+
|
|
38
|
+
Thanks for [Docmost](https://docmost.com/), who have supported the development of Headless Tree with a sponsor contribution. Docmost is a wiki-software that can be self-hosted or used in cloud. Thank you!
|
|
39
|
+
|
|
20
40
|
## 1.4.0
|
|
21
41
|
|
|
22
42
|
### Minor Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -199,6 +199,9 @@ type MainFeatureDef<T = any> = {
|
|
|
199
199
|
hotkeys: never;
|
|
200
200
|
};
|
|
201
201
|
|
|
202
|
+
interface SelectionDataRef {
|
|
203
|
+
selectUpToAnchorId?: string | null;
|
|
204
|
+
}
|
|
202
205
|
type SelectionFeatureDef<T> = {
|
|
203
206
|
state: {
|
|
204
207
|
selectedItems: string[];
|
|
@@ -276,7 +279,10 @@ type SyncDataLoaderFeatureDef<T> = {
|
|
|
276
279
|
retrieveChildrenIds: (itemId: string, skipFetch?: boolean) => string[];
|
|
277
280
|
};
|
|
278
281
|
itemInstance: {
|
|
282
|
+
/** Returns false. Provided for consistency with async data loader */
|
|
279
283
|
isLoading: () => boolean;
|
|
284
|
+
/** Returns true. Provided for consistency with async data loader */
|
|
285
|
+
hasLoadedData: () => boolean;
|
|
280
286
|
};
|
|
281
287
|
hotkeys: never;
|
|
282
288
|
};
|
|
@@ -284,6 +290,8 @@ type SyncDataLoaderFeatureDef<T> = {
|
|
|
284
290
|
interface AsyncDataLoaderDataRef<T = any> {
|
|
285
291
|
itemData: Record<string, T>;
|
|
286
292
|
childrenIds: Record<string, string[]>;
|
|
293
|
+
loadingDataSubs: Record<string, (() => void)[]>;
|
|
294
|
+
loadingChildrenSubs: Record<string, (() => void)[]>;
|
|
287
295
|
}
|
|
288
296
|
/**
|
|
289
297
|
* @category Async Data Loader/General
|
|
@@ -324,6 +332,7 @@ type AsyncDataLoaderFeatureDef<T> = {
|
|
|
324
332
|
/** Set to undefined to clear cache without triggering automatic refetch. Use @invalidateItemData to clear and triggering refetch. */
|
|
325
333
|
updateCachedData: (data: T | undefined) => void;
|
|
326
334
|
updateCachedChildrenIds: (childrenIds: string[]) => void;
|
|
335
|
+
hasLoadedData: () => boolean;
|
|
327
336
|
isLoading: () => boolean;
|
|
328
337
|
};
|
|
329
338
|
hotkeys: SyncDataLoaderFeatureDef<T>["hotkeys"];
|
|
@@ -601,4 +610,4 @@ declare const isOrderedDragTarget: <T>(dragTarget: DragTarget<T>) => dragTarget
|
|
|
601
610
|
dragLineLevel: number;
|
|
602
611
|
};
|
|
603
612
|
|
|
604
|
-
export { AssistiveDndState, type AsyncDataLoaderDataRef, type AsyncDataLoaderFeatureDef, type CheckboxesFeatureDef, CheckedState, type CustomHotkeysConfig, type DndDataRef, type DndState, type DragAndDropFeatureDef, type DragLineData, type DragTarget, DragTargetPosition, type EmptyFeatureDef, type ExpandAllDataRef, type ExpandAllFeatureDef, type FeatureDef, type FeatureImplementation, type HotkeyConfig, type HotkeyName, type HotkeysConfig, type HotkeysCoreDataRef, type HotkeysCoreFeatureDef, type InstanceBuilder, type ItemInstance, type ItemInstanceOpts, type ItemMeta, type KDndDataRef, type KeyboardDragAndDropFeatureDef, type MainFeatureDef, type PropMemoizationDataRef, type PropMemoizationFeatureDef, type RegisteredFeatures, type RenamingFeatureDef, type SearchFeatureDataRef, type SearchFeatureDef, type SelectionFeatureDef, type SetStateFn, type SyncDataLoaderFeatureDef, type TreeConfig, type TreeDataLoader, type TreeFeatureDef, type TreeInstance, type TreeInstanceOpts, type TreeItemDataRef, type TreeState, type Updater, asyncDataLoaderFeature, buildProxiedInstance, buildStaticInstance, checkboxesFeature, createOnDropHandler, createTree, dragAndDropFeature, expandAllFeature, hotkeysCoreFeature, insertItemsAtTarget, isOrderedDragTarget, keyboardDragAndDropFeature, makeStateUpdater, propMemoizationFeature, removeItemsFromParents, renamingFeature, searchFeature, selectionFeature, syncDataLoaderFeature };
|
|
613
|
+
export { AssistiveDndState, type AsyncDataLoaderDataRef, type AsyncDataLoaderFeatureDef, type CheckboxesFeatureDef, CheckedState, type CustomHotkeysConfig, type DndDataRef, type DndState, type DragAndDropFeatureDef, type DragLineData, type DragTarget, DragTargetPosition, type EmptyFeatureDef, type ExpandAllDataRef, type ExpandAllFeatureDef, type FeatureDef, type FeatureImplementation, type HotkeyConfig, type HotkeyName, type HotkeysConfig, type HotkeysCoreDataRef, type HotkeysCoreFeatureDef, type InstanceBuilder, type ItemInstance, type ItemInstanceOpts, type ItemMeta, type KDndDataRef, type KeyboardDragAndDropFeatureDef, type MainFeatureDef, type PropMemoizationDataRef, type PropMemoizationFeatureDef, type RegisteredFeatures, type RenamingFeatureDef, type SearchFeatureDataRef, type SearchFeatureDef, type SelectionDataRef, type SelectionFeatureDef, type SetStateFn, type SyncDataLoaderFeatureDef, type TreeConfig, type TreeDataLoader, type TreeFeatureDef, type TreeInstance, type TreeInstanceOpts, type TreeItemDataRef, type TreeState, type Updater, asyncDataLoaderFeature, buildProxiedInstance, buildStaticInstance, checkboxesFeature, createOnDropHandler, createTree, dragAndDropFeature, expandAllFeature, hotkeysCoreFeature, insertItemsAtTarget, isOrderedDragTarget, keyboardDragAndDropFeature, makeStateUpdater, propMemoizationFeature, removeItemsFromParents, renamingFeature, searchFeature, selectionFeature, syncDataLoaderFeature };
|
package/dist/index.d.ts
CHANGED
|
@@ -199,6 +199,9 @@ type MainFeatureDef<T = any> = {
|
|
|
199
199
|
hotkeys: never;
|
|
200
200
|
};
|
|
201
201
|
|
|
202
|
+
interface SelectionDataRef {
|
|
203
|
+
selectUpToAnchorId?: string | null;
|
|
204
|
+
}
|
|
202
205
|
type SelectionFeatureDef<T> = {
|
|
203
206
|
state: {
|
|
204
207
|
selectedItems: string[];
|
|
@@ -276,7 +279,10 @@ type SyncDataLoaderFeatureDef<T> = {
|
|
|
276
279
|
retrieveChildrenIds: (itemId: string, skipFetch?: boolean) => string[];
|
|
277
280
|
};
|
|
278
281
|
itemInstance: {
|
|
282
|
+
/** Returns false. Provided for consistency with async data loader */
|
|
279
283
|
isLoading: () => boolean;
|
|
284
|
+
/** Returns true. Provided for consistency with async data loader */
|
|
285
|
+
hasLoadedData: () => boolean;
|
|
280
286
|
};
|
|
281
287
|
hotkeys: never;
|
|
282
288
|
};
|
|
@@ -284,6 +290,8 @@ type SyncDataLoaderFeatureDef<T> = {
|
|
|
284
290
|
interface AsyncDataLoaderDataRef<T = any> {
|
|
285
291
|
itemData: Record<string, T>;
|
|
286
292
|
childrenIds: Record<string, string[]>;
|
|
293
|
+
loadingDataSubs: Record<string, (() => void)[]>;
|
|
294
|
+
loadingChildrenSubs: Record<string, (() => void)[]>;
|
|
287
295
|
}
|
|
288
296
|
/**
|
|
289
297
|
* @category Async Data Loader/General
|
|
@@ -324,6 +332,7 @@ type AsyncDataLoaderFeatureDef<T> = {
|
|
|
324
332
|
/** Set to undefined to clear cache without triggering automatic refetch. Use @invalidateItemData to clear and triggering refetch. */
|
|
325
333
|
updateCachedData: (data: T | undefined) => void;
|
|
326
334
|
updateCachedChildrenIds: (childrenIds: string[]) => void;
|
|
335
|
+
hasLoadedData: () => boolean;
|
|
327
336
|
isLoading: () => boolean;
|
|
328
337
|
};
|
|
329
338
|
hotkeys: SyncDataLoaderFeatureDef<T>["hotkeys"];
|
|
@@ -601,4 +610,4 @@ declare const isOrderedDragTarget: <T>(dragTarget: DragTarget<T>) => dragTarget
|
|
|
601
610
|
dragLineLevel: number;
|
|
602
611
|
};
|
|
603
612
|
|
|
604
|
-
export { AssistiveDndState, type AsyncDataLoaderDataRef, type AsyncDataLoaderFeatureDef, type CheckboxesFeatureDef, CheckedState, type CustomHotkeysConfig, type DndDataRef, type DndState, type DragAndDropFeatureDef, type DragLineData, type DragTarget, DragTargetPosition, type EmptyFeatureDef, type ExpandAllDataRef, type ExpandAllFeatureDef, type FeatureDef, type FeatureImplementation, type HotkeyConfig, type HotkeyName, type HotkeysConfig, type HotkeysCoreDataRef, type HotkeysCoreFeatureDef, type InstanceBuilder, type ItemInstance, type ItemInstanceOpts, type ItemMeta, type KDndDataRef, type KeyboardDragAndDropFeatureDef, type MainFeatureDef, type PropMemoizationDataRef, type PropMemoizationFeatureDef, type RegisteredFeatures, type RenamingFeatureDef, type SearchFeatureDataRef, type SearchFeatureDef, type SelectionFeatureDef, type SetStateFn, type SyncDataLoaderFeatureDef, type TreeConfig, type TreeDataLoader, type TreeFeatureDef, type TreeInstance, type TreeInstanceOpts, type TreeItemDataRef, type TreeState, type Updater, asyncDataLoaderFeature, buildProxiedInstance, buildStaticInstance, checkboxesFeature, createOnDropHandler, createTree, dragAndDropFeature, expandAllFeature, hotkeysCoreFeature, insertItemsAtTarget, isOrderedDragTarget, keyboardDragAndDropFeature, makeStateUpdater, propMemoizationFeature, removeItemsFromParents, renamingFeature, searchFeature, selectionFeature, syncDataLoaderFeature };
|
|
613
|
+
export { AssistiveDndState, type AsyncDataLoaderDataRef, type AsyncDataLoaderFeatureDef, type CheckboxesFeatureDef, CheckedState, type CustomHotkeysConfig, type DndDataRef, type DndState, type DragAndDropFeatureDef, type DragLineData, type DragTarget, DragTargetPosition, type EmptyFeatureDef, type ExpandAllDataRef, type ExpandAllFeatureDef, type FeatureDef, type FeatureImplementation, type HotkeyConfig, type HotkeyName, type HotkeysConfig, type HotkeysCoreDataRef, type HotkeysCoreFeatureDef, type InstanceBuilder, type ItemInstance, type ItemInstanceOpts, type ItemMeta, type KDndDataRef, type KeyboardDragAndDropFeatureDef, type MainFeatureDef, type PropMemoizationDataRef, type PropMemoizationFeatureDef, type RegisteredFeatures, type RenamingFeatureDef, type SearchFeatureDataRef, type SearchFeatureDef, type SelectionDataRef, type SelectionFeatureDef, type SetStateFn, type SyncDataLoaderFeatureDef, type TreeConfig, type TreeDataLoader, type TreeFeatureDef, type TreeInstance, type TreeInstanceOpts, type TreeItemDataRef, type TreeState, type Updater, asyncDataLoaderFeature, buildProxiedInstance, buildStaticInstance, checkboxesFeature, createOnDropHandler, createTree, dragAndDropFeature, expandAllFeature, hotkeysCoreFeature, insertItemsAtTarget, isOrderedDragTarget, keyboardDragAndDropFeature, makeStateUpdater, propMemoizationFeature, removeItemsFromParents, renamingFeature, searchFeature, selectionFeature, syncDataLoaderFeature };
|
package/dist/index.js
CHANGED
|
@@ -303,7 +303,7 @@ var treeFeature = {
|
|
|
303
303
|
);
|
|
304
304
|
},
|
|
305
305
|
isFocused: ({ tree, item, itemId }) => tree.getState().focusedItem === itemId || tree.getState().focusedItem === null && item.getItemMeta().index === 0,
|
|
306
|
-
isFolder: ({ tree, item }) =>
|
|
306
|
+
isFolder: ({ tree, item, itemId }) => itemId === tree.getConfig().rootItemId || tree.getConfig().isItemFolder(item),
|
|
307
307
|
getItemName: ({ tree, item }) => {
|
|
308
308
|
const config = tree.getConfig();
|
|
309
309
|
return config.getItemName(item);
|
|
@@ -760,7 +760,9 @@ var selectionFeature = {
|
|
|
760
760
|
},
|
|
761
761
|
selectUpTo: ({ tree, item }, ctrl) => {
|
|
762
762
|
const indexA = item.getItemMeta().index;
|
|
763
|
-
const
|
|
763
|
+
const { selectUpToAnchorId } = tree.getDataRef().current;
|
|
764
|
+
const itemB = selectUpToAnchorId ? tree.getItemInstance(selectUpToAnchorId) : tree.getFocusedItem();
|
|
765
|
+
const indexB = itemB.getItemMeta().index;
|
|
764
766
|
const [a, b] = indexA < indexB ? [indexA, indexB] : [indexB, indexA];
|
|
765
767
|
const newSelectedItems = tree.getItems().slice(a, b + 1).map((treeItem) => treeItem.getItemMeta().itemId);
|
|
766
768
|
if (!ctrl) {
|
|
@@ -791,6 +793,9 @@ var selectionFeature = {
|
|
|
791
793
|
} else {
|
|
792
794
|
tree.setSelectedItems([item.getItemMeta().itemId]);
|
|
793
795
|
}
|
|
796
|
+
if (!e.shiftKey) {
|
|
797
|
+
tree.getDataRef().current.selectUpToAnchorId = item.getId();
|
|
798
|
+
}
|
|
794
799
|
(_b = (_a = prev == null ? void 0 : prev()) == null ? void 0 : _a.onClick) == null ? void 0 : _b.call(_a, e);
|
|
795
800
|
}
|
|
796
801
|
})
|
|
@@ -858,8 +863,11 @@ var getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
|
858
863
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
859
864
|
};
|
|
860
865
|
var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null, null, function* () {
|
|
861
|
-
|
|
862
|
-
if (!
|
|
866
|
+
const item = tree.getItemInstance(itemId);
|
|
867
|
+
if (!item.hasLoadedData()) {
|
|
868
|
+
yield tree.loadItemData(itemId);
|
|
869
|
+
}
|
|
870
|
+
if (!tree.getConfig().isItemFolder(item)) {
|
|
863
871
|
return [itemId];
|
|
864
872
|
}
|
|
865
873
|
const childrenIds = yield tree.loadChildrenIds(itemId);
|
|
@@ -871,17 +879,24 @@ var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null,
|
|
|
871
879
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
872
880
|
});
|
|
873
881
|
var withLoadingState = (tree, itemId, callback) => __async(null, null, function* () {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
882
|
+
const prom = callback();
|
|
883
|
+
const immediate = {};
|
|
884
|
+
const firstCompleted = yield Promise.race([prom, immediate]);
|
|
885
|
+
if (firstCompleted !== immediate) {
|
|
886
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) => [
|
|
887
|
+
...items,
|
|
888
|
+
itemId
|
|
889
|
+
]);
|
|
890
|
+
try {
|
|
891
|
+
yield prom;
|
|
892
|
+
} finally {
|
|
893
|
+
tree.applySubStateUpdate(
|
|
894
|
+
"loadingCheckPropagationItems",
|
|
895
|
+
(items) => items.filter((id) => id !== itemId)
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
} else {
|
|
899
|
+
yield prom;
|
|
885
900
|
}
|
|
886
901
|
});
|
|
887
902
|
var checkboxesFeature = {
|
|
@@ -1007,7 +1022,8 @@ var specialKeys = {
|
|
|
1007
1022
|
minus: /^(NumpadSubtract|Minus)$/,
|
|
1008
1023
|
control: /^(ControlLeft|ControlRight)$/,
|
|
1009
1024
|
shift: /^(ShiftLeft|ShiftRight)$/,
|
|
1010
|
-
metaorcontrol: /^(MetaLeft|MetaRight|ControlLeft|ControlRight)
|
|
1025
|
+
metaorcontrol: /^(MetaLeft|MetaRight|ControlLeft|ControlRight)$/,
|
|
1026
|
+
enter: /^(Enter|NumpadEnter)$/
|
|
1011
1027
|
};
|
|
1012
1028
|
var testHotkeyMatch = (pressedKeys, tree, hotkey) => {
|
|
1013
1029
|
const supposedKeys = hotkey.hotkey.toLowerCase().split("+");
|
|
@@ -1102,16 +1118,27 @@ var hotkeysCoreFeature = {
|
|
|
1102
1118
|
|
|
1103
1119
|
// src/features/async-data-loader/feature.ts
|
|
1104
1120
|
var getDataRef = (tree) => {
|
|
1105
|
-
var _a, _b, _c, _d;
|
|
1121
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1106
1122
|
const dataRef = tree.getDataRef();
|
|
1107
1123
|
(_b = (_a = dataRef.current).itemData) != null ? _b : _a.itemData = {};
|
|
1108
1124
|
(_d = (_c = dataRef.current).childrenIds) != null ? _d : _c.childrenIds = {};
|
|
1125
|
+
(_f = (_e = dataRef.current).loadingDataSubs) != null ? _f : _e.loadingDataSubs = {};
|
|
1126
|
+
(_h = (_g = dataRef.current).loadingChildrenSubs) != null ? _h : _g.loadingChildrenSubs = {};
|
|
1109
1127
|
return dataRef;
|
|
1110
1128
|
};
|
|
1111
1129
|
var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
1112
|
-
var _a;
|
|
1130
|
+
var _a, _b;
|
|
1113
1131
|
const config = tree.getConfig();
|
|
1114
1132
|
const dataRef = getDataRef(tree);
|
|
1133
|
+
if (tree.getState().loadingItemData.includes(itemId)) {
|
|
1134
|
+
return new Promise((resolve) => {
|
|
1135
|
+
var _a2, _b2;
|
|
1136
|
+
(_b2 = (_a2 = dataRef.current.loadingDataSubs)[itemId]) != null ? _b2 : _a2[itemId] = [];
|
|
1137
|
+
dataRef.current.loadingDataSubs[itemId].push(() => {
|
|
1138
|
+
resolve(dataRef.current.itemData[itemId]);
|
|
1139
|
+
});
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1115
1142
|
if (!dataRef.current.itemData[itemId]) {
|
|
1116
1143
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1117
1144
|
...loadingItemData,
|
|
@@ -1125,13 +1152,23 @@ var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
|
1125
1152
|
"loadingItemData",
|
|
1126
1153
|
(loadingItemData) => loadingItemData.filter((id) => id !== itemId)
|
|
1127
1154
|
);
|
|
1155
|
+
(_b = dataRef.current.loadingDataSubs[itemId]) == null ? void 0 : _b.forEach((cb) => cb());
|
|
1128
1156
|
return item;
|
|
1129
1157
|
});
|
|
1130
1158
|
var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
1131
|
-
var _a, _b;
|
|
1159
|
+
var _a, _b, _c;
|
|
1132
1160
|
const config = tree.getConfig();
|
|
1133
1161
|
const dataRef = getDataRef(tree);
|
|
1134
1162
|
let childrenIds;
|
|
1163
|
+
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
1164
|
+
return new Promise((resolve) => {
|
|
1165
|
+
var _a2, _b2;
|
|
1166
|
+
(_b2 = (_a2 = dataRef.current.loadingChildrenSubs)[itemId]) != null ? _b2 : _a2[itemId] = [];
|
|
1167
|
+
dataRef.current.loadingChildrenSubs[itemId].push(() => {
|
|
1168
|
+
resolve(dataRef.current.childrenIds[itemId]);
|
|
1169
|
+
});
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1135
1172
|
if (!dataRef.current.childrenIds[itemId]) {
|
|
1136
1173
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [
|
|
1137
1174
|
...loadingItemChildrens,
|
|
@@ -1163,6 +1200,7 @@ var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
|
1163
1200
|
"loadingItemChildrens",
|
|
1164
1201
|
(loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId)
|
|
1165
1202
|
);
|
|
1203
|
+
(_c = dataRef.current.loadingChildrenSubs[itemId]) == null ? void 0 : _c.forEach((cb) => cb());
|
|
1166
1204
|
return childrenIds;
|
|
1167
1205
|
});
|
|
1168
1206
|
var asyncDataLoaderFeature = {
|
|
@@ -1239,6 +1277,10 @@ var asyncDataLoaderFeature = {
|
|
|
1239
1277
|
const dataRef = tree.getDataRef();
|
|
1240
1278
|
dataRef.current.itemData[itemId] = data;
|
|
1241
1279
|
tree.rebuildTree();
|
|
1280
|
+
},
|
|
1281
|
+
hasLoadedData: ({ tree, itemId }) => {
|
|
1282
|
+
const dataRef = tree.getDataRef();
|
|
1283
|
+
return dataRef.current.itemData[itemId] !== void 0;
|
|
1242
1284
|
}
|
|
1243
1285
|
}
|
|
1244
1286
|
};
|
|
@@ -1290,7 +1332,8 @@ var syncDataLoaderFeature = {
|
|
|
1290
1332
|
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId)
|
|
1291
1333
|
},
|
|
1292
1334
|
itemInstance: {
|
|
1293
|
-
isLoading: () => false
|
|
1335
|
+
isLoading: () => false,
|
|
1336
|
+
hasLoadedData: () => true
|
|
1294
1337
|
}
|
|
1295
1338
|
};
|
|
1296
1339
|
|
package/dist/index.mjs
CHANGED
|
@@ -259,7 +259,7 @@ var treeFeature = {
|
|
|
259
259
|
);
|
|
260
260
|
},
|
|
261
261
|
isFocused: ({ tree, item, itemId }) => tree.getState().focusedItem === itemId || tree.getState().focusedItem === null && item.getItemMeta().index === 0,
|
|
262
|
-
isFolder: ({ tree, item }) =>
|
|
262
|
+
isFolder: ({ tree, item, itemId }) => itemId === tree.getConfig().rootItemId || tree.getConfig().isItemFolder(item),
|
|
263
263
|
getItemName: ({ tree, item }) => {
|
|
264
264
|
const config = tree.getConfig();
|
|
265
265
|
return config.getItemName(item);
|
|
@@ -716,7 +716,9 @@ var selectionFeature = {
|
|
|
716
716
|
},
|
|
717
717
|
selectUpTo: ({ tree, item }, ctrl) => {
|
|
718
718
|
const indexA = item.getItemMeta().index;
|
|
719
|
-
const
|
|
719
|
+
const { selectUpToAnchorId } = tree.getDataRef().current;
|
|
720
|
+
const itemB = selectUpToAnchorId ? tree.getItemInstance(selectUpToAnchorId) : tree.getFocusedItem();
|
|
721
|
+
const indexB = itemB.getItemMeta().index;
|
|
720
722
|
const [a, b] = indexA < indexB ? [indexA, indexB] : [indexB, indexA];
|
|
721
723
|
const newSelectedItems = tree.getItems().slice(a, b + 1).map((treeItem) => treeItem.getItemMeta().itemId);
|
|
722
724
|
if (!ctrl) {
|
|
@@ -747,6 +749,9 @@ var selectionFeature = {
|
|
|
747
749
|
} else {
|
|
748
750
|
tree.setSelectedItems([item.getItemMeta().itemId]);
|
|
749
751
|
}
|
|
752
|
+
if (!e.shiftKey) {
|
|
753
|
+
tree.getDataRef().current.selectUpToAnchorId = item.getId();
|
|
754
|
+
}
|
|
750
755
|
(_b = (_a = prev == null ? void 0 : prev()) == null ? void 0 : _a.onClick) == null ? void 0 : _b.call(_a, e);
|
|
751
756
|
}
|
|
752
757
|
})
|
|
@@ -814,8 +819,11 @@ var getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
|
814
819
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
815
820
|
};
|
|
816
821
|
var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null, null, function* () {
|
|
817
|
-
|
|
818
|
-
if (!
|
|
822
|
+
const item = tree.getItemInstance(itemId);
|
|
823
|
+
if (!item.hasLoadedData()) {
|
|
824
|
+
yield tree.loadItemData(itemId);
|
|
825
|
+
}
|
|
826
|
+
if (!tree.getConfig().isItemFolder(item)) {
|
|
819
827
|
return [itemId];
|
|
820
828
|
}
|
|
821
829
|
const childrenIds = yield tree.loadChildrenIds(itemId);
|
|
@@ -827,17 +835,24 @@ var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null,
|
|
|
827
835
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
828
836
|
});
|
|
829
837
|
var withLoadingState = (tree, itemId, callback) => __async(null, null, function* () {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
838
|
+
const prom = callback();
|
|
839
|
+
const immediate = {};
|
|
840
|
+
const firstCompleted = yield Promise.race([prom, immediate]);
|
|
841
|
+
if (firstCompleted !== immediate) {
|
|
842
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) => [
|
|
843
|
+
...items,
|
|
844
|
+
itemId
|
|
845
|
+
]);
|
|
846
|
+
try {
|
|
847
|
+
yield prom;
|
|
848
|
+
} finally {
|
|
849
|
+
tree.applySubStateUpdate(
|
|
850
|
+
"loadingCheckPropagationItems",
|
|
851
|
+
(items) => items.filter((id) => id !== itemId)
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
} else {
|
|
855
|
+
yield prom;
|
|
841
856
|
}
|
|
842
857
|
});
|
|
843
858
|
var checkboxesFeature = {
|
|
@@ -963,7 +978,8 @@ var specialKeys = {
|
|
|
963
978
|
minus: /^(NumpadSubtract|Minus)$/,
|
|
964
979
|
control: /^(ControlLeft|ControlRight)$/,
|
|
965
980
|
shift: /^(ShiftLeft|ShiftRight)$/,
|
|
966
|
-
metaorcontrol: /^(MetaLeft|MetaRight|ControlLeft|ControlRight)
|
|
981
|
+
metaorcontrol: /^(MetaLeft|MetaRight|ControlLeft|ControlRight)$/,
|
|
982
|
+
enter: /^(Enter|NumpadEnter)$/
|
|
967
983
|
};
|
|
968
984
|
var testHotkeyMatch = (pressedKeys, tree, hotkey) => {
|
|
969
985
|
const supposedKeys = hotkey.hotkey.toLowerCase().split("+");
|
|
@@ -1058,16 +1074,27 @@ var hotkeysCoreFeature = {
|
|
|
1058
1074
|
|
|
1059
1075
|
// src/features/async-data-loader/feature.ts
|
|
1060
1076
|
var getDataRef = (tree) => {
|
|
1061
|
-
var _a, _b, _c, _d;
|
|
1077
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1062
1078
|
const dataRef = tree.getDataRef();
|
|
1063
1079
|
(_b = (_a = dataRef.current).itemData) != null ? _b : _a.itemData = {};
|
|
1064
1080
|
(_d = (_c = dataRef.current).childrenIds) != null ? _d : _c.childrenIds = {};
|
|
1081
|
+
(_f = (_e = dataRef.current).loadingDataSubs) != null ? _f : _e.loadingDataSubs = {};
|
|
1082
|
+
(_h = (_g = dataRef.current).loadingChildrenSubs) != null ? _h : _g.loadingChildrenSubs = {};
|
|
1065
1083
|
return dataRef;
|
|
1066
1084
|
};
|
|
1067
1085
|
var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
1068
|
-
var _a;
|
|
1086
|
+
var _a, _b;
|
|
1069
1087
|
const config = tree.getConfig();
|
|
1070
1088
|
const dataRef = getDataRef(tree);
|
|
1089
|
+
if (tree.getState().loadingItemData.includes(itemId)) {
|
|
1090
|
+
return new Promise((resolve) => {
|
|
1091
|
+
var _a2, _b2;
|
|
1092
|
+
(_b2 = (_a2 = dataRef.current.loadingDataSubs)[itemId]) != null ? _b2 : _a2[itemId] = [];
|
|
1093
|
+
dataRef.current.loadingDataSubs[itemId].push(() => {
|
|
1094
|
+
resolve(dataRef.current.itemData[itemId]);
|
|
1095
|
+
});
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1071
1098
|
if (!dataRef.current.itemData[itemId]) {
|
|
1072
1099
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1073
1100
|
...loadingItemData,
|
|
@@ -1081,13 +1108,23 @@ var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
|
1081
1108
|
"loadingItemData",
|
|
1082
1109
|
(loadingItemData) => loadingItemData.filter((id) => id !== itemId)
|
|
1083
1110
|
);
|
|
1111
|
+
(_b = dataRef.current.loadingDataSubs[itemId]) == null ? void 0 : _b.forEach((cb) => cb());
|
|
1084
1112
|
return item;
|
|
1085
1113
|
});
|
|
1086
1114
|
var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
1087
|
-
var _a, _b;
|
|
1115
|
+
var _a, _b, _c;
|
|
1088
1116
|
const config = tree.getConfig();
|
|
1089
1117
|
const dataRef = getDataRef(tree);
|
|
1090
1118
|
let childrenIds;
|
|
1119
|
+
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
1120
|
+
return new Promise((resolve) => {
|
|
1121
|
+
var _a2, _b2;
|
|
1122
|
+
(_b2 = (_a2 = dataRef.current.loadingChildrenSubs)[itemId]) != null ? _b2 : _a2[itemId] = [];
|
|
1123
|
+
dataRef.current.loadingChildrenSubs[itemId].push(() => {
|
|
1124
|
+
resolve(dataRef.current.childrenIds[itemId]);
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1091
1128
|
if (!dataRef.current.childrenIds[itemId]) {
|
|
1092
1129
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [
|
|
1093
1130
|
...loadingItemChildrens,
|
|
@@ -1119,6 +1156,7 @@ var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
|
1119
1156
|
"loadingItemChildrens",
|
|
1120
1157
|
(loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId)
|
|
1121
1158
|
);
|
|
1159
|
+
(_c = dataRef.current.loadingChildrenSubs[itemId]) == null ? void 0 : _c.forEach((cb) => cb());
|
|
1122
1160
|
return childrenIds;
|
|
1123
1161
|
});
|
|
1124
1162
|
var asyncDataLoaderFeature = {
|
|
@@ -1195,6 +1233,10 @@ var asyncDataLoaderFeature = {
|
|
|
1195
1233
|
const dataRef = tree.getDataRef();
|
|
1196
1234
|
dataRef.current.itemData[itemId] = data;
|
|
1197
1235
|
tree.rebuildTree();
|
|
1236
|
+
},
|
|
1237
|
+
hasLoadedData: ({ tree, itemId }) => {
|
|
1238
|
+
const dataRef = tree.getDataRef();
|
|
1239
|
+
return dataRef.current.itemData[itemId] !== void 0;
|
|
1198
1240
|
}
|
|
1199
1241
|
}
|
|
1200
1242
|
};
|
|
@@ -1246,7 +1288,8 @@ var syncDataLoaderFeature = {
|
|
|
1246
1288
|
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId)
|
|
1247
1289
|
},
|
|
1248
1290
|
itemInstance: {
|
|
1249
|
-
isLoading: () => false
|
|
1291
|
+
isLoading: () => false,
|
|
1292
|
+
hasLoadedData: () => true
|
|
1250
1293
|
}
|
|
1251
1294
|
};
|
|
1252
1295
|
|
package/package.json
CHANGED
|
@@ -6,6 +6,8 @@ const getDataRef = <T>(tree: TreeInstance<T>) => {
|
|
|
6
6
|
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
7
7
|
dataRef.current.itemData ??= {};
|
|
8
8
|
dataRef.current.childrenIds ??= {};
|
|
9
|
+
dataRef.current.loadingDataSubs ??= {};
|
|
10
|
+
dataRef.current.loadingChildrenSubs ??= {};
|
|
9
11
|
return dataRef;
|
|
10
12
|
};
|
|
11
13
|
|
|
@@ -13,6 +15,16 @@ const loadItemData = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
|
13
15
|
const config = tree.getConfig();
|
|
14
16
|
const dataRef = getDataRef(tree);
|
|
15
17
|
|
|
18
|
+
if (tree.getState().loadingItemData.includes(itemId)) {
|
|
19
|
+
// if currently loading, await existing load
|
|
20
|
+
return new Promise<T>((resolve) => {
|
|
21
|
+
dataRef.current.loadingDataSubs[itemId] ??= [];
|
|
22
|
+
dataRef.current.loadingDataSubs[itemId].push(() => {
|
|
23
|
+
resolve(dataRef.current.itemData[itemId]);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
if (!dataRef.current.itemData[itemId]) {
|
|
17
29
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
18
30
|
...loadingItemData,
|
|
@@ -26,6 +38,7 @@ const loadItemData = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
|
26
38
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) =>
|
|
27
39
|
loadingItemData.filter((id) => id !== itemId),
|
|
28
40
|
);
|
|
41
|
+
dataRef.current.loadingDataSubs[itemId]?.forEach((cb) => cb());
|
|
29
42
|
|
|
30
43
|
return item;
|
|
31
44
|
};
|
|
@@ -35,6 +48,16 @@ const loadChildrenIds = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
|
35
48
|
const dataRef = getDataRef(tree);
|
|
36
49
|
let childrenIds: string[];
|
|
37
50
|
|
|
51
|
+
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
52
|
+
// if currently loading, await existing load
|
|
53
|
+
return new Promise<string[]>((resolve) => {
|
|
54
|
+
dataRef.current.loadingChildrenSubs[itemId] ??= [];
|
|
55
|
+
dataRef.current.loadingChildrenSubs[itemId].push(() => {
|
|
56
|
+
resolve(dataRef.current.childrenIds[itemId]);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
38
61
|
if (!dataRef.current.childrenIds[itemId]) {
|
|
39
62
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [
|
|
40
63
|
...loadingItemChildrens,
|
|
@@ -66,6 +89,7 @@ const loadChildrenIds = async <T>(tree: TreeInstance<T>, itemId: string) => {
|
|
|
66
89
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) =>
|
|
67
90
|
loadingItemChildrens.filter((id) => id !== itemId),
|
|
68
91
|
);
|
|
92
|
+
dataRef.current.loadingChildrenSubs[itemId]?.forEach((cb) => cb());
|
|
69
93
|
|
|
70
94
|
return childrenIds;
|
|
71
95
|
};
|
|
@@ -166,5 +190,9 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
166
190
|
dataRef.current.itemData[itemId] = data;
|
|
167
191
|
tree.rebuildTree();
|
|
168
192
|
},
|
|
193
|
+
hasLoadedData: ({ tree, itemId }) => {
|
|
194
|
+
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
195
|
+
return dataRef.current.itemData[itemId] !== undefined;
|
|
196
|
+
},
|
|
169
197
|
},
|
|
170
198
|
};
|
|
@@ -4,6 +4,11 @@ import { SyncDataLoaderFeatureDef } from "../sync-data-loader/types";
|
|
|
4
4
|
export interface AsyncDataLoaderDataRef<T = any> {
|
|
5
5
|
itemData: Record<string, T>;
|
|
6
6
|
childrenIds: Record<string, string[]>;
|
|
7
|
+
|
|
8
|
+
// If an item load is requested while it is already loading, we reuse the existing load promise
|
|
9
|
+
// and store callbacks to be called when the load completes
|
|
10
|
+
loadingDataSubs: Record<string, (() => void)[]>;
|
|
11
|
+
loadingChildrenSubs: Record<string, (() => void)[]>;
|
|
7
12
|
}
|
|
8
13
|
|
|
9
14
|
/**
|
|
@@ -50,6 +55,7 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
50
55
|
/** Set to undefined to clear cache without triggering automatic refetch. Use @invalidateItemData to clear and triggering refetch. */
|
|
51
56
|
updateCachedData: (data: T | undefined) => void;
|
|
52
57
|
updateCachedChildrenIds: (childrenIds: string[]) => void;
|
|
58
|
+
hasLoadedData: () => boolean;
|
|
53
59
|
isLoading: () => boolean;
|
|
54
60
|
};
|
|
55
61
|
hotkeys: SyncDataLoaderFeatureDef<T>["hotkeys"];
|
|
@@ -22,8 +22,11 @@ const getAllDescendants = async <T>(
|
|
|
22
22
|
itemId: string,
|
|
23
23
|
includeFolders = false,
|
|
24
24
|
): Promise<string[]> => {
|
|
25
|
-
|
|
26
|
-
if (!
|
|
25
|
+
const item = tree.getItemInstance(itemId);
|
|
26
|
+
if (!item.hasLoadedData()) {
|
|
27
|
+
await tree.loadItemData(itemId);
|
|
28
|
+
}
|
|
29
|
+
if (!tree.getConfig().isItemFolder(item)) {
|
|
27
30
|
return [itemId];
|
|
28
31
|
}
|
|
29
32
|
const childrenIds = await tree.loadChildrenIds(itemId);
|
|
@@ -42,16 +45,25 @@ const withLoadingState = async <T>(
|
|
|
42
45
|
itemId: string,
|
|
43
46
|
callback: () => Promise<void>,
|
|
44
47
|
) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
const prom = callback();
|
|
49
|
+
const immediate = {};
|
|
50
|
+
const firstCompleted = await Promise.race([prom, immediate]);
|
|
51
|
+
if (firstCompleted !== immediate) {
|
|
52
|
+
// don't update loading state if load is immediate
|
|
53
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) => [
|
|
54
|
+
...items,
|
|
55
|
+
itemId,
|
|
56
|
+
]);
|
|
57
|
+
try {
|
|
58
|
+
await prom;
|
|
59
|
+
} finally {
|
|
60
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) =>
|
|
61
|
+
items.filter((id) => id !== itemId),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// immediately completed, await anyway to keep errors in the same execution flow
|
|
66
|
+
await prom;
|
|
55
67
|
}
|
|
56
68
|
};
|
|
57
69
|
|
|
@@ -14,6 +14,7 @@ const specialKeys: Record<string, RegExp> = {
|
|
|
14
14
|
control: /^(ControlLeft|ControlRight)$/,
|
|
15
15
|
shift: /^(ShiftLeft|ShiftRight)$/,
|
|
16
16
|
metaorcontrol: /^(MetaLeft|MetaRight|ControlLeft|ControlRight)$/,
|
|
17
|
+
enter: /^(Enter|NumpadEnter)$/,
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
const testHotkeyMatch = (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FeatureImplementation } from "../../types/core";
|
|
2
2
|
import { makeStateUpdater } from "../../utils";
|
|
3
|
+
import type { SelectionDataRef } from "./types";
|
|
3
4
|
|
|
4
5
|
export const selectionFeature: FeatureImplementation = {
|
|
5
6
|
key: "selection",
|
|
@@ -50,8 +51,12 @@ export const selectionFeature: FeatureImplementation = {
|
|
|
50
51
|
|
|
51
52
|
selectUpTo: ({ tree, item }, ctrl: boolean) => {
|
|
52
53
|
const indexA = item.getItemMeta().index;
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
const { selectUpToAnchorId } =
|
|
55
|
+
tree.getDataRef<SelectionDataRef>().current;
|
|
56
|
+
const itemB = selectUpToAnchorId
|
|
57
|
+
? tree.getItemInstance(selectUpToAnchorId)
|
|
58
|
+
: tree.getFocusedItem();
|
|
59
|
+
const indexB = itemB.getItemMeta().index;
|
|
55
60
|
const [a, b] = indexA < indexB ? [indexA, indexB] : [indexB, indexA];
|
|
56
61
|
const newSelectedItems = tree
|
|
57
62
|
.getItems()
|
|
@@ -90,6 +95,11 @@ export const selectionFeature: FeatureImplementation = {
|
|
|
90
95
|
tree.setSelectedItems([item.getItemMeta().itemId]);
|
|
91
96
|
}
|
|
92
97
|
|
|
98
|
+
if (!e.shiftKey) {
|
|
99
|
+
tree.getDataRef<SelectionDataRef>().current.selectUpToAnchorId =
|
|
100
|
+
item.getId();
|
|
101
|
+
}
|
|
102
|
+
|
|
93
103
|
prev?.()?.onClick?.(e);
|
|
94
104
|
},
|
|
95
105
|
}),
|
|
@@ -27,7 +27,10 @@ export type SyncDataLoaderFeatureDef<T> = {
|
|
|
27
27
|
retrieveChildrenIds: (itemId: string, skipFetch?: boolean) => string[];
|
|
28
28
|
};
|
|
29
29
|
itemInstance: {
|
|
30
|
+
/** Returns false. Provided for consistency with async data loader */
|
|
30
31
|
isLoading: () => boolean;
|
|
32
|
+
/** Returns true. Provided for consistency with async data loader */
|
|
33
|
+
hasLoadedData: () => boolean;
|
|
31
34
|
};
|
|
32
35
|
hotkeys: never;
|
|
33
36
|
};
|
|
@@ -203,8 +203,8 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
203
203
|
isFocused: ({ tree, item, itemId }) =>
|
|
204
204
|
tree.getState().focusedItem === itemId ||
|
|
205
205
|
(tree.getState().focusedItem === null && item.getItemMeta().index === 0),
|
|
206
|
-
isFolder: ({ tree, item }) =>
|
|
207
|
-
|
|
206
|
+
isFolder: ({ tree, item, itemId }) =>
|
|
207
|
+
itemId === tree.getConfig().rootItemId ||
|
|
208
208
|
tree.getConfig().isItemFolder(item as ItemInstance<any>),
|
|
209
209
|
getItemName: ({ tree, item }) => {
|
|
210
210
|
const config = tree.getConfig();
|
|
@@ -4,8 +4,8 @@ import { propMemoizationFeature } from "../prop-memoization/feature";
|
|
|
4
4
|
|
|
5
5
|
const factory = TestTree.default({}).withFeatures(propMemoizationFeature);
|
|
6
6
|
|
|
7
|
-
describe("core-feature/
|
|
8
|
-
factory.forSuits((tree) => {
|
|
7
|
+
describe("core-feature/tree", () => {
|
|
8
|
+
factory.forSuits((tree, title) => {
|
|
9
9
|
describe("expanded items", () => {
|
|
10
10
|
it("can expand via tree instance", () => {
|
|
11
11
|
const setExpandedItems = tree.mockedHandler("setExpandedItems");
|
|
@@ -156,6 +156,17 @@ describe("core-feature/selections", () => {
|
|
|
156
156
|
});
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
+
it("unloaded item", () => {
|
|
160
|
+
expect(tree.instance.getItemInstance("x444").getItemMeta()).toEqual({
|
|
161
|
+
index: -1,
|
|
162
|
+
itemId: "x444",
|
|
163
|
+
level: -1,
|
|
164
|
+
parentId: null,
|
|
165
|
+
posInSet: 0,
|
|
166
|
+
setSize: 1,
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
159
170
|
it("expanded container", () => {
|
|
160
171
|
expect(tree.instance.getItemInstance("x11").getItemMeta()).toEqual({
|
|
161
172
|
index: 1,
|
|
@@ -312,8 +323,30 @@ describe("core-feature/selections", () => {
|
|
|
312
323
|
expect(tree.instance.getItemInstance("x1").isFolder()).toBe(true);
|
|
313
324
|
});
|
|
314
325
|
|
|
315
|
-
it("returns correctly for
|
|
316
|
-
expect(tree.instance.getItemInstance("
|
|
326
|
+
it("returns correctly for true cases of isFolder() ", () => {
|
|
327
|
+
expect(tree.instance.getItemInstance("x1").isFolder()).toBe(true);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("returns correct isFolder for hidden items", async () => {
|
|
331
|
+
if (title.toLocaleLowerCase().includes("async")) {
|
|
332
|
+
// async test tree defaults to "loading" item names
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const testTree = await tree
|
|
336
|
+
.with({
|
|
337
|
+
isItemFolder: (item: any) => item.getItemData().length < 4,
|
|
338
|
+
})
|
|
339
|
+
.createTestCaseTree();
|
|
340
|
+
|
|
341
|
+
// Reference: https://github.com/lukasbach/headless-tree/issues/166
|
|
342
|
+
expect(testTree.instance.getItemInstance("x44").isFolder()).toBe(true);
|
|
343
|
+
expect(testTree.instance.getItemInstance("x444").isFolder()).toBe(
|
|
344
|
+
false,
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("returns isFolder=true for root item", () => {
|
|
349
|
+
expect(tree.instance.getItemInstance("x").isFolder()).toBe(true);
|
|
317
350
|
});
|
|
318
351
|
|
|
319
352
|
it("returns correctly for getParent()", () => {
|
|
@@ -63,15 +63,15 @@ export class TestTree<T = string> {
|
|
|
63
63
|
}),
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
forSuits(runSuite: (tree: TestTree<T
|
|
66
|
+
forSuits(runSuite: (tree: TestTree<T>, title: string) => void) {
|
|
67
67
|
describe.for([
|
|
68
68
|
this.suits.sync(),
|
|
69
69
|
this.suits.async(),
|
|
70
70
|
this.suits.proxifiedSync(),
|
|
71
71
|
this.suits.proxifiedAsync(),
|
|
72
|
-
])("$title", ({ tree }) => {
|
|
72
|
+
])("$title", ({ tree, title }) => {
|
|
73
73
|
tree.resetBeforeEach();
|
|
74
|
-
runSuite(tree);
|
|
74
|
+
runSuite(tree, title);
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
|