@headless-tree/core 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +61 -18
- package/dist/index.mjs +61 -18
- 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/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 +1 -0
- package/src/features/tree/tree.spec.ts +18 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @headless-tree/core
|
|
2
2
|
|
|
3
|
+
## 1.6.0
|
|
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
|
+
- 287fe40: Added missing `aria-expanded` attribute to tree items (thanks to @gordey4doronin for the contribution) (#171)
|
|
12
|
+
- 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)
|
|
13
|
+
|
|
3
14
|
## 1.5.1
|
|
4
15
|
|
|
5
16
|
### Patch 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
|
@@ -251,6 +251,7 @@ var treeFeature = {
|
|
|
251
251
|
"aria-selected": "false",
|
|
252
252
|
"aria-label": item.getItemName(),
|
|
253
253
|
"aria-level": itemMeta.level + 1,
|
|
254
|
+
"aria-expanded": item.isFolder() ? item.isExpanded() : void 0,
|
|
254
255
|
tabIndex: item.isFocused() ? 0 : -1,
|
|
255
256
|
onClick: (e) => {
|
|
256
257
|
item.setFocused();
|
|
@@ -760,7 +761,9 @@ var selectionFeature = {
|
|
|
760
761
|
},
|
|
761
762
|
selectUpTo: ({ tree, item }, ctrl) => {
|
|
762
763
|
const indexA = item.getItemMeta().index;
|
|
763
|
-
const
|
|
764
|
+
const { selectUpToAnchorId } = tree.getDataRef().current;
|
|
765
|
+
const itemB = selectUpToAnchorId ? tree.getItemInstance(selectUpToAnchorId) : tree.getFocusedItem();
|
|
766
|
+
const indexB = itemB.getItemMeta().index;
|
|
764
767
|
const [a, b] = indexA < indexB ? [indexA, indexB] : [indexB, indexA];
|
|
765
768
|
const newSelectedItems = tree.getItems().slice(a, b + 1).map((treeItem) => treeItem.getItemMeta().itemId);
|
|
766
769
|
if (!ctrl) {
|
|
@@ -791,6 +794,9 @@ var selectionFeature = {
|
|
|
791
794
|
} else {
|
|
792
795
|
tree.setSelectedItems([item.getItemMeta().itemId]);
|
|
793
796
|
}
|
|
797
|
+
if (!e.shiftKey) {
|
|
798
|
+
tree.getDataRef().current.selectUpToAnchorId = item.getId();
|
|
799
|
+
}
|
|
794
800
|
(_b = (_a = prev == null ? void 0 : prev()) == null ? void 0 : _a.onClick) == null ? void 0 : _b.call(_a, e);
|
|
795
801
|
}
|
|
796
802
|
})
|
|
@@ -858,8 +864,11 @@ var getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
|
858
864
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
859
865
|
};
|
|
860
866
|
var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null, null, function* () {
|
|
861
|
-
|
|
862
|
-
if (!
|
|
867
|
+
const item = tree.getItemInstance(itemId);
|
|
868
|
+
if (!item.hasLoadedData()) {
|
|
869
|
+
yield tree.loadItemData(itemId);
|
|
870
|
+
}
|
|
871
|
+
if (!tree.getConfig().isItemFolder(item)) {
|
|
863
872
|
return [itemId];
|
|
864
873
|
}
|
|
865
874
|
const childrenIds = yield tree.loadChildrenIds(itemId);
|
|
@@ -871,17 +880,24 @@ var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null,
|
|
|
871
880
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
872
881
|
});
|
|
873
882
|
var withLoadingState = (tree, itemId, callback) => __async(null, null, function* () {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
883
|
+
const prom = callback();
|
|
884
|
+
const immediate = {};
|
|
885
|
+
const firstCompleted = yield Promise.race([prom, immediate]);
|
|
886
|
+
if (firstCompleted !== immediate) {
|
|
887
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) => [
|
|
888
|
+
...items,
|
|
889
|
+
itemId
|
|
890
|
+
]);
|
|
891
|
+
try {
|
|
892
|
+
yield prom;
|
|
893
|
+
} finally {
|
|
894
|
+
tree.applySubStateUpdate(
|
|
895
|
+
"loadingCheckPropagationItems",
|
|
896
|
+
(items) => items.filter((id) => id !== itemId)
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
} else {
|
|
900
|
+
yield prom;
|
|
885
901
|
}
|
|
886
902
|
});
|
|
887
903
|
var checkboxesFeature = {
|
|
@@ -1103,16 +1119,27 @@ var hotkeysCoreFeature = {
|
|
|
1103
1119
|
|
|
1104
1120
|
// src/features/async-data-loader/feature.ts
|
|
1105
1121
|
var getDataRef = (tree) => {
|
|
1106
|
-
var _a, _b, _c, _d;
|
|
1122
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1107
1123
|
const dataRef = tree.getDataRef();
|
|
1108
1124
|
(_b = (_a = dataRef.current).itemData) != null ? _b : _a.itemData = {};
|
|
1109
1125
|
(_d = (_c = dataRef.current).childrenIds) != null ? _d : _c.childrenIds = {};
|
|
1126
|
+
(_f = (_e = dataRef.current).loadingDataSubs) != null ? _f : _e.loadingDataSubs = {};
|
|
1127
|
+
(_h = (_g = dataRef.current).loadingChildrenSubs) != null ? _h : _g.loadingChildrenSubs = {};
|
|
1110
1128
|
return dataRef;
|
|
1111
1129
|
};
|
|
1112
1130
|
var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
1113
|
-
var _a;
|
|
1131
|
+
var _a, _b;
|
|
1114
1132
|
const config = tree.getConfig();
|
|
1115
1133
|
const dataRef = getDataRef(tree);
|
|
1134
|
+
if (tree.getState().loadingItemData.includes(itemId)) {
|
|
1135
|
+
return new Promise((resolve) => {
|
|
1136
|
+
var _a2, _b2;
|
|
1137
|
+
(_b2 = (_a2 = dataRef.current.loadingDataSubs)[itemId]) != null ? _b2 : _a2[itemId] = [];
|
|
1138
|
+
dataRef.current.loadingDataSubs[itemId].push(() => {
|
|
1139
|
+
resolve(dataRef.current.itemData[itemId]);
|
|
1140
|
+
});
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1116
1143
|
if (!dataRef.current.itemData[itemId]) {
|
|
1117
1144
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1118
1145
|
...loadingItemData,
|
|
@@ -1126,13 +1153,23 @@ var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
|
1126
1153
|
"loadingItemData",
|
|
1127
1154
|
(loadingItemData) => loadingItemData.filter((id) => id !== itemId)
|
|
1128
1155
|
);
|
|
1156
|
+
(_b = dataRef.current.loadingDataSubs[itemId]) == null ? void 0 : _b.forEach((cb) => cb());
|
|
1129
1157
|
return item;
|
|
1130
1158
|
});
|
|
1131
1159
|
var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
1132
|
-
var _a, _b;
|
|
1160
|
+
var _a, _b, _c;
|
|
1133
1161
|
const config = tree.getConfig();
|
|
1134
1162
|
const dataRef = getDataRef(tree);
|
|
1135
1163
|
let childrenIds;
|
|
1164
|
+
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
1165
|
+
return new Promise((resolve) => {
|
|
1166
|
+
var _a2, _b2;
|
|
1167
|
+
(_b2 = (_a2 = dataRef.current.loadingChildrenSubs)[itemId]) != null ? _b2 : _a2[itemId] = [];
|
|
1168
|
+
dataRef.current.loadingChildrenSubs[itemId].push(() => {
|
|
1169
|
+
resolve(dataRef.current.childrenIds[itemId]);
|
|
1170
|
+
});
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1136
1173
|
if (!dataRef.current.childrenIds[itemId]) {
|
|
1137
1174
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [
|
|
1138
1175
|
...loadingItemChildrens,
|
|
@@ -1164,6 +1201,7 @@ var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
|
1164
1201
|
"loadingItemChildrens",
|
|
1165
1202
|
(loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId)
|
|
1166
1203
|
);
|
|
1204
|
+
(_c = dataRef.current.loadingChildrenSubs[itemId]) == null ? void 0 : _c.forEach((cb) => cb());
|
|
1167
1205
|
return childrenIds;
|
|
1168
1206
|
});
|
|
1169
1207
|
var asyncDataLoaderFeature = {
|
|
@@ -1240,6 +1278,10 @@ var asyncDataLoaderFeature = {
|
|
|
1240
1278
|
const dataRef = tree.getDataRef();
|
|
1241
1279
|
dataRef.current.itemData[itemId] = data;
|
|
1242
1280
|
tree.rebuildTree();
|
|
1281
|
+
},
|
|
1282
|
+
hasLoadedData: ({ tree, itemId }) => {
|
|
1283
|
+
const dataRef = tree.getDataRef();
|
|
1284
|
+
return dataRef.current.itemData[itemId] !== void 0;
|
|
1243
1285
|
}
|
|
1244
1286
|
}
|
|
1245
1287
|
};
|
|
@@ -1291,7 +1333,8 @@ var syncDataLoaderFeature = {
|
|
|
1291
1333
|
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId)
|
|
1292
1334
|
},
|
|
1293
1335
|
itemInstance: {
|
|
1294
|
-
isLoading: () => false
|
|
1336
|
+
isLoading: () => false,
|
|
1337
|
+
hasLoadedData: () => true
|
|
1295
1338
|
}
|
|
1296
1339
|
};
|
|
1297
1340
|
|
package/dist/index.mjs
CHANGED
|
@@ -207,6 +207,7 @@ var treeFeature = {
|
|
|
207
207
|
"aria-selected": "false",
|
|
208
208
|
"aria-label": item.getItemName(),
|
|
209
209
|
"aria-level": itemMeta.level + 1,
|
|
210
|
+
"aria-expanded": item.isFolder() ? item.isExpanded() : void 0,
|
|
210
211
|
tabIndex: item.isFocused() ? 0 : -1,
|
|
211
212
|
onClick: (e) => {
|
|
212
213
|
item.setFocused();
|
|
@@ -716,7 +717,9 @@ var selectionFeature = {
|
|
|
716
717
|
},
|
|
717
718
|
selectUpTo: ({ tree, item }, ctrl) => {
|
|
718
719
|
const indexA = item.getItemMeta().index;
|
|
719
|
-
const
|
|
720
|
+
const { selectUpToAnchorId } = tree.getDataRef().current;
|
|
721
|
+
const itemB = selectUpToAnchorId ? tree.getItemInstance(selectUpToAnchorId) : tree.getFocusedItem();
|
|
722
|
+
const indexB = itemB.getItemMeta().index;
|
|
720
723
|
const [a, b] = indexA < indexB ? [indexA, indexB] : [indexB, indexA];
|
|
721
724
|
const newSelectedItems = tree.getItems().slice(a, b + 1).map((treeItem) => treeItem.getItemMeta().itemId);
|
|
722
725
|
if (!ctrl) {
|
|
@@ -747,6 +750,9 @@ var selectionFeature = {
|
|
|
747
750
|
} else {
|
|
748
751
|
tree.setSelectedItems([item.getItemMeta().itemId]);
|
|
749
752
|
}
|
|
753
|
+
if (!e.shiftKey) {
|
|
754
|
+
tree.getDataRef().current.selectUpToAnchorId = item.getId();
|
|
755
|
+
}
|
|
750
756
|
(_b = (_a = prev == null ? void 0 : prev()) == null ? void 0 : _a.onClick) == null ? void 0 : _b.call(_a, e);
|
|
751
757
|
}
|
|
752
758
|
})
|
|
@@ -814,8 +820,11 @@ var getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
|
814
820
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
815
821
|
};
|
|
816
822
|
var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null, null, function* () {
|
|
817
|
-
|
|
818
|
-
if (!
|
|
823
|
+
const item = tree.getItemInstance(itemId);
|
|
824
|
+
if (!item.hasLoadedData()) {
|
|
825
|
+
yield tree.loadItemData(itemId);
|
|
826
|
+
}
|
|
827
|
+
if (!tree.getConfig().isItemFolder(item)) {
|
|
819
828
|
return [itemId];
|
|
820
829
|
}
|
|
821
830
|
const childrenIds = yield tree.loadChildrenIds(itemId);
|
|
@@ -827,17 +836,24 @@ var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null,
|
|
|
827
836
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
828
837
|
});
|
|
829
838
|
var withLoadingState = (tree, itemId, callback) => __async(null, null, function* () {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
839
|
+
const prom = callback();
|
|
840
|
+
const immediate = {};
|
|
841
|
+
const firstCompleted = yield Promise.race([prom, immediate]);
|
|
842
|
+
if (firstCompleted !== immediate) {
|
|
843
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) => [
|
|
844
|
+
...items,
|
|
845
|
+
itemId
|
|
846
|
+
]);
|
|
847
|
+
try {
|
|
848
|
+
yield prom;
|
|
849
|
+
} finally {
|
|
850
|
+
tree.applySubStateUpdate(
|
|
851
|
+
"loadingCheckPropagationItems",
|
|
852
|
+
(items) => items.filter((id) => id !== itemId)
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
} else {
|
|
856
|
+
yield prom;
|
|
841
857
|
}
|
|
842
858
|
});
|
|
843
859
|
var checkboxesFeature = {
|
|
@@ -1059,16 +1075,27 @@ var hotkeysCoreFeature = {
|
|
|
1059
1075
|
|
|
1060
1076
|
// src/features/async-data-loader/feature.ts
|
|
1061
1077
|
var getDataRef = (tree) => {
|
|
1062
|
-
var _a, _b, _c, _d;
|
|
1078
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1063
1079
|
const dataRef = tree.getDataRef();
|
|
1064
1080
|
(_b = (_a = dataRef.current).itemData) != null ? _b : _a.itemData = {};
|
|
1065
1081
|
(_d = (_c = dataRef.current).childrenIds) != null ? _d : _c.childrenIds = {};
|
|
1082
|
+
(_f = (_e = dataRef.current).loadingDataSubs) != null ? _f : _e.loadingDataSubs = {};
|
|
1083
|
+
(_h = (_g = dataRef.current).loadingChildrenSubs) != null ? _h : _g.loadingChildrenSubs = {};
|
|
1066
1084
|
return dataRef;
|
|
1067
1085
|
};
|
|
1068
1086
|
var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
1069
|
-
var _a;
|
|
1087
|
+
var _a, _b;
|
|
1070
1088
|
const config = tree.getConfig();
|
|
1071
1089
|
const dataRef = getDataRef(tree);
|
|
1090
|
+
if (tree.getState().loadingItemData.includes(itemId)) {
|
|
1091
|
+
return new Promise((resolve) => {
|
|
1092
|
+
var _a2, _b2;
|
|
1093
|
+
(_b2 = (_a2 = dataRef.current.loadingDataSubs)[itemId]) != null ? _b2 : _a2[itemId] = [];
|
|
1094
|
+
dataRef.current.loadingDataSubs[itemId].push(() => {
|
|
1095
|
+
resolve(dataRef.current.itemData[itemId]);
|
|
1096
|
+
});
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1072
1099
|
if (!dataRef.current.itemData[itemId]) {
|
|
1073
1100
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1074
1101
|
...loadingItemData,
|
|
@@ -1082,13 +1109,23 @@ var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
|
1082
1109
|
"loadingItemData",
|
|
1083
1110
|
(loadingItemData) => loadingItemData.filter((id) => id !== itemId)
|
|
1084
1111
|
);
|
|
1112
|
+
(_b = dataRef.current.loadingDataSubs[itemId]) == null ? void 0 : _b.forEach((cb) => cb());
|
|
1085
1113
|
return item;
|
|
1086
1114
|
});
|
|
1087
1115
|
var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
1088
|
-
var _a, _b;
|
|
1116
|
+
var _a, _b, _c;
|
|
1089
1117
|
const config = tree.getConfig();
|
|
1090
1118
|
const dataRef = getDataRef(tree);
|
|
1091
1119
|
let childrenIds;
|
|
1120
|
+
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
1121
|
+
return new Promise((resolve) => {
|
|
1122
|
+
var _a2, _b2;
|
|
1123
|
+
(_b2 = (_a2 = dataRef.current.loadingChildrenSubs)[itemId]) != null ? _b2 : _a2[itemId] = [];
|
|
1124
|
+
dataRef.current.loadingChildrenSubs[itemId].push(() => {
|
|
1125
|
+
resolve(dataRef.current.childrenIds[itemId]);
|
|
1126
|
+
});
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1092
1129
|
if (!dataRef.current.childrenIds[itemId]) {
|
|
1093
1130
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [
|
|
1094
1131
|
...loadingItemChildrens,
|
|
@@ -1120,6 +1157,7 @@ var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
|
1120
1157
|
"loadingItemChildrens",
|
|
1121
1158
|
(loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId)
|
|
1122
1159
|
);
|
|
1160
|
+
(_c = dataRef.current.loadingChildrenSubs[itemId]) == null ? void 0 : _c.forEach((cb) => cb());
|
|
1123
1161
|
return childrenIds;
|
|
1124
1162
|
});
|
|
1125
1163
|
var asyncDataLoaderFeature = {
|
|
@@ -1196,6 +1234,10 @@ var asyncDataLoaderFeature = {
|
|
|
1196
1234
|
const dataRef = tree.getDataRef();
|
|
1197
1235
|
dataRef.current.itemData[itemId] = data;
|
|
1198
1236
|
tree.rebuildTree();
|
|
1237
|
+
},
|
|
1238
|
+
hasLoadedData: ({ tree, itemId }) => {
|
|
1239
|
+
const dataRef = tree.getDataRef();
|
|
1240
|
+
return dataRef.current.itemData[itemId] !== void 0;
|
|
1199
1241
|
}
|
|
1200
1242
|
}
|
|
1201
1243
|
};
|
|
@@ -1247,7 +1289,8 @@ var syncDataLoaderFeature = {
|
|
|
1247
1289
|
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId)
|
|
1248
1290
|
},
|
|
1249
1291
|
itemInstance: {
|
|
1250
|
-
isLoading: () => false
|
|
1292
|
+
isLoading: () => false,
|
|
1293
|
+
hasLoadedData: () => true
|
|
1251
1294
|
}
|
|
1252
1295
|
};
|
|
1253
1296
|
|
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
|
|
|
@@ -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
|
};
|
|
@@ -144,6 +144,7 @@ export const treeFeature: FeatureImplementation<any> = {
|
|
|
144
144
|
"aria-selected": "false",
|
|
145
145
|
"aria-label": item.getItemName(),
|
|
146
146
|
"aria-level": itemMeta.level + 1,
|
|
147
|
+
"aria-expanded": item.isFolder() ? item.isExpanded() : undefined,
|
|
147
148
|
tabIndex: item.isFocused() ? 0 : -1,
|
|
148
149
|
onClick: (e: MouseEvent) => {
|
|
149
150
|
item.setFocused();
|
|
@@ -250,6 +250,7 @@ describe("core-feature/tree", () => {
|
|
|
250
250
|
"aria-posinset": 2,
|
|
251
251
|
"aria-selected": "false",
|
|
252
252
|
"aria-setsize": 4,
|
|
253
|
+
"aria-expanded": false,
|
|
253
254
|
onClick: expect.any(Function),
|
|
254
255
|
ref: expect.any(Function),
|
|
255
256
|
role: "treeitem",
|
|
@@ -264,12 +265,29 @@ describe("core-feature/tree", () => {
|
|
|
264
265
|
"aria-posinset": 1,
|
|
265
266
|
"aria-selected": "false",
|
|
266
267
|
"aria-setsize": 4,
|
|
268
|
+
"aria-expanded": true,
|
|
267
269
|
onClick: expect.any(Function),
|
|
268
270
|
ref: expect.any(Function),
|
|
269
271
|
role: "treeitem",
|
|
270
272
|
tabIndex: 0,
|
|
271
273
|
});
|
|
272
274
|
});
|
|
275
|
+
|
|
276
|
+
it("generates aria-expanded for non expanded folder", () => {
|
|
277
|
+
expect(tree.instance.getItemInstance("x3").getProps()).toEqual(
|
|
278
|
+
expect.objectContaining({
|
|
279
|
+
"aria-expanded": false,
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("generates aria-expanded for leaf", () => {
|
|
285
|
+
expect(tree.instance.getItemInstance("x111").getProps()).toEqual(
|
|
286
|
+
expect.objectContaining({
|
|
287
|
+
"aria-expanded": undefined,
|
|
288
|
+
}),
|
|
289
|
+
);
|
|
290
|
+
});
|
|
273
291
|
});
|
|
274
292
|
|
|
275
293
|
describe("util functions", () => {
|