@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/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
- state[stateName] = typeof updater === "function" ? updater(state[stateName]) : updater;
503
- const externalStateSetter = config[stateHandlerNames[stateName]];
504
- externalStateSetter == null ? void 0 : externalStateSetter(state[stateName]);
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
- rebuildItemMeta();
510
- (_a2 = config.setState) == null ? void 0 : _a2.call(config, state);
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: () => itemInstances,
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, _c;
787
- const hasAsyncLoader = (_a = defaultConfig.features) == null ? void 0 : _a.some(
788
- (f) => f.key === "async-data-loader"
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
- const { propagateCheckedState, canCheckFolders } = tree.getConfig();
849
- if (item.isFolder() && propagateCheckedState) {
850
- tree.applySubStateUpdate("checkedItems", (items) => [
851
- ...items,
852
- ...getAllLoadedDescendants(tree, itemId, canCheckFolders)
853
- ]);
854
- } else if (!item.isFolder() || canCheckFolders) {
855
- tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
856
- }
857
- },
858
- setUnchecked: ({ item, tree, itemId }) => {
859
- const { propagateCheckedState, canCheckFolders } = tree.getConfig();
860
- if (item.isFolder() && propagateCheckedState) {
861
- const descendants = getAllLoadedDescendants(
862
- tree,
863
- itemId,
864
- canCheckFolders
865
- );
866
- tree.applySubStateUpdate(
867
- "checkedItems",
868
- (items) => items.filter((id) => !descendants.includes(id) && id !== itemId)
869
- );
870
- } else {
871
- tree.applySubStateUpdate(
872
- "checkedItems",
873
- (items) => items.filter((id) => id !== itemId)
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
- tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
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.applySubStateUpdate(
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 = (e, item, tree) => {
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 (!((_b = (_a = config.canDrag) == null ? void 0 : _a.call(config, items)) != null ? _b : true)) {
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
- (_c = e.dataTransfer) == null ? void 0 : _c.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
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 nextDragCode = getDragCode(e, item, tree);
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
- if (!canDrop(e.dataTransfer, target, tree)) {
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
- const selectedItems = tree.getSelectedItems();
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
- "version": "1.3.0",
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.js",
15
- "default": "./dist/index.d.ts"
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
  [![Documentation](https://img.shields.io/badge/docs-1e1f22?style=flat)](https://headless-tree.lukasbach.com/)
4
4
  [![Chat on Discord](https://img.shields.io/badge/discord-4c57d9?style=flat&logo=discord&logoColor=ffffff)](https://discord.gg/KuZ6EezzVw)
5
- [![Follow on BLuesky](https://img.shields.io/badge/bluesky-0285FF?style=flat&logo=bluesky&logoColor=ffffff)](https://bsky.app/profile/lukasbach.bsky.social)
5
+ [![Follow on Bluesky](https://img.shields.io/badge/bluesky-0285FF?style=flat&logo=bluesky&logoColor=ffffff)](https://bsky.app/profile/lukasbach.bsky.social)
6
+ [![Follow on X](https://img.shields.io/badge/x-000000?style=flat&logo=x&logoColor=ffffff)](https://x.com/lukasmbach)
6
7
  [![Support on Github Sponsors](https://img.shields.io/badge/sponsor-EA4AAA?style=flat&logo=githubsponsors&logoColor=ffffff)](https://github.com/sponsors/lukasbach)
7
8
  [![Follow on Github](https://img.shields.io/badge/follow-181717?style=flat&logo=github&logoColor=ffffff)](https://github.com/lukasbach)
8
9
  [![NPM Core package](https://img.shields.io/badge/core-CB3837?style=flat&logo=npm&logoColor=ffffff)](https://www.npmjs.com/package/@headless-tree/core)
9
10
  [![NPM React package](https://img.shields.io/badge/react-CB3837?style=flat&logo=npm&logoColor=ffffff)](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.
@@ -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
- state[stateName] =
167
- typeof updater === "function" ? updater(state[stateName]) : updater;
168
- const externalStateSetter = config[
169
- stateHandlerNames[stateName]
170
- ] as Function;
171
- externalStateSetter?.(state[stateName]);
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
- rebuildItemMeta();
176
- config.setState?.(state);
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: () => itemInstances,
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();