@headless-tree/core 1.5.1 → 1.6.1
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 +17 -0
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +69 -21
- package/dist/index.mjs +69 -21
- 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 +19 -5
- package/src/features/selection/selection.spec.ts +4 -6
- 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,22 @@
|
|
|
1
1
|
# @headless-tree/core
|
|
2
2
|
|
|
3
|
+
## 1.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4ddeaf3: Fixed behavior where shift-selecting an item with no previously selected or focused item would multiselect all items from the top to the clicked item. Now, shift-selecting an item with no previously clicked items will only select the clicked item (#176)
|
|
8
|
+
|
|
9
|
+
## 1.6.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 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)
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 287fe40: Added missing `aria-expanded` attribute to tree items (thanks to @gordey4doronin for the contribution) (#171)
|
|
18
|
+
- 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)
|
|
19
|
+
|
|
3
20
|
## 1.5.1
|
|
4
21
|
|
|
5
22
|
### 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();
|
|
@@ -758,9 +759,16 @@ var selectionFeature = {
|
|
|
758
759
|
const { selectedItems } = tree.getState();
|
|
759
760
|
return selectedItems.includes(itemId);
|
|
760
761
|
},
|
|
761
|
-
selectUpTo: ({ tree, item }, ctrl) => {
|
|
762
|
+
selectUpTo: ({ tree, item, itemId }, ctrl) => {
|
|
762
763
|
const indexA = item.getItemMeta().index;
|
|
763
|
-
const
|
|
764
|
+
const dataRef = tree.getDataRef();
|
|
765
|
+
if (!dataRef.current.selectUpToAnchorId) {
|
|
766
|
+
dataRef.current.selectUpToAnchorId = itemId;
|
|
767
|
+
tree.setSelectedItems([itemId]);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const itemB = tree.getItemInstance(dataRef.current.selectUpToAnchorId);
|
|
771
|
+
const indexB = itemB.getItemMeta().index;
|
|
764
772
|
const [a, b] = indexA < indexB ? [indexA, indexB] : [indexB, indexA];
|
|
765
773
|
const newSelectedItems = tree.getItems().slice(a, b + 1).map((treeItem) => treeItem.getItemMeta().itemId);
|
|
766
774
|
if (!ctrl) {
|
|
@@ -780,7 +788,7 @@ var selectionFeature = {
|
|
|
780
788
|
item.select();
|
|
781
789
|
}
|
|
782
790
|
},
|
|
783
|
-
getProps: ({ tree, item, prev }) => __spreadProps(__spreadValues({}, prev == null ? void 0 : prev()), {
|
|
791
|
+
getProps: ({ tree, item, itemId, prev }) => __spreadProps(__spreadValues({}, prev == null ? void 0 : prev()), {
|
|
784
792
|
"aria-selected": item.isSelected() ? "true" : "false",
|
|
785
793
|
onClick: (e) => {
|
|
786
794
|
var _a, _b;
|
|
@@ -789,7 +797,10 @@ var selectionFeature = {
|
|
|
789
797
|
} else if (e.ctrlKey || e.metaKey) {
|
|
790
798
|
item.toggleSelect();
|
|
791
799
|
} else {
|
|
792
|
-
tree.setSelectedItems([
|
|
800
|
+
tree.setSelectedItems([itemId]);
|
|
801
|
+
}
|
|
802
|
+
if (!e.shiftKey) {
|
|
803
|
+
tree.getDataRef().current.selectUpToAnchorId = itemId;
|
|
793
804
|
}
|
|
794
805
|
(_b = (_a = prev == null ? void 0 : prev()) == null ? void 0 : _a.onClick) == null ? void 0 : _b.call(_a, e);
|
|
795
806
|
}
|
|
@@ -858,8 +869,11 @@ var getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
|
858
869
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
859
870
|
};
|
|
860
871
|
var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null, null, function* () {
|
|
861
|
-
|
|
862
|
-
if (!
|
|
872
|
+
const item = tree.getItemInstance(itemId);
|
|
873
|
+
if (!item.hasLoadedData()) {
|
|
874
|
+
yield tree.loadItemData(itemId);
|
|
875
|
+
}
|
|
876
|
+
if (!tree.getConfig().isItemFolder(item)) {
|
|
863
877
|
return [itemId];
|
|
864
878
|
}
|
|
865
879
|
const childrenIds = yield tree.loadChildrenIds(itemId);
|
|
@@ -871,17 +885,24 @@ var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null,
|
|
|
871
885
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
872
886
|
});
|
|
873
887
|
var withLoadingState = (tree, itemId, callback) => __async(null, null, function* () {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
888
|
+
const prom = callback();
|
|
889
|
+
const immediate = {};
|
|
890
|
+
const firstCompleted = yield Promise.race([prom, immediate]);
|
|
891
|
+
if (firstCompleted !== immediate) {
|
|
892
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) => [
|
|
893
|
+
...items,
|
|
894
|
+
itemId
|
|
895
|
+
]);
|
|
896
|
+
try {
|
|
897
|
+
yield prom;
|
|
898
|
+
} finally {
|
|
899
|
+
tree.applySubStateUpdate(
|
|
900
|
+
"loadingCheckPropagationItems",
|
|
901
|
+
(items) => items.filter((id) => id !== itemId)
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
} else {
|
|
905
|
+
yield prom;
|
|
885
906
|
}
|
|
886
907
|
});
|
|
887
908
|
var checkboxesFeature = {
|
|
@@ -1103,16 +1124,27 @@ var hotkeysCoreFeature = {
|
|
|
1103
1124
|
|
|
1104
1125
|
// src/features/async-data-loader/feature.ts
|
|
1105
1126
|
var getDataRef = (tree) => {
|
|
1106
|
-
var _a, _b, _c, _d;
|
|
1127
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1107
1128
|
const dataRef = tree.getDataRef();
|
|
1108
1129
|
(_b = (_a = dataRef.current).itemData) != null ? _b : _a.itemData = {};
|
|
1109
1130
|
(_d = (_c = dataRef.current).childrenIds) != null ? _d : _c.childrenIds = {};
|
|
1131
|
+
(_f = (_e = dataRef.current).loadingDataSubs) != null ? _f : _e.loadingDataSubs = {};
|
|
1132
|
+
(_h = (_g = dataRef.current).loadingChildrenSubs) != null ? _h : _g.loadingChildrenSubs = {};
|
|
1110
1133
|
return dataRef;
|
|
1111
1134
|
};
|
|
1112
1135
|
var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
1113
|
-
var _a;
|
|
1136
|
+
var _a, _b;
|
|
1114
1137
|
const config = tree.getConfig();
|
|
1115
1138
|
const dataRef = getDataRef(tree);
|
|
1139
|
+
if (tree.getState().loadingItemData.includes(itemId)) {
|
|
1140
|
+
return new Promise((resolve) => {
|
|
1141
|
+
var _a2, _b2;
|
|
1142
|
+
(_b2 = (_a2 = dataRef.current.loadingDataSubs)[itemId]) != null ? _b2 : _a2[itemId] = [];
|
|
1143
|
+
dataRef.current.loadingDataSubs[itemId].push(() => {
|
|
1144
|
+
resolve(dataRef.current.itemData[itemId]);
|
|
1145
|
+
});
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1116
1148
|
if (!dataRef.current.itemData[itemId]) {
|
|
1117
1149
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1118
1150
|
...loadingItemData,
|
|
@@ -1126,13 +1158,23 @@ var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
|
1126
1158
|
"loadingItemData",
|
|
1127
1159
|
(loadingItemData) => loadingItemData.filter((id) => id !== itemId)
|
|
1128
1160
|
);
|
|
1161
|
+
(_b = dataRef.current.loadingDataSubs[itemId]) == null ? void 0 : _b.forEach((cb) => cb());
|
|
1129
1162
|
return item;
|
|
1130
1163
|
});
|
|
1131
1164
|
var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
1132
|
-
var _a, _b;
|
|
1165
|
+
var _a, _b, _c;
|
|
1133
1166
|
const config = tree.getConfig();
|
|
1134
1167
|
const dataRef = getDataRef(tree);
|
|
1135
1168
|
let childrenIds;
|
|
1169
|
+
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
1170
|
+
return new Promise((resolve) => {
|
|
1171
|
+
var _a2, _b2;
|
|
1172
|
+
(_b2 = (_a2 = dataRef.current.loadingChildrenSubs)[itemId]) != null ? _b2 : _a2[itemId] = [];
|
|
1173
|
+
dataRef.current.loadingChildrenSubs[itemId].push(() => {
|
|
1174
|
+
resolve(dataRef.current.childrenIds[itemId]);
|
|
1175
|
+
});
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1136
1178
|
if (!dataRef.current.childrenIds[itemId]) {
|
|
1137
1179
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [
|
|
1138
1180
|
...loadingItemChildrens,
|
|
@@ -1164,6 +1206,7 @@ var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
|
1164
1206
|
"loadingItemChildrens",
|
|
1165
1207
|
(loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId)
|
|
1166
1208
|
);
|
|
1209
|
+
(_c = dataRef.current.loadingChildrenSubs[itemId]) == null ? void 0 : _c.forEach((cb) => cb());
|
|
1167
1210
|
return childrenIds;
|
|
1168
1211
|
});
|
|
1169
1212
|
var asyncDataLoaderFeature = {
|
|
@@ -1240,6 +1283,10 @@ var asyncDataLoaderFeature = {
|
|
|
1240
1283
|
const dataRef = tree.getDataRef();
|
|
1241
1284
|
dataRef.current.itemData[itemId] = data;
|
|
1242
1285
|
tree.rebuildTree();
|
|
1286
|
+
},
|
|
1287
|
+
hasLoadedData: ({ tree, itemId }) => {
|
|
1288
|
+
const dataRef = tree.getDataRef();
|
|
1289
|
+
return dataRef.current.itemData[itemId] !== void 0;
|
|
1243
1290
|
}
|
|
1244
1291
|
}
|
|
1245
1292
|
};
|
|
@@ -1291,7 +1338,8 @@ var syncDataLoaderFeature = {
|
|
|
1291
1338
|
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId)
|
|
1292
1339
|
},
|
|
1293
1340
|
itemInstance: {
|
|
1294
|
-
isLoading: () => false
|
|
1341
|
+
isLoading: () => false,
|
|
1342
|
+
hasLoadedData: () => true
|
|
1295
1343
|
}
|
|
1296
1344
|
};
|
|
1297
1345
|
|
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();
|
|
@@ -714,9 +715,16 @@ var selectionFeature = {
|
|
|
714
715
|
const { selectedItems } = tree.getState();
|
|
715
716
|
return selectedItems.includes(itemId);
|
|
716
717
|
},
|
|
717
|
-
selectUpTo: ({ tree, item }, ctrl) => {
|
|
718
|
+
selectUpTo: ({ tree, item, itemId }, ctrl) => {
|
|
718
719
|
const indexA = item.getItemMeta().index;
|
|
719
|
-
const
|
|
720
|
+
const dataRef = tree.getDataRef();
|
|
721
|
+
if (!dataRef.current.selectUpToAnchorId) {
|
|
722
|
+
dataRef.current.selectUpToAnchorId = itemId;
|
|
723
|
+
tree.setSelectedItems([itemId]);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
const itemB = tree.getItemInstance(dataRef.current.selectUpToAnchorId);
|
|
727
|
+
const indexB = itemB.getItemMeta().index;
|
|
720
728
|
const [a, b] = indexA < indexB ? [indexA, indexB] : [indexB, indexA];
|
|
721
729
|
const newSelectedItems = tree.getItems().slice(a, b + 1).map((treeItem) => treeItem.getItemMeta().itemId);
|
|
722
730
|
if (!ctrl) {
|
|
@@ -736,7 +744,7 @@ var selectionFeature = {
|
|
|
736
744
|
item.select();
|
|
737
745
|
}
|
|
738
746
|
},
|
|
739
|
-
getProps: ({ tree, item, prev }) => __spreadProps(__spreadValues({}, prev == null ? void 0 : prev()), {
|
|
747
|
+
getProps: ({ tree, item, itemId, prev }) => __spreadProps(__spreadValues({}, prev == null ? void 0 : prev()), {
|
|
740
748
|
"aria-selected": item.isSelected() ? "true" : "false",
|
|
741
749
|
onClick: (e) => {
|
|
742
750
|
var _a, _b;
|
|
@@ -745,7 +753,10 @@ var selectionFeature = {
|
|
|
745
753
|
} else if (e.ctrlKey || e.metaKey) {
|
|
746
754
|
item.toggleSelect();
|
|
747
755
|
} else {
|
|
748
|
-
tree.setSelectedItems([
|
|
756
|
+
tree.setSelectedItems([itemId]);
|
|
757
|
+
}
|
|
758
|
+
if (!e.shiftKey) {
|
|
759
|
+
tree.getDataRef().current.selectUpToAnchorId = itemId;
|
|
749
760
|
}
|
|
750
761
|
(_b = (_a = prev == null ? void 0 : prev()) == null ? void 0 : _a.onClick) == null ? void 0 : _b.call(_a, e);
|
|
751
762
|
}
|
|
@@ -814,8 +825,11 @@ var getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
|
814
825
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
815
826
|
};
|
|
816
827
|
var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null, null, function* () {
|
|
817
|
-
|
|
818
|
-
if (!
|
|
828
|
+
const item = tree.getItemInstance(itemId);
|
|
829
|
+
if (!item.hasLoadedData()) {
|
|
830
|
+
yield tree.loadItemData(itemId);
|
|
831
|
+
}
|
|
832
|
+
if (!tree.getConfig().isItemFolder(item)) {
|
|
819
833
|
return [itemId];
|
|
820
834
|
}
|
|
821
835
|
const childrenIds = yield tree.loadChildrenIds(itemId);
|
|
@@ -827,17 +841,24 @@ var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null,
|
|
|
827
841
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
828
842
|
});
|
|
829
843
|
var withLoadingState = (tree, itemId, callback) => __async(null, null, function* () {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
844
|
+
const prom = callback();
|
|
845
|
+
const immediate = {};
|
|
846
|
+
const firstCompleted = yield Promise.race([prom, immediate]);
|
|
847
|
+
if (firstCompleted !== immediate) {
|
|
848
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) => [
|
|
849
|
+
...items,
|
|
850
|
+
itemId
|
|
851
|
+
]);
|
|
852
|
+
try {
|
|
853
|
+
yield prom;
|
|
854
|
+
} finally {
|
|
855
|
+
tree.applySubStateUpdate(
|
|
856
|
+
"loadingCheckPropagationItems",
|
|
857
|
+
(items) => items.filter((id) => id !== itemId)
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
} else {
|
|
861
|
+
yield prom;
|
|
841
862
|
}
|
|
842
863
|
});
|
|
843
864
|
var checkboxesFeature = {
|
|
@@ -1059,16 +1080,27 @@ var hotkeysCoreFeature = {
|
|
|
1059
1080
|
|
|
1060
1081
|
// src/features/async-data-loader/feature.ts
|
|
1061
1082
|
var getDataRef = (tree) => {
|
|
1062
|
-
var _a, _b, _c, _d;
|
|
1083
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1063
1084
|
const dataRef = tree.getDataRef();
|
|
1064
1085
|
(_b = (_a = dataRef.current).itemData) != null ? _b : _a.itemData = {};
|
|
1065
1086
|
(_d = (_c = dataRef.current).childrenIds) != null ? _d : _c.childrenIds = {};
|
|
1087
|
+
(_f = (_e = dataRef.current).loadingDataSubs) != null ? _f : _e.loadingDataSubs = {};
|
|
1088
|
+
(_h = (_g = dataRef.current).loadingChildrenSubs) != null ? _h : _g.loadingChildrenSubs = {};
|
|
1066
1089
|
return dataRef;
|
|
1067
1090
|
};
|
|
1068
1091
|
var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
1069
|
-
var _a;
|
|
1092
|
+
var _a, _b;
|
|
1070
1093
|
const config = tree.getConfig();
|
|
1071
1094
|
const dataRef = getDataRef(tree);
|
|
1095
|
+
if (tree.getState().loadingItemData.includes(itemId)) {
|
|
1096
|
+
return new Promise((resolve) => {
|
|
1097
|
+
var _a2, _b2;
|
|
1098
|
+
(_b2 = (_a2 = dataRef.current.loadingDataSubs)[itemId]) != null ? _b2 : _a2[itemId] = [];
|
|
1099
|
+
dataRef.current.loadingDataSubs[itemId].push(() => {
|
|
1100
|
+
resolve(dataRef.current.itemData[itemId]);
|
|
1101
|
+
});
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1072
1104
|
if (!dataRef.current.itemData[itemId]) {
|
|
1073
1105
|
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1074
1106
|
...loadingItemData,
|
|
@@ -1082,13 +1114,23 @@ var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
|
1082
1114
|
"loadingItemData",
|
|
1083
1115
|
(loadingItemData) => loadingItemData.filter((id) => id !== itemId)
|
|
1084
1116
|
);
|
|
1117
|
+
(_b = dataRef.current.loadingDataSubs[itemId]) == null ? void 0 : _b.forEach((cb) => cb());
|
|
1085
1118
|
return item;
|
|
1086
1119
|
});
|
|
1087
1120
|
var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
1088
|
-
var _a, _b;
|
|
1121
|
+
var _a, _b, _c;
|
|
1089
1122
|
const config = tree.getConfig();
|
|
1090
1123
|
const dataRef = getDataRef(tree);
|
|
1091
1124
|
let childrenIds;
|
|
1125
|
+
if (tree.getState().loadingItemChildrens.includes(itemId)) {
|
|
1126
|
+
return new Promise((resolve) => {
|
|
1127
|
+
var _a2, _b2;
|
|
1128
|
+
(_b2 = (_a2 = dataRef.current.loadingChildrenSubs)[itemId]) != null ? _b2 : _a2[itemId] = [];
|
|
1129
|
+
dataRef.current.loadingChildrenSubs[itemId].push(() => {
|
|
1130
|
+
resolve(dataRef.current.childrenIds[itemId]);
|
|
1131
|
+
});
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1092
1134
|
if (!dataRef.current.childrenIds[itemId]) {
|
|
1093
1135
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [
|
|
1094
1136
|
...loadingItemChildrens,
|
|
@@ -1120,6 +1162,7 @@ var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
|
1120
1162
|
"loadingItemChildrens",
|
|
1121
1163
|
(loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId)
|
|
1122
1164
|
);
|
|
1165
|
+
(_c = dataRef.current.loadingChildrenSubs[itemId]) == null ? void 0 : _c.forEach((cb) => cb());
|
|
1123
1166
|
return childrenIds;
|
|
1124
1167
|
});
|
|
1125
1168
|
var asyncDataLoaderFeature = {
|
|
@@ -1196,6 +1239,10 @@ var asyncDataLoaderFeature = {
|
|
|
1196
1239
|
const dataRef = tree.getDataRef();
|
|
1197
1240
|
dataRef.current.itemData[itemId] = data;
|
|
1198
1241
|
tree.rebuildTree();
|
|
1242
|
+
},
|
|
1243
|
+
hasLoadedData: ({ tree, itemId }) => {
|
|
1244
|
+
const dataRef = tree.getDataRef();
|
|
1245
|
+
return dataRef.current.itemData[itemId] !== void 0;
|
|
1199
1246
|
}
|
|
1200
1247
|
}
|
|
1201
1248
|
};
|
|
@@ -1247,7 +1294,8 @@ var syncDataLoaderFeature = {
|
|
|
1247
1294
|
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId)
|
|
1248
1295
|
},
|
|
1249
1296
|
itemInstance: {
|
|
1250
|
-
isLoading: () => false
|
|
1297
|
+
isLoading: () => false,
|
|
1298
|
+
hasLoadedData: () => true
|
|
1251
1299
|
}
|
|
1252
1300
|
};
|
|
1253
1301
|
|
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",
|
|
@@ -48,10 +49,18 @@ export const selectionFeature: FeatureImplementation = {
|
|
|
48
49
|
return selectedItems.includes(itemId);
|
|
49
50
|
},
|
|
50
51
|
|
|
51
|
-
selectUpTo: ({ tree, item }, ctrl: boolean) => {
|
|
52
|
+
selectUpTo: ({ tree, item, itemId }, ctrl: boolean) => {
|
|
52
53
|
const indexA = item.getItemMeta().index;
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
const dataRef = tree.getDataRef<SelectionDataRef>();
|
|
55
|
+
|
|
56
|
+
if (!dataRef.current.selectUpToAnchorId) {
|
|
57
|
+
dataRef.current.selectUpToAnchorId = itemId;
|
|
58
|
+
tree.setSelectedItems([itemId]);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const itemB = tree.getItemInstance(dataRef.current.selectUpToAnchorId);
|
|
63
|
+
const indexB = itemB.getItemMeta().index;
|
|
55
64
|
const [a, b] = indexA < indexB ? [indexA, indexB] : [indexB, indexA];
|
|
56
65
|
const newSelectedItems = tree
|
|
57
66
|
.getItems()
|
|
@@ -78,7 +87,7 @@ export const selectionFeature: FeatureImplementation = {
|
|
|
78
87
|
}
|
|
79
88
|
},
|
|
80
89
|
|
|
81
|
-
getProps: ({ tree, item, prev }) => ({
|
|
90
|
+
getProps: ({ tree, item, itemId, prev }) => ({
|
|
82
91
|
...prev?.(),
|
|
83
92
|
"aria-selected": item.isSelected() ? "true" : "false",
|
|
84
93
|
onClick: (e: MouseEvent) => {
|
|
@@ -87,7 +96,12 @@ export const selectionFeature: FeatureImplementation = {
|
|
|
87
96
|
} else if (e.ctrlKey || e.metaKey) {
|
|
88
97
|
item.toggleSelect();
|
|
89
98
|
} else {
|
|
90
|
-
tree.setSelectedItems([
|
|
99
|
+
tree.setSelectedItems([itemId]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!e.shiftKey) {
|
|
103
|
+
tree.getDataRef<SelectionDataRef>().current.selectUpToAnchorId =
|
|
104
|
+
itemId;
|
|
91
105
|
}
|
|
92
106
|
|
|
93
107
|
prev?.()?.onClick?.(e);
|
|
@@ -176,9 +176,8 @@ describe("core-feature/selections", () => {
|
|
|
176
176
|
|
|
177
177
|
it("should handle selectUpTo without ctrl", () => {
|
|
178
178
|
const setSelectedItems = tree.mockedHandler("setSelectedItems");
|
|
179
|
-
tree.
|
|
180
|
-
tree.
|
|
181
|
-
tree.instance.getItemInstance("x112").setFocused();
|
|
179
|
+
tree.do.ctrlSelectItem("x111");
|
|
180
|
+
tree.do.ctrlSelectItem("x112");
|
|
182
181
|
tree.instance.getItemInstance("x114").selectUpTo(false);
|
|
183
182
|
expect(setSelectedItems).toHaveBeenCalledWith([
|
|
184
183
|
"x112",
|
|
@@ -190,9 +189,8 @@ describe("core-feature/selections", () => {
|
|
|
190
189
|
|
|
191
190
|
it("should handle selectUpTo with ctrl", () => {
|
|
192
191
|
const setSelectedItems = tree.mockedHandler("setSelectedItems");
|
|
193
|
-
tree.
|
|
194
|
-
tree.
|
|
195
|
-
tree.instance.getItemInstance("x112").setFocused();
|
|
192
|
+
tree.do.ctrlSelectItem("x111");
|
|
193
|
+
tree.do.ctrlSelectItem("x112");
|
|
196
194
|
tree.instance.getItemInstance("x114").selectUpTo(true);
|
|
197
195
|
expect(setSelectedItems).toHaveBeenCalledWith([
|
|
198
196
|
"x111",
|
|
@@ -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", () => {
|