@headless-tree/core 1.3.0 → 1.5.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 +29 -0
- package/dist/index.d.mts +37 -10
- package/dist/index.d.ts +37 -10
- package/dist/index.js +190 -86
- package/dist/index.mjs +190 -86
- package/package.json +19 -3
- package/readme.md +6 -6
- package/src/core/create-tree.ts +45 -9
- package/src/features/async-data-loader/async-data-loader.spec.ts +1 -0
- package/src/features/async-data-loader/feature.ts +16 -20
- package/src/features/async-data-loader/types.ts +2 -1
- package/src/features/checkboxes/checkboxes.spec.ts +111 -122
- package/src/features/checkboxes/feature.ts +89 -40
- package/src/features/checkboxes/types.ts +16 -3
- package/src/features/drag-and-drop/feature.ts +63 -8
- package/src/features/drag-and-drop/types.ts +10 -0
- package/src/features/drag-and-drop/utils.ts +8 -6
- package/src/features/hotkeys-core/feature.ts +1 -0
- package/src/features/keyboard-drag-and-drop/feature.ts +3 -1
- package/src/features/main/types.ts +9 -0
- package/src/features/sync-data-loader/types.ts +7 -1
- package/src/mddocs-entry.ts +13 -0
- package/src/test-utils/test-tree-do.ts +6 -0
- package/src/test-utils/test-tree.ts +14 -3
- package/src/types/core.ts +5 -5
package/dist/index.mjs
CHANGED
|
@@ -441,6 +441,7 @@ var createTree = (initialConfig) => {
|
|
|
441
441
|
);
|
|
442
442
|
let treeElement;
|
|
443
443
|
const treeDataRef = { current: {} };
|
|
444
|
+
let rebuildScheduled = false;
|
|
444
445
|
const itemInstancesMap = {};
|
|
445
446
|
let itemInstances = [];
|
|
446
447
|
const itemElementsMap = {};
|
|
@@ -484,6 +485,7 @@ var createTree = (initialConfig) => {
|
|
|
484
485
|
itemInstances.push(itemInstancesMap[item.itemId]);
|
|
485
486
|
}
|
|
486
487
|
}
|
|
488
|
+
rebuildScheduled = false;
|
|
487
489
|
};
|
|
488
490
|
const eachFeature = (fn) => {
|
|
489
491
|
for (const feature of additionalFeatures) {
|
|
@@ -498,16 +500,48 @@ var createTree = (initialConfig) => {
|
|
|
498
500
|
var _a2;
|
|
499
501
|
(_a2 = config.setState) == null ? void 0 : _a2.call(config, state);
|
|
500
502
|
},
|
|
503
|
+
setMounted: ({}, isMounted) => {
|
|
504
|
+
var _a2;
|
|
505
|
+
const ref = treeDataRef.current;
|
|
506
|
+
ref.isMounted = isMounted;
|
|
507
|
+
if (isMounted) {
|
|
508
|
+
(_a2 = ref.waitingForMount) == null ? void 0 : _a2.forEach((cb) => cb());
|
|
509
|
+
ref.waitingForMount = [];
|
|
510
|
+
}
|
|
511
|
+
},
|
|
501
512
|
applySubStateUpdate: ({}, stateName, updater) => {
|
|
502
|
-
|
|
503
|
-
const
|
|
504
|
-
|
|
513
|
+
var _a2;
|
|
514
|
+
const apply = () => {
|
|
515
|
+
state[stateName] = typeof updater === "function" ? updater(state[stateName]) : updater;
|
|
516
|
+
const externalStateSetter = config[stateHandlerNames[stateName]];
|
|
517
|
+
externalStateSetter == null ? void 0 : externalStateSetter(state[stateName]);
|
|
518
|
+
};
|
|
519
|
+
const ref = treeDataRef.current;
|
|
520
|
+
if (ref.isMounted) {
|
|
521
|
+
apply();
|
|
522
|
+
} else {
|
|
523
|
+
(_a2 = ref.waitingForMount) != null ? _a2 : ref.waitingForMount = [];
|
|
524
|
+
ref.waitingForMount.push(apply);
|
|
525
|
+
}
|
|
505
526
|
},
|
|
506
527
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
507
528
|
rebuildTree: () => {
|
|
508
|
-
var _a2;
|
|
509
|
-
|
|
510
|
-
(
|
|
529
|
+
var _a2, _b2;
|
|
530
|
+
const ref = treeDataRef.current;
|
|
531
|
+
if (ref.isMounted) {
|
|
532
|
+
rebuildItemMeta();
|
|
533
|
+
(_a2 = config.setState) == null ? void 0 : _a2.call(config, state);
|
|
534
|
+
} else {
|
|
535
|
+
(_b2 = ref.waitingForMount) != null ? _b2 : ref.waitingForMount = [];
|
|
536
|
+
ref.waitingForMount.push(() => {
|
|
537
|
+
var _a3;
|
|
538
|
+
rebuildItemMeta();
|
|
539
|
+
(_a3 = config.setState) == null ? void 0 : _a3.call(config, state);
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
scheduleRebuildTree: () => {
|
|
544
|
+
rebuildScheduled = true;
|
|
511
545
|
},
|
|
512
546
|
getConfig: () => config,
|
|
513
547
|
setConfig: (_, updater) => {
|
|
@@ -540,7 +574,10 @@ var createTree = (initialConfig) => {
|
|
|
540
574
|
}
|
|
541
575
|
return existingInstance;
|
|
542
576
|
},
|
|
543
|
-
getItems: () =>
|
|
577
|
+
getItems: () => {
|
|
578
|
+
if (rebuildScheduled) rebuildItemMeta();
|
|
579
|
+
return itemInstances;
|
|
580
|
+
},
|
|
544
581
|
registerElement: ({}, element) => {
|
|
545
582
|
if (treeElement === element) {
|
|
546
583
|
return;
|
|
@@ -773,33 +810,60 @@ var getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
|
773
810
|
if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) {
|
|
774
811
|
return [itemId];
|
|
775
812
|
}
|
|
776
|
-
const descendants = tree.retrieveChildrenIds(itemId).map((child) => getAllLoadedDescendants(tree, child, includeFolders)).flat();
|
|
813
|
+
const descendants = tree.retrieveChildrenIds(itemId, true).map((child) => getAllLoadedDescendants(tree, child, includeFolders)).flat();
|
|
777
814
|
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
778
815
|
};
|
|
816
|
+
var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null, null, function* () {
|
|
817
|
+
yield tree.loadItemData(itemId);
|
|
818
|
+
if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) {
|
|
819
|
+
return [itemId];
|
|
820
|
+
}
|
|
821
|
+
const childrenIds = yield tree.loadChildrenIds(itemId);
|
|
822
|
+
const descendants = (yield Promise.all(
|
|
823
|
+
childrenIds.map(
|
|
824
|
+
(child) => getAllDescendants(tree, child, includeFolders)
|
|
825
|
+
)
|
|
826
|
+
)).flat();
|
|
827
|
+
return includeFolders ? [itemId, ...descendants] : descendants;
|
|
828
|
+
});
|
|
829
|
+
var withLoadingState = (tree, itemId, callback) => __async(null, null, function* () {
|
|
830
|
+
tree.applySubStateUpdate("loadingCheckPropagationItems", (items) => [
|
|
831
|
+
...items,
|
|
832
|
+
itemId
|
|
833
|
+
]);
|
|
834
|
+
try {
|
|
835
|
+
yield callback();
|
|
836
|
+
} finally {
|
|
837
|
+
tree.applySubStateUpdate(
|
|
838
|
+
"loadingCheckPropagationItems",
|
|
839
|
+
(items) => items.filter((id) => id !== itemId)
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
779
843
|
var checkboxesFeature = {
|
|
780
844
|
key: "checkboxes",
|
|
781
845
|
overwrites: ["selection"],
|
|
782
846
|
getInitialState: (initialState) => __spreadValues({
|
|
783
|
-
checkedItems: []
|
|
847
|
+
checkedItems: [],
|
|
848
|
+
loadingCheckPropagationItems: []
|
|
784
849
|
}, initialState),
|
|
785
850
|
getDefaultConfig: (defaultConfig, tree) => {
|
|
786
|
-
var _a, _b
|
|
787
|
-
const
|
|
788
|
-
|
|
789
|
-
);
|
|
790
|
-
if (hasAsyncLoader && defaultConfig.propagateCheckedState) {
|
|
791
|
-
throwError(`propagateCheckedState not supported with async trees`);
|
|
792
|
-
}
|
|
793
|
-
const propagateCheckedState = (_b = defaultConfig.propagateCheckedState) != null ? _b : !hasAsyncLoader;
|
|
794
|
-
const canCheckFolders = (_c = defaultConfig.canCheckFolders) != null ? _c : !propagateCheckedState;
|
|
851
|
+
var _a, _b;
|
|
852
|
+
const propagateCheckedState = (_a = defaultConfig.propagateCheckedState) != null ? _a : true;
|
|
853
|
+
const canCheckFolders = (_b = defaultConfig.canCheckFolders) != null ? _b : !propagateCheckedState;
|
|
795
854
|
return __spreadValues({
|
|
796
855
|
setCheckedItems: makeStateUpdater("checkedItems", tree),
|
|
856
|
+
setLoadingCheckPropagationItems: makeStateUpdater(
|
|
857
|
+
"loadingCheckPropagationItems",
|
|
858
|
+
tree
|
|
859
|
+
),
|
|
797
860
|
propagateCheckedState,
|
|
798
861
|
canCheckFolders
|
|
799
862
|
}, defaultConfig);
|
|
800
863
|
},
|
|
801
864
|
stateHandlerNames: {
|
|
802
|
-
checkedItems: "setCheckedItems"
|
|
865
|
+
checkedItems: "setCheckedItems",
|
|
866
|
+
loadingCheckPropagationItems: "setLoadingCheckPropagationItems"
|
|
803
867
|
},
|
|
804
868
|
treeInstance: {
|
|
805
869
|
setCheckedItems: ({ tree }, checkedItems) => {
|
|
@@ -819,13 +883,13 @@ var checkboxesFeature = {
|
|
|
819
883
|
}
|
|
820
884
|
};
|
|
821
885
|
},
|
|
822
|
-
toggleCheckedState: ({ item })
|
|
886
|
+
toggleCheckedState: (_0) => __async(null, [_0], function* ({ item }) {
|
|
823
887
|
if (item.getCheckedState() === "checked" /* Checked */) {
|
|
824
|
-
item.setUnchecked();
|
|
888
|
+
yield item.setUnchecked();
|
|
825
889
|
} else {
|
|
826
|
-
item.setChecked();
|
|
890
|
+
yield item.setChecked();
|
|
827
891
|
}
|
|
828
|
-
},
|
|
892
|
+
}),
|
|
829
893
|
getCheckedState: ({ item, tree }) => {
|
|
830
894
|
const { checkedItems } = tree.getState();
|
|
831
895
|
const { propagateCheckedState } = tree.getConfig();
|
|
@@ -835,6 +899,7 @@ var checkboxesFeature = {
|
|
|
835
899
|
}
|
|
836
900
|
if (item.isFolder() && propagateCheckedState) {
|
|
837
901
|
const descendants = getAllLoadedDescendants(tree, itemId);
|
|
902
|
+
if (descendants.length === 0) return "unchecked" /* Unchecked */;
|
|
838
903
|
if (descendants.every((d) => checkedItems.includes(d))) {
|
|
839
904
|
return "checked" /* Checked */;
|
|
840
905
|
}
|
|
@@ -844,36 +909,48 @@ var checkboxesFeature = {
|
|
|
844
909
|
}
|
|
845
910
|
return "unchecked" /* Unchecked */;
|
|
846
911
|
},
|
|
847
|
-
setChecked: ({ item, tree, itemId })
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
912
|
+
setChecked: (_0) => __async(null, [_0], function* ({ item, tree, itemId }) {
|
|
913
|
+
yield withLoadingState(tree, itemId, () => __async(null, null, function* () {
|
|
914
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
915
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
916
|
+
const descendants = yield getAllDescendants(
|
|
917
|
+
tree,
|
|
918
|
+
itemId,
|
|
919
|
+
canCheckFolders
|
|
920
|
+
);
|
|
921
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
922
|
+
...items,
|
|
923
|
+
...descendants
|
|
924
|
+
]);
|
|
925
|
+
} else if (!item.isFolder() || canCheckFolders) {
|
|
926
|
+
tree.applySubStateUpdate("checkedItems", (items) => [
|
|
927
|
+
...items,
|
|
928
|
+
itemId
|
|
929
|
+
]);
|
|
930
|
+
}
|
|
931
|
+
}));
|
|
932
|
+
}),
|
|
933
|
+
setUnchecked: (_0) => __async(null, [_0], function* ({ item, tree, itemId }) {
|
|
934
|
+
yield withLoadingState(tree, itemId, () => __async(null, null, function* () {
|
|
935
|
+
const { propagateCheckedState, canCheckFolders } = tree.getConfig();
|
|
936
|
+
if (item.isFolder() && propagateCheckedState) {
|
|
937
|
+
const descendants = yield getAllDescendants(
|
|
938
|
+
tree,
|
|
939
|
+
itemId,
|
|
940
|
+
canCheckFolders
|
|
941
|
+
);
|
|
942
|
+
tree.applySubStateUpdate(
|
|
943
|
+
"checkedItems",
|
|
944
|
+
(items) => items.filter((id) => !descendants.includes(id) && id !== itemId)
|
|
945
|
+
);
|
|
946
|
+
} else {
|
|
947
|
+
tree.applySubStateUpdate(
|
|
948
|
+
"checkedItems",
|
|
949
|
+
(items) => items.filter((id) => id !== itemId)
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
}));
|
|
953
|
+
})
|
|
877
954
|
}
|
|
878
955
|
};
|
|
879
956
|
|
|
@@ -885,7 +962,8 @@ var specialKeys = {
|
|
|
885
962
|
plus: /^(NumpadAdd|Plus)$/,
|
|
886
963
|
minus: /^(NumpadSubtract|Minus)$/,
|
|
887
964
|
control: /^(ControlLeft|ControlRight)$/,
|
|
888
|
-
shift: /^(ShiftLeft|ShiftRight)
|
|
965
|
+
shift: /^(ShiftLeft|ShiftRight)$/,
|
|
966
|
+
metaorcontrol: /^(MetaLeft|MetaRight|ControlLeft|ControlRight)$/
|
|
889
967
|
};
|
|
890
968
|
var testHotkeyMatch = (pressedKeys, tree, hotkey) => {
|
|
891
969
|
const supposedKeys = hotkey.hotkey.toLowerCase().split("+");
|
|
@@ -990,6 +1068,12 @@ var loadItemData = (tree, itemId) => __async(null, null, function* () {
|
|
|
990
1068
|
var _a;
|
|
991
1069
|
const config = tree.getConfig();
|
|
992
1070
|
const dataRef = getDataRef(tree);
|
|
1071
|
+
if (!dataRef.current.itemData[itemId]) {
|
|
1072
|
+
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1073
|
+
...loadingItemData,
|
|
1074
|
+
itemId
|
|
1075
|
+
]);
|
|
1076
|
+
}
|
|
993
1077
|
const item = yield config.dataLoader.getItem(itemId);
|
|
994
1078
|
dataRef.current.itemData[itemId] = item;
|
|
995
1079
|
(_a = config.onLoadedItem) == null ? void 0 : _a.call(config, itemId, item);
|
|
@@ -1004,6 +1088,12 @@ var loadChildrenIds = (tree, itemId) => __async(null, null, function* () {
|
|
|
1004
1088
|
const config = tree.getConfig();
|
|
1005
1089
|
const dataRef = getDataRef(tree);
|
|
1006
1090
|
let childrenIds;
|
|
1091
|
+
if (!dataRef.current.childrenIds[itemId]) {
|
|
1092
|
+
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [
|
|
1093
|
+
...loadingItemChildrens,
|
|
1094
|
+
itemId
|
|
1095
|
+
]);
|
|
1096
|
+
}
|
|
1007
1097
|
if ("getChildrenWithData" in config.dataLoader) {
|
|
1008
1098
|
const children = yield config.dataLoader.getChildrenWithData(itemId);
|
|
1009
1099
|
childrenIds = children.map((c) => c.id);
|
|
@@ -1064,11 +1154,7 @@ var asyncDataLoaderFeature = {
|
|
|
1064
1154
|
return dataRef.current.itemData[itemId];
|
|
1065
1155
|
}
|
|
1066
1156
|
if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
|
|
1067
|
-
|
|
1068
|
-
...loadingItemData,
|
|
1069
|
-
itemId
|
|
1070
|
-
]);
|
|
1071
|
-
loadItemData(tree, itemId);
|
|
1157
|
+
setTimeout(() => loadItemData(tree, itemId));
|
|
1072
1158
|
}
|
|
1073
1159
|
return (_b = (_a = config.createLoadingItemData) == null ? void 0 : _a.call(config)) != null ? _b : null;
|
|
1074
1160
|
},
|
|
@@ -1080,11 +1166,7 @@ var asyncDataLoaderFeature = {
|
|
|
1080
1166
|
if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
|
|
1081
1167
|
return [];
|
|
1082
1168
|
}
|
|
1083
|
-
tree
|
|
1084
|
-
"loadingItemChildrens",
|
|
1085
|
-
(loadingItemChildrens) => [...loadingItemChildrens, itemId]
|
|
1086
|
-
);
|
|
1087
|
-
loadChildrenIds(tree, itemId);
|
|
1169
|
+
setTimeout(() => loadChildrenIds(tree, itemId));
|
|
1088
1170
|
return [];
|
|
1089
1171
|
}
|
|
1090
1172
|
},
|
|
@@ -1094,10 +1176,6 @@ var asyncDataLoaderFeature = {
|
|
|
1094
1176
|
var _a;
|
|
1095
1177
|
if (!optimistic) {
|
|
1096
1178
|
(_a = getDataRef(tree).current.itemData) == null ? true : delete _a[itemId];
|
|
1097
|
-
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
1098
|
-
...loadingItemData,
|
|
1099
|
-
itemId
|
|
1100
|
-
]);
|
|
1101
1179
|
}
|
|
1102
1180
|
yield loadItemData(tree, itemId);
|
|
1103
1181
|
}),
|
|
@@ -1105,10 +1183,6 @@ var asyncDataLoaderFeature = {
|
|
|
1105
1183
|
var _a;
|
|
1106
1184
|
if (!optimistic) {
|
|
1107
1185
|
(_a = getDataRef(tree).current.childrenIds) == null ? true : delete _a[itemId];
|
|
1108
|
-
tree.applySubStateUpdate(
|
|
1109
|
-
"loadingItemChildrens",
|
|
1110
|
-
(loadingItemChildrens) => [...loadingItemChildrens, itemId]
|
|
1111
|
-
);
|
|
1112
1186
|
}
|
|
1113
1187
|
yield loadChildrenIds(tree, itemId);
|
|
1114
1188
|
}),
|
|
@@ -1252,8 +1326,7 @@ var getTargetPlacement = (e, item, tree, canMakeChild) => {
|
|
|
1252
1326
|
}
|
|
1253
1327
|
return { type: makeChildType };
|
|
1254
1328
|
};
|
|
1255
|
-
var getDragCode = (
|
|
1256
|
-
const placement = getTargetPlacement(e, item, tree, true);
|
|
1329
|
+
var getDragCode = (item, placement) => {
|
|
1257
1330
|
return [
|
|
1258
1331
|
item.getId(),
|
|
1259
1332
|
placement.type,
|
|
@@ -1294,6 +1367,9 @@ var getDragTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) =>
|
|
|
1294
1367
|
const canMakeChild = canDrop(e.dataTransfer, itemTarget, tree);
|
|
1295
1368
|
const placement = getTargetPlacement(e, item, tree, canMakeChild);
|
|
1296
1369
|
if (!canReorder && parent && canBecomeSibling && placement.type !== 2 /* MakeChild */) {
|
|
1370
|
+
if (draggedItems == null ? void 0 : draggedItems.some((item2) => item2.isDescendentOf(parent.getId()))) {
|
|
1371
|
+
return itemTarget;
|
|
1372
|
+
}
|
|
1297
1373
|
return parentTarget;
|
|
1298
1374
|
}
|
|
1299
1375
|
if (!canReorder && parent && !canBecomeSibling) {
|
|
@@ -1328,16 +1404,29 @@ var getDragTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) =>
|
|
|
1328
1404
|
};
|
|
1329
1405
|
|
|
1330
1406
|
// src/features/drag-and-drop/feature.ts
|
|
1407
|
+
var handleAutoOpenFolder = (dataRef, tree, item, placement) => {
|
|
1408
|
+
const { openOnDropDelay } = tree.getConfig();
|
|
1409
|
+
const dragCode = dataRef.current.lastDragCode;
|
|
1410
|
+
if (!openOnDropDelay || !item.isFolder() || item.isExpanded() || placement.type !== 2 /* MakeChild */) {
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
clearTimeout(dataRef.current.autoExpandTimeout);
|
|
1414
|
+
dataRef.current.autoExpandTimeout = setTimeout(() => {
|
|
1415
|
+
if (dragCode !== dataRef.current.lastDragCode || !dataRef.current.lastAllowDrop)
|
|
1416
|
+
return;
|
|
1417
|
+
item.expand();
|
|
1418
|
+
}, openOnDropDelay);
|
|
1419
|
+
};
|
|
1331
1420
|
var defaultCanDropForeignDragObject = () => false;
|
|
1332
1421
|
var dragAndDropFeature = {
|
|
1333
1422
|
key: "drag-and-drop",
|
|
1334
|
-
deps: ["selection"],
|
|
1335
1423
|
getDefaultConfig: (defaultConfig, tree) => __spreadValues({
|
|
1336
1424
|
canDrop: (_, target) => target.item.isFolder(),
|
|
1337
1425
|
canDropForeignDragObject: defaultCanDropForeignDragObject,
|
|
1338
1426
|
canDragForeignDragObjectOver: defaultConfig.canDropForeignDragObject !== defaultCanDropForeignDragObject ? (dataTransfer) => dataTransfer.effectAllowed !== "none" : () => false,
|
|
1339
1427
|
setDndState: makeStateUpdater("dnd", tree),
|
|
1340
|
-
canReorder: true
|
|
1428
|
+
canReorder: true,
|
|
1429
|
+
openOnDropDelay: 800
|
|
1341
1430
|
}, defaultConfig),
|
|
1342
1431
|
stateHandlerNames: {
|
|
1343
1432
|
dnd: "setDndState"
|
|
@@ -1378,7 +1467,7 @@ var dragAndDropFeature = {
|
|
|
1378
1467
|
};
|
|
1379
1468
|
}
|
|
1380
1469
|
}
|
|
1381
|
-
const bb = (_f = targetItem.getElement()) == null ? void 0 : _f.getBoundingClientRect();
|
|
1470
|
+
const bb = (_f = targetItem == null ? void 0 : targetItem.getElement()) == null ? void 0 : _f.getBoundingClientRect();
|
|
1382
1471
|
if (bb) {
|
|
1383
1472
|
return {
|
|
1384
1473
|
indent,
|
|
@@ -1434,20 +1523,20 @@ var dragAndDropFeature = {
|
|
|
1434
1523
|
draggable: true,
|
|
1435
1524
|
onDragEnter: (e) => e.preventDefault(),
|
|
1436
1525
|
onDragStart: (e) => {
|
|
1437
|
-
var _a, _b, _c;
|
|
1438
|
-
const selectedItems = tree.getSelectedItems();
|
|
1526
|
+
var _a, _b, _c, _d;
|
|
1527
|
+
const selectedItems = tree.getSelectedItems ? tree.getSelectedItems() : [tree.getFocusedItem()];
|
|
1439
1528
|
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
1440
1529
|
const config = tree.getConfig();
|
|
1441
1530
|
if (!selectedItems.includes(item)) {
|
|
1442
|
-
tree.setSelectedItems([item.getItemMeta().itemId]);
|
|
1531
|
+
(_a = tree.setSelectedItems) == null ? void 0 : _a.call(tree, [item.getItemMeta().itemId]);
|
|
1443
1532
|
}
|
|
1444
|
-
if (!((
|
|
1533
|
+
if (!((_c = (_b = config.canDrag) == null ? void 0 : _b.call(config, items)) != null ? _c : true)) {
|
|
1445
1534
|
e.preventDefault();
|
|
1446
1535
|
return;
|
|
1447
1536
|
}
|
|
1448
1537
|
if (config.setDragImage) {
|
|
1449
1538
|
const { imgElement, xOffset, yOffset } = config.setDragImage(items);
|
|
1450
|
-
(
|
|
1539
|
+
(_d = e.dataTransfer) == null ? void 0 : _d.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
|
|
1451
1540
|
}
|
|
1452
1541
|
if (config.createForeignDragObject && e.dataTransfer) {
|
|
1453
1542
|
const { format, data, dropEffect, effectAllowed } = config.createForeignDragObject(items);
|
|
@@ -1464,7 +1553,8 @@ var dragAndDropFeature = {
|
|
|
1464
1553
|
var _a, _b, _c;
|
|
1465
1554
|
e.stopPropagation();
|
|
1466
1555
|
const dataRef = tree.getDataRef();
|
|
1467
|
-
const
|
|
1556
|
+
const placement = getTargetPlacement(e, item, tree, true);
|
|
1557
|
+
const nextDragCode = getDragCode(item, placement);
|
|
1468
1558
|
if (nextDragCode === dataRef.current.lastDragCode) {
|
|
1469
1559
|
if (dataRef.current.lastAllowDrop) {
|
|
1470
1560
|
e.preventDefault();
|
|
@@ -1473,6 +1563,7 @@ var dragAndDropFeature = {
|
|
|
1473
1563
|
}
|
|
1474
1564
|
dataRef.current.lastDragCode = nextDragCode;
|
|
1475
1565
|
dataRef.current.lastDragEnter = Date.now();
|
|
1566
|
+
handleAutoOpenFolder(dataRef, tree, item, placement);
|
|
1476
1567
|
const target = getDragTarget(e, item, tree);
|
|
1477
1568
|
if (!((_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems) && (!e.dataTransfer || !((_c = (_b = tree.getConfig()).canDragForeignDragObjectOver) == null ? void 0 : _c.call(_b, e.dataTransfer, target)))) {
|
|
1478
1569
|
dataRef.current.lastAllowDrop = false;
|
|
@@ -1519,12 +1610,18 @@ var dragAndDropFeature = {
|
|
|
1519
1610
|
e.stopPropagation();
|
|
1520
1611
|
const dataRef = tree.getDataRef();
|
|
1521
1612
|
const target = getDragTarget(e, item, tree);
|
|
1522
|
-
|
|
1613
|
+
const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
|
|
1614
|
+
const isValidDrop = canDrop(e.dataTransfer, target, tree);
|
|
1615
|
+
tree.applySubStateUpdate("dnd", {
|
|
1616
|
+
draggedItems: void 0,
|
|
1617
|
+
draggingOverItem: void 0,
|
|
1618
|
+
dragTarget: void 0
|
|
1619
|
+
});
|
|
1620
|
+
if (!isValidDrop) {
|
|
1523
1621
|
return;
|
|
1524
1622
|
}
|
|
1525
1623
|
e.preventDefault();
|
|
1526
1624
|
const config = tree.getConfig();
|
|
1527
|
-
const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
|
|
1528
1625
|
dataRef.current.lastDragCode = void 0;
|
|
1529
1626
|
if (draggedItems) {
|
|
1530
1627
|
yield (_b = config.onDrop) == null ? void 0 : _b.call(config, draggedItems, target);
|
|
@@ -1537,6 +1634,10 @@ var dragAndDropFeature = {
|
|
|
1537
1634
|
const target = tree.getDragTarget();
|
|
1538
1635
|
return target ? target.item.getId() === item.getId() : false;
|
|
1539
1636
|
},
|
|
1637
|
+
isUnorderedDragTarget: ({ tree, item }) => {
|
|
1638
|
+
const target = tree.getDragTarget();
|
|
1639
|
+
return target ? !isOrderedDragTarget(target) && target.item.getId() === item.getId() : false;
|
|
1640
|
+
},
|
|
1540
1641
|
isDragTargetAbove: ({ tree, item }) => {
|
|
1541
1642
|
const target = tree.getDragTarget();
|
|
1542
1643
|
if (!target || !isOrderedDragTarget(target) || target.item !== item.getParent())
|
|
@@ -1696,7 +1797,10 @@ var keyboardDragAndDropFeature = {
|
|
|
1696
1797
|
preventDefault: true,
|
|
1697
1798
|
isEnabled: (tree) => !tree.getState().dnd,
|
|
1698
1799
|
handler: (_, tree) => {
|
|
1699
|
-
|
|
1800
|
+
var _a, _b;
|
|
1801
|
+
const selectedItems = (_b = (_a = tree.getSelectedItems) == null ? void 0 : _a.call(tree)) != null ? _b : [
|
|
1802
|
+
tree.getFocusedItem()
|
|
1803
|
+
];
|
|
1700
1804
|
const focusedItem = tree.getFocusedItem();
|
|
1701
1805
|
tree.startKeyboardDrag(
|
|
1702
1806
|
selectedItems.includes(focusedItem) ? selectedItems : selectedItems.concat(focusedItem)
|
package/package.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@headless-tree/core",
|
|
3
|
-
"
|
|
3
|
+
"description": "The definitive tree component for the Web",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"tree",
|
|
6
|
+
"component",
|
|
7
|
+
"web",
|
|
8
|
+
"headless",
|
|
9
|
+
"ui",
|
|
10
|
+
"react",
|
|
11
|
+
"nested",
|
|
12
|
+
"async",
|
|
13
|
+
"checkbox",
|
|
14
|
+
"hook"
|
|
15
|
+
],
|
|
16
|
+
"version": "1.5.0",
|
|
4
17
|
"main": "dist/index.d.ts",
|
|
5
18
|
"module": "dist/index.mjs",
|
|
6
19
|
"types": "dist/index.d.mts",
|
|
@@ -11,13 +24,16 @@
|
|
|
11
24
|
"default": "./dist/index.mjs"
|
|
12
25
|
},
|
|
13
26
|
"require": {
|
|
14
|
-
"types": "./dist/index.
|
|
15
|
-
"default": "./dist/index.
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
16
29
|
}
|
|
17
30
|
},
|
|
18
31
|
"./package.json": "./package.json"
|
|
19
32
|
},
|
|
20
33
|
"sideEffects": false,
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"provenance": true
|
|
36
|
+
},
|
|
21
37
|
"scripts": {
|
|
22
38
|
"build": "tsup ./src/index.ts --format esm,cjs --dts",
|
|
23
39
|
"start": "tsup ./src/index.ts --format esm,cjs --dts --watch",
|
package/readme.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://headless-tree.lukasbach.com/)
|
|
4
4
|
[](https://discord.gg/KuZ6EezzVw)
|
|
5
|
-
[](https://bsky.app/profile/lukasbach.bsky.social)
|
|
6
|
+
[](https://x.com/lukasmbach)
|
|
6
7
|
[](https://github.com/sponsors/lukasbach)
|
|
7
8
|
[](https://github.com/lukasbach)
|
|
8
9
|
[](https://www.npmjs.com/package/@headless-tree/core)
|
|
9
10
|
[](https://www.npmjs.com/package/@headless-tree/react)
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
Super-easy integration of complex tree components into React. Supports ordered
|
|
12
|
+
Super-easy integration of complex tree components into React. Supports ordered
|
|
13
13
|
and unordered drag-and-drop, extensive keybindings, search, renaming and more.
|
|
14
14
|
Fully customizable and accessible. Headless Tree is the official successor for
|
|
15
15
|
[react-complex-tree](https://github.com/lukasbach/react-complex-tree).
|
|
@@ -18,7 +18,7 @@ It aims to bring the many features of complex tree views, like multi-select,
|
|
|
18
18
|
drag-and-drop, keyboard navigation, tree search, renaming and more, while
|
|
19
19
|
being unopinionated about the styling and rendering of the tree itself.
|
|
20
20
|
Accessibility is ensured by default, and the integration is extremely
|
|
21
|
-
simple and flexible.
|
|
21
|
+
simple and flexible.
|
|
22
22
|
|
|
23
23
|
The interface gives you a flat list of tree nodes
|
|
24
24
|
that you can easily render yourself, which keeps the complexity of the
|
|
@@ -39,7 +39,7 @@ to get an idea of what you can do with it.
|
|
|
39
39
|
> I have collected feedback and fixed any bugs that might arise. I've written
|
|
40
40
|
> [a blog post](https://medium.com/@lukasbach/headless-tree-and-the-future-of-react-complex-tree-fc920700e82a)
|
|
41
41
|
> about the details of the change, and the future of the library.
|
|
42
|
-
>
|
|
42
|
+
>
|
|
43
43
|
> Join
|
|
44
44
|
> [the Discord](https://discord.gg/KuZ6EezzVw) to get involved, and
|
|
45
45
|
> [follow on Bluesky](https://bsky.app/profile/lukasbach.bsky.social) to
|
|
@@ -154,4 +154,4 @@ Then, render your tree based on the tree instance returned from the hook:
|
|
|
154
154
|
```
|
|
155
155
|
|
|
156
156
|
Read on in the [get started guide](https://headless-tree.lukasbach.com/getstarted) to learn more about
|
|
157
|
-
how to use Headless Tree, and how to customize it to your needs.
|
|
157
|
+
how to use Headless Tree, and how to customize it to your needs.
|
package/src/core/create-tree.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { treeFeature } from "../features/tree/feature";
|
|
|
11
11
|
import { ItemMeta } from "../features/tree/types";
|
|
12
12
|
import { buildStaticInstance } from "./build-static-instance";
|
|
13
13
|
import { throwError } from "../utilities/errors";
|
|
14
|
+
import type { TreeDataRef } from "../features/main/types";
|
|
14
15
|
|
|
15
16
|
const verifyFeatures = (features: FeatureImplementation[] | undefined) => {
|
|
16
17
|
const loadedFeatures = features?.map((feature) => feature.key);
|
|
@@ -92,6 +93,7 @@ export const createTree = <T>(
|
|
|
92
93
|
let treeElement: HTMLElement | undefined | null;
|
|
93
94
|
const treeDataRef: { current: any } = { current: {} };
|
|
94
95
|
|
|
96
|
+
let rebuildScheduled = false;
|
|
95
97
|
const itemInstancesMap: Record<string, ItemInstance<T>> = {};
|
|
96
98
|
let itemInstances: ItemInstance<T>[] = [];
|
|
97
99
|
const itemElementsMap: Record<string, HTMLElement | undefined | null> = {};
|
|
@@ -140,6 +142,8 @@ export const createTree = <T>(
|
|
|
140
142
|
itemInstances.push(itemInstancesMap[item.itemId]);
|
|
141
143
|
}
|
|
142
144
|
}
|
|
145
|
+
|
|
146
|
+
rebuildScheduled = false;
|
|
143
147
|
};
|
|
144
148
|
|
|
145
149
|
const eachFeature = (fn: (feature: FeatureImplementation<any>) => void) => {
|
|
@@ -158,22 +162,51 @@ export const createTree = <T>(
|
|
|
158
162
|
config.setState?.(state); // TODO this cant be right... This doesnt allow external state updates
|
|
159
163
|
// TODO this is never used, remove
|
|
160
164
|
},
|
|
165
|
+
setMounted: ({}, isMounted) => {
|
|
166
|
+
const ref = treeDataRef.current as TreeDataRef;
|
|
167
|
+
ref.isMounted = isMounted;
|
|
168
|
+
if (isMounted) {
|
|
169
|
+
ref.waitingForMount?.forEach((cb) => cb());
|
|
170
|
+
ref.waitingForMount = [];
|
|
171
|
+
}
|
|
172
|
+
},
|
|
161
173
|
applySubStateUpdate: <K extends keyof TreeState<any>>(
|
|
162
174
|
{},
|
|
163
175
|
stateName: K,
|
|
164
176
|
updater: Updater<TreeState<T>[K]>,
|
|
165
177
|
) => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
178
|
+
const apply = () => {
|
|
179
|
+
state[stateName] =
|
|
180
|
+
typeof updater === "function" ? updater(state[stateName]) : updater;
|
|
181
|
+
const externalStateSetter = config[
|
|
182
|
+
stateHandlerNames[stateName]
|
|
183
|
+
] as Function;
|
|
184
|
+
externalStateSetter?.(state[stateName]);
|
|
185
|
+
};
|
|
186
|
+
const ref = treeDataRef.current as TreeDataRef;
|
|
187
|
+
if (ref.isMounted) {
|
|
188
|
+
apply();
|
|
189
|
+
} else {
|
|
190
|
+
ref.waitingForMount ??= [];
|
|
191
|
+
ref.waitingForMount.push(apply);
|
|
192
|
+
}
|
|
172
193
|
},
|
|
173
194
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
174
195
|
rebuildTree: () => {
|
|
175
|
-
|
|
176
|
-
|
|
196
|
+
const ref = treeDataRef.current as TreeDataRef;
|
|
197
|
+
if (ref.isMounted) {
|
|
198
|
+
rebuildItemMeta();
|
|
199
|
+
config.setState?.(state);
|
|
200
|
+
} else {
|
|
201
|
+
ref.waitingForMount ??= [];
|
|
202
|
+
ref.waitingForMount.push(() => {
|
|
203
|
+
rebuildItemMeta();
|
|
204
|
+
config.setState?.(state);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
scheduleRebuildTree: () => {
|
|
209
|
+
rebuildScheduled = true;
|
|
177
210
|
},
|
|
178
211
|
getConfig: () => config,
|
|
179
212
|
setConfig: (_, updater) => {
|
|
@@ -210,7 +243,10 @@ export const createTree = <T>(
|
|
|
210
243
|
}
|
|
211
244
|
return existingInstance;
|
|
212
245
|
},
|
|
213
|
-
getItems: () =>
|
|
246
|
+
getItems: () => {
|
|
247
|
+
if (rebuildScheduled) rebuildItemMeta();
|
|
248
|
+
return itemInstances;
|
|
249
|
+
},
|
|
214
250
|
registerElement: ({}, element) => {
|
|
215
251
|
if (treeElement === element) {
|
|
216
252
|
return;
|
|
@@ -46,6 +46,7 @@ describe("core-feature/selections", () => {
|
|
|
46
46
|
);
|
|
47
47
|
const setLoadingItemData = tree.mockedHandler("setLoadingItemData");
|
|
48
48
|
tree.do.selectItem("x12");
|
|
49
|
+
await tree.do.awaitNextTick();
|
|
49
50
|
expect(setLoadingItemChildrens).toHaveBeenCalledWith(["x12"]);
|
|
50
51
|
expect(setLoadingItemData).not.toHaveBeenCalled();
|
|
51
52
|
await tree.resolveAsyncVisibleItems();
|