@headless-tree/core 0.0.0-20250807215101 → 0.0.0-20250818221556

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 CHANGED
@@ -1,6 +1,24 @@
1
1
  # @headless-tree/core
2
2
 
3
- ## 0.0.0-20250807215101
3
+ ## 0.0.0-20250818221556
4
+
5
+ ### Patch Changes
6
+
7
+ - 215ab4b: add a new symbol that can be used in hotkey configurations "metaorcontrol" that will trigger if either any windows control key or mac meta key is pressed (#141)
8
+
9
+ ## 1.4.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 7ef4864: added feature where closed items are auto-expanded briefly after dragging onto them. set config option `openOnDropDelay` to zero to disable.
14
+
15
+ ### Patch Changes
16
+
17
+ - 8d53b4f: fixed a bug where external changes to focused or selected items don't trigger a rerender (#150)
18
+ - 1cee368: fixed a bug where the drag line is not cleared after drop (#149)
19
+ - 1e833bb: drag-and-drop feature is no longer dependent on selection feature, and fill default to focused item if selection feature is missing (#143)
20
+
21
+ ## 1.3.0
4
22
 
5
23
  ### Minor Changes
6
24
 
package/dist/index.d.mts CHANGED
@@ -2,6 +2,7 @@ interface DndDataRef {
2
2
  lastDragCode?: string;
3
3
  lastAllowDrop?: boolean;
4
4
  lastDragEnter?: number;
5
+ autoExpandTimeout?: any;
5
6
  windowDragEndListener?: () => void;
6
7
  }
7
8
  interface DndState<T> {
@@ -65,6 +66,8 @@ type DragAndDropFeatureDef<T> = {
65
66
  onDrop?: (items: ItemInstance<T>[], target: DragTarget<T>) => void | Promise<void>;
66
67
  onDropForeignDragObject?: (dataTransfer: DataTransfer, target: DragTarget<T>) => void | Promise<void>;
67
68
  onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void;
69
+ /** When dragging for this many ms on a closed folder, the folder will automatically open. Set to zero to disable. */
70
+ openOnDropDelay?: number;
68
71
  };
69
72
  treeInstance: {
70
73
  getDragTarget: () => DragTarget<T> | null;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ interface DndDataRef {
2
2
  lastDragCode?: string;
3
3
  lastAllowDrop?: boolean;
4
4
  lastDragEnter?: number;
5
+ autoExpandTimeout?: any;
5
6
  windowDragEndListener?: () => void;
6
7
  }
7
8
  interface DndState<T> {
@@ -65,6 +66,8 @@ type DragAndDropFeatureDef<T> = {
65
66
  onDrop?: (items: ItemInstance<T>[], target: DragTarget<T>) => void | Promise<void>;
66
67
  onDropForeignDragObject?: (dataTransfer: DataTransfer, target: DragTarget<T>) => void | Promise<void>;
67
68
  onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void;
69
+ /** When dragging for this many ms on a closed folder, the folder will automatically open. Set to zero to disable. */
70
+ openOnDropDelay?: number;
68
71
  };
69
72
  treeInstance: {
70
73
  getDragTarget: () => DragTarget<T> | null;
package/dist/index.js CHANGED
@@ -929,7 +929,8 @@ var specialKeys = {
929
929
  plus: /^(NumpadAdd|Plus)$/,
930
930
  minus: /^(NumpadSubtract|Minus)$/,
931
931
  control: /^(ControlLeft|ControlRight)$/,
932
- shift: /^(ShiftLeft|ShiftRight)$/
932
+ shift: /^(ShiftLeft|ShiftRight)$/,
933
+ metaorcontrol: /^(MetaLeft|MetaRight|ControlLeft|ControlRight)$/
933
934
  };
934
935
  var testHotkeyMatch = (pressedKeys, tree, hotkey) => {
935
936
  const supposedKeys = hotkey.hotkey.toLowerCase().split("+");
@@ -1296,8 +1297,7 @@ var getTargetPlacement = (e, item, tree, canMakeChild) => {
1296
1297
  }
1297
1298
  return { type: makeChildType };
1298
1299
  };
1299
- var getDragCode = (e, item, tree) => {
1300
- const placement = getTargetPlacement(e, item, tree, true);
1300
+ var getDragCode = (item, placement) => {
1301
1301
  return [
1302
1302
  item.getId(),
1303
1303
  placement.type,
@@ -1338,6 +1338,9 @@ var getDragTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) =>
1338
1338
  const canMakeChild = canDrop(e.dataTransfer, itemTarget, tree);
1339
1339
  const placement = getTargetPlacement(e, item, tree, canMakeChild);
1340
1340
  if (!canReorder && parent && canBecomeSibling && placement.type !== 2 /* MakeChild */) {
1341
+ if (draggedItems == null ? void 0 : draggedItems.some((item2) => item2.isDescendentOf(parent.getId()))) {
1342
+ return itemTarget;
1343
+ }
1341
1344
  return parentTarget;
1342
1345
  }
1343
1346
  if (!canReorder && parent && !canBecomeSibling) {
@@ -1372,16 +1375,29 @@ var getDragTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) =>
1372
1375
  };
1373
1376
 
1374
1377
  // src/features/drag-and-drop/feature.ts
1378
+ var handleAutoOpenFolder = (dataRef, tree, item, placement) => {
1379
+ const { openOnDropDelay } = tree.getConfig();
1380
+ const dragCode = dataRef.current.lastDragCode;
1381
+ if (!openOnDropDelay || !item.isFolder() || item.isExpanded() || placement.type !== 2 /* MakeChild */) {
1382
+ return;
1383
+ }
1384
+ clearTimeout(dataRef.current.autoExpandTimeout);
1385
+ dataRef.current.autoExpandTimeout = setTimeout(() => {
1386
+ if (dragCode !== dataRef.current.lastDragCode || !dataRef.current.lastAllowDrop)
1387
+ return;
1388
+ item.expand();
1389
+ }, openOnDropDelay);
1390
+ };
1375
1391
  var defaultCanDropForeignDragObject = () => false;
1376
1392
  var dragAndDropFeature = {
1377
1393
  key: "drag-and-drop",
1378
- deps: ["selection"],
1379
1394
  getDefaultConfig: (defaultConfig, tree) => __spreadValues({
1380
1395
  canDrop: (_, target) => target.item.isFolder(),
1381
1396
  canDropForeignDragObject: defaultCanDropForeignDragObject,
1382
1397
  canDragForeignDragObjectOver: defaultConfig.canDropForeignDragObject !== defaultCanDropForeignDragObject ? (dataTransfer) => dataTransfer.effectAllowed !== "none" : () => false,
1383
1398
  setDndState: makeStateUpdater("dnd", tree),
1384
- canReorder: true
1399
+ canReorder: true,
1400
+ openOnDropDelay: 800
1385
1401
  }, defaultConfig),
1386
1402
  stateHandlerNames: {
1387
1403
  dnd: "setDndState"
@@ -1422,7 +1438,7 @@ var dragAndDropFeature = {
1422
1438
  };
1423
1439
  }
1424
1440
  }
1425
- const bb = (_f = targetItem.getElement()) == null ? void 0 : _f.getBoundingClientRect();
1441
+ const bb = (_f = targetItem == null ? void 0 : targetItem.getElement()) == null ? void 0 : _f.getBoundingClientRect();
1426
1442
  if (bb) {
1427
1443
  return {
1428
1444
  indent,
@@ -1478,20 +1494,20 @@ var dragAndDropFeature = {
1478
1494
  draggable: true,
1479
1495
  onDragEnter: (e) => e.preventDefault(),
1480
1496
  onDragStart: (e) => {
1481
- var _a, _b, _c;
1482
- const selectedItems = tree.getSelectedItems();
1497
+ var _a, _b, _c, _d;
1498
+ const selectedItems = tree.getSelectedItems ? tree.getSelectedItems() : [tree.getFocusedItem()];
1483
1499
  const items = selectedItems.includes(item) ? selectedItems : [item];
1484
1500
  const config = tree.getConfig();
1485
1501
  if (!selectedItems.includes(item)) {
1486
- tree.setSelectedItems([item.getItemMeta().itemId]);
1502
+ (_a = tree.setSelectedItems) == null ? void 0 : _a.call(tree, [item.getItemMeta().itemId]);
1487
1503
  }
1488
- if (!((_b = (_a = config.canDrag) == null ? void 0 : _a.call(config, items)) != null ? _b : true)) {
1504
+ if (!((_c = (_b = config.canDrag) == null ? void 0 : _b.call(config, items)) != null ? _c : true)) {
1489
1505
  e.preventDefault();
1490
1506
  return;
1491
1507
  }
1492
1508
  if (config.setDragImage) {
1493
1509
  const { imgElement, xOffset, yOffset } = config.setDragImage(items);
1494
- (_c = e.dataTransfer) == null ? void 0 : _c.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
1510
+ (_d = e.dataTransfer) == null ? void 0 : _d.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
1495
1511
  }
1496
1512
  if (config.createForeignDragObject && e.dataTransfer) {
1497
1513
  const { format, data, dropEffect, effectAllowed } = config.createForeignDragObject(items);
@@ -1508,7 +1524,8 @@ var dragAndDropFeature = {
1508
1524
  var _a, _b, _c;
1509
1525
  e.stopPropagation();
1510
1526
  const dataRef = tree.getDataRef();
1511
- const nextDragCode = getDragCode(e, item, tree);
1527
+ const placement = getTargetPlacement(e, item, tree, true);
1528
+ const nextDragCode = getDragCode(item, placement);
1512
1529
  if (nextDragCode === dataRef.current.lastDragCode) {
1513
1530
  if (dataRef.current.lastAllowDrop) {
1514
1531
  e.preventDefault();
@@ -1517,6 +1534,7 @@ var dragAndDropFeature = {
1517
1534
  }
1518
1535
  dataRef.current.lastDragCode = nextDragCode;
1519
1536
  dataRef.current.lastDragEnter = Date.now();
1537
+ handleAutoOpenFolder(dataRef, tree, item, placement);
1520
1538
  const target = getDragTarget(e, item, tree);
1521
1539
  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)))) {
1522
1540
  dataRef.current.lastAllowDrop = false;
@@ -1563,12 +1581,18 @@ var dragAndDropFeature = {
1563
1581
  e.stopPropagation();
1564
1582
  const dataRef = tree.getDataRef();
1565
1583
  const target = getDragTarget(e, item, tree);
1566
- if (!canDrop(e.dataTransfer, target, tree)) {
1584
+ const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
1585
+ const isValidDrop = canDrop(e.dataTransfer, target, tree);
1586
+ tree.applySubStateUpdate("dnd", {
1587
+ draggedItems: void 0,
1588
+ draggingOverItem: void 0,
1589
+ dragTarget: void 0
1590
+ });
1591
+ if (!isValidDrop) {
1567
1592
  return;
1568
1593
  }
1569
1594
  e.preventDefault();
1570
1595
  const config = tree.getConfig();
1571
- const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
1572
1596
  dataRef.current.lastDragCode = void 0;
1573
1597
  if (draggedItems) {
1574
1598
  yield (_b = config.onDrop) == null ? void 0 : _b.call(config, draggedItems, target);
@@ -1740,7 +1764,10 @@ var keyboardDragAndDropFeature = {
1740
1764
  preventDefault: true,
1741
1765
  isEnabled: (tree) => !tree.getState().dnd,
1742
1766
  handler: (_, tree) => {
1743
- const selectedItems = tree.getSelectedItems();
1767
+ var _a, _b;
1768
+ const selectedItems = (_b = (_a = tree.getSelectedItems) == null ? void 0 : _a.call(tree)) != null ? _b : [
1769
+ tree.getFocusedItem()
1770
+ ];
1744
1771
  const focusedItem = tree.getFocusedItem();
1745
1772
  tree.startKeyboardDrag(
1746
1773
  selectedItems.includes(focusedItem) ? selectedItems : selectedItems.concat(focusedItem)
package/dist/index.mjs CHANGED
@@ -885,7 +885,8 @@ var specialKeys = {
885
885
  plus: /^(NumpadAdd|Plus)$/,
886
886
  minus: /^(NumpadSubtract|Minus)$/,
887
887
  control: /^(ControlLeft|ControlRight)$/,
888
- shift: /^(ShiftLeft|ShiftRight)$/
888
+ shift: /^(ShiftLeft|ShiftRight)$/,
889
+ metaorcontrol: /^(MetaLeft|MetaRight|ControlLeft|ControlRight)$/
889
890
  };
890
891
  var testHotkeyMatch = (pressedKeys, tree, hotkey) => {
891
892
  const supposedKeys = hotkey.hotkey.toLowerCase().split("+");
@@ -1252,8 +1253,7 @@ var getTargetPlacement = (e, item, tree, canMakeChild) => {
1252
1253
  }
1253
1254
  return { type: makeChildType };
1254
1255
  };
1255
- var getDragCode = (e, item, tree) => {
1256
- const placement = getTargetPlacement(e, item, tree, true);
1256
+ var getDragCode = (item, placement) => {
1257
1257
  return [
1258
1258
  item.getId(),
1259
1259
  placement.type,
@@ -1294,6 +1294,9 @@ var getDragTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) =>
1294
1294
  const canMakeChild = canDrop(e.dataTransfer, itemTarget, tree);
1295
1295
  const placement = getTargetPlacement(e, item, tree, canMakeChild);
1296
1296
  if (!canReorder && parent && canBecomeSibling && placement.type !== 2 /* MakeChild */) {
1297
+ if (draggedItems == null ? void 0 : draggedItems.some((item2) => item2.isDescendentOf(parent.getId()))) {
1298
+ return itemTarget;
1299
+ }
1297
1300
  return parentTarget;
1298
1301
  }
1299
1302
  if (!canReorder && parent && !canBecomeSibling) {
@@ -1328,16 +1331,29 @@ var getDragTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) =>
1328
1331
  };
1329
1332
 
1330
1333
  // src/features/drag-and-drop/feature.ts
1334
+ var handleAutoOpenFolder = (dataRef, tree, item, placement) => {
1335
+ const { openOnDropDelay } = tree.getConfig();
1336
+ const dragCode = dataRef.current.lastDragCode;
1337
+ if (!openOnDropDelay || !item.isFolder() || item.isExpanded() || placement.type !== 2 /* MakeChild */) {
1338
+ return;
1339
+ }
1340
+ clearTimeout(dataRef.current.autoExpandTimeout);
1341
+ dataRef.current.autoExpandTimeout = setTimeout(() => {
1342
+ if (dragCode !== dataRef.current.lastDragCode || !dataRef.current.lastAllowDrop)
1343
+ return;
1344
+ item.expand();
1345
+ }, openOnDropDelay);
1346
+ };
1331
1347
  var defaultCanDropForeignDragObject = () => false;
1332
1348
  var dragAndDropFeature = {
1333
1349
  key: "drag-and-drop",
1334
- deps: ["selection"],
1335
1350
  getDefaultConfig: (defaultConfig, tree) => __spreadValues({
1336
1351
  canDrop: (_, target) => target.item.isFolder(),
1337
1352
  canDropForeignDragObject: defaultCanDropForeignDragObject,
1338
1353
  canDragForeignDragObjectOver: defaultConfig.canDropForeignDragObject !== defaultCanDropForeignDragObject ? (dataTransfer) => dataTransfer.effectAllowed !== "none" : () => false,
1339
1354
  setDndState: makeStateUpdater("dnd", tree),
1340
- canReorder: true
1355
+ canReorder: true,
1356
+ openOnDropDelay: 800
1341
1357
  }, defaultConfig),
1342
1358
  stateHandlerNames: {
1343
1359
  dnd: "setDndState"
@@ -1378,7 +1394,7 @@ var dragAndDropFeature = {
1378
1394
  };
1379
1395
  }
1380
1396
  }
1381
- const bb = (_f = targetItem.getElement()) == null ? void 0 : _f.getBoundingClientRect();
1397
+ const bb = (_f = targetItem == null ? void 0 : targetItem.getElement()) == null ? void 0 : _f.getBoundingClientRect();
1382
1398
  if (bb) {
1383
1399
  return {
1384
1400
  indent,
@@ -1434,20 +1450,20 @@ var dragAndDropFeature = {
1434
1450
  draggable: true,
1435
1451
  onDragEnter: (e) => e.preventDefault(),
1436
1452
  onDragStart: (e) => {
1437
- var _a, _b, _c;
1438
- const selectedItems = tree.getSelectedItems();
1453
+ var _a, _b, _c, _d;
1454
+ const selectedItems = tree.getSelectedItems ? tree.getSelectedItems() : [tree.getFocusedItem()];
1439
1455
  const items = selectedItems.includes(item) ? selectedItems : [item];
1440
1456
  const config = tree.getConfig();
1441
1457
  if (!selectedItems.includes(item)) {
1442
- tree.setSelectedItems([item.getItemMeta().itemId]);
1458
+ (_a = tree.setSelectedItems) == null ? void 0 : _a.call(tree, [item.getItemMeta().itemId]);
1443
1459
  }
1444
- if (!((_b = (_a = config.canDrag) == null ? void 0 : _a.call(config, items)) != null ? _b : true)) {
1460
+ if (!((_c = (_b = config.canDrag) == null ? void 0 : _b.call(config, items)) != null ? _c : true)) {
1445
1461
  e.preventDefault();
1446
1462
  return;
1447
1463
  }
1448
1464
  if (config.setDragImage) {
1449
1465
  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);
1466
+ (_d = e.dataTransfer) == null ? void 0 : _d.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
1451
1467
  }
1452
1468
  if (config.createForeignDragObject && e.dataTransfer) {
1453
1469
  const { format, data, dropEffect, effectAllowed } = config.createForeignDragObject(items);
@@ -1464,7 +1480,8 @@ var dragAndDropFeature = {
1464
1480
  var _a, _b, _c;
1465
1481
  e.stopPropagation();
1466
1482
  const dataRef = tree.getDataRef();
1467
- const nextDragCode = getDragCode(e, item, tree);
1483
+ const placement = getTargetPlacement(e, item, tree, true);
1484
+ const nextDragCode = getDragCode(item, placement);
1468
1485
  if (nextDragCode === dataRef.current.lastDragCode) {
1469
1486
  if (dataRef.current.lastAllowDrop) {
1470
1487
  e.preventDefault();
@@ -1473,6 +1490,7 @@ var dragAndDropFeature = {
1473
1490
  }
1474
1491
  dataRef.current.lastDragCode = nextDragCode;
1475
1492
  dataRef.current.lastDragEnter = Date.now();
1493
+ handleAutoOpenFolder(dataRef, tree, item, placement);
1476
1494
  const target = getDragTarget(e, item, tree);
1477
1495
  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
1496
  dataRef.current.lastAllowDrop = false;
@@ -1519,12 +1537,18 @@ var dragAndDropFeature = {
1519
1537
  e.stopPropagation();
1520
1538
  const dataRef = tree.getDataRef();
1521
1539
  const target = getDragTarget(e, item, tree);
1522
- if (!canDrop(e.dataTransfer, target, tree)) {
1540
+ const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
1541
+ const isValidDrop = canDrop(e.dataTransfer, target, tree);
1542
+ tree.applySubStateUpdate("dnd", {
1543
+ draggedItems: void 0,
1544
+ draggingOverItem: void 0,
1545
+ dragTarget: void 0
1546
+ });
1547
+ if (!isValidDrop) {
1523
1548
  return;
1524
1549
  }
1525
1550
  e.preventDefault();
1526
1551
  const config = tree.getConfig();
1527
- const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
1528
1552
  dataRef.current.lastDragCode = void 0;
1529
1553
  if (draggedItems) {
1530
1554
  yield (_b = config.onDrop) == null ? void 0 : _b.call(config, draggedItems, target);
@@ -1696,7 +1720,10 @@ var keyboardDragAndDropFeature = {
1696
1720
  preventDefault: true,
1697
1721
  isEnabled: (tree) => !tree.getState().dnd,
1698
1722
  handler: (_, tree) => {
1699
- const selectedItems = tree.getSelectedItems();
1723
+ var _a, _b;
1724
+ const selectedItems = (_b = (_a = tree.getSelectedItems) == null ? void 0 : _a.call(tree)) != null ? _b : [
1725
+ tree.getFocusedItem()
1726
+ ];
1700
1727
  const focusedItem = tree.getFocusedItem();
1701
1728
  tree.startKeyboardDrag(
1702
1729
  selectedItems.includes(focusedItem) ? selectedItems : selectedItems.concat(focusedItem)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-tree/core",
3
- "version": "0.0.0-20250807215101",
3
+ "version": "0.0.0-20250818221556",
4
4
  "main": "dist/index.d.ts",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.mts",
@@ -1,17 +1,51 @@
1
- import { FeatureImplementation } from "../../types/core";
1
+ import {
2
+ FeatureImplementation,
3
+ type ItemInstance,
4
+ type TreeInstance,
5
+ } from "../../types/core";
2
6
  import { DndDataRef, DragLineData, DragTarget } from "./types";
3
7
  import {
8
+ PlacementType,
9
+ type TargetPlacement,
4
10
  canDrop,
5
11
  getDragCode,
6
12
  getDragTarget,
13
+ getTargetPlacement,
7
14
  isOrderedDragTarget,
8
15
  } from "./utils";
9
16
  import { makeStateUpdater } from "../../utils";
10
17
 
18
+ const handleAutoOpenFolder = (
19
+ dataRef: { current: DndDataRef },
20
+ tree: TreeInstance<any>,
21
+ item: ItemInstance<any>,
22
+ placement: TargetPlacement,
23
+ ) => {
24
+ const { openOnDropDelay } = tree.getConfig();
25
+ const dragCode = dataRef.current.lastDragCode;
26
+
27
+ if (
28
+ !openOnDropDelay ||
29
+ !item.isFolder() ||
30
+ item.isExpanded() ||
31
+ placement.type !== PlacementType.MakeChild
32
+ ) {
33
+ return;
34
+ }
35
+ clearTimeout(dataRef.current.autoExpandTimeout);
36
+ dataRef.current.autoExpandTimeout = setTimeout(() => {
37
+ if (
38
+ dragCode !== dataRef.current.lastDragCode ||
39
+ !dataRef.current.lastAllowDrop
40
+ )
41
+ return;
42
+ item.expand();
43
+ }, openOnDropDelay);
44
+ };
45
+
11
46
  const defaultCanDropForeignDragObject = () => false;
12
47
  export const dragAndDropFeature: FeatureImplementation = {
13
48
  key: "drag-and-drop",
14
- deps: ["selection"],
15
49
 
16
50
  getDefaultConfig: (defaultConfig, tree) => ({
17
51
  canDrop: (_, target) => target.item.isFolder(),
@@ -22,6 +56,7 @@ export const dragAndDropFeature: FeatureImplementation = {
22
56
  : () => false,
23
57
  setDndState: makeStateUpdater("dnd", tree),
24
58
  canReorder: true,
59
+ openOnDropDelay: 800,
25
60
  ...defaultConfig,
26
61
  }),
27
62
 
@@ -74,7 +109,7 @@ export const dragAndDropFeature: FeatureImplementation = {
74
109
  }
75
110
  }
76
111
 
77
- const bb = targetItem.getElement()?.getBoundingClientRect();
112
+ const bb = targetItem?.getElement()?.getBoundingClientRect();
78
113
 
79
114
  if (bb) {
80
115
  return {
@@ -149,12 +184,14 @@ export const dragAndDropFeature: FeatureImplementation = {
149
184
  onDragEnter: (e: DragEvent) => e.preventDefault(),
150
185
 
151
186
  onDragStart: (e: DragEvent) => {
152
- const selectedItems = tree.getSelectedItems();
187
+ const selectedItems = tree.getSelectedItems
188
+ ? tree.getSelectedItems()
189
+ : [tree.getFocusedItem()];
153
190
  const items = selectedItems.includes(item) ? selectedItems : [item];
154
191
  const config = tree.getConfig();
155
192
 
156
193
  if (!selectedItems.includes(item)) {
157
- tree.setSelectedItems([item.getItemMeta().itemId]);
194
+ tree.setSelectedItems?.([item.getItemMeta().itemId]);
158
195
  }
159
196
 
160
197
  if (!(config.canDrag?.(items) ?? true)) {
@@ -185,7 +222,9 @@ export const dragAndDropFeature: FeatureImplementation = {
185
222
  onDragOver: (e: DragEvent) => {
186
223
  e.stopPropagation(); // don't bubble up to container dragover
187
224
  const dataRef = tree.getDataRef<DndDataRef>();
188
- const nextDragCode = getDragCode(e, item, tree);
225
+ const placement = getTargetPlacement(e, item, tree, true);
226
+ const nextDragCode = getDragCode(item, placement);
227
+
189
228
  if (nextDragCode === dataRef.current.lastDragCode) {
190
229
  if (dataRef.current.lastAllowDrop) {
191
230
  e.preventDefault();
@@ -195,6 +234,8 @@ export const dragAndDropFeature: FeatureImplementation = {
195
234
  dataRef.current.lastDragCode = nextDragCode;
196
235
  dataRef.current.lastDragEnter = Date.now();
197
236
 
237
+ handleAutoOpenFolder(dataRef, tree, item, placement);
238
+
198
239
  const target = getDragTarget(e, item, tree);
199
240
 
200
241
  if (
@@ -260,14 +301,21 @@ export const dragAndDropFeature: FeatureImplementation = {
260
301
  e.stopPropagation();
261
302
  const dataRef = tree.getDataRef<DndDataRef>();
262
303
  const target = getDragTarget(e, item, tree);
304
+ const draggedItems = tree.getState().dnd?.draggedItems;
305
+ const isValidDrop = canDrop(e.dataTransfer, target, tree);
263
306
 
264
- if (!canDrop(e.dataTransfer, target, tree)) {
307
+ tree.applySubStateUpdate("dnd", {
308
+ draggedItems: undefined,
309
+ draggingOverItem: undefined,
310
+ dragTarget: undefined,
311
+ });
312
+
313
+ if (!isValidDrop) {
265
314
  return;
266
315
  }
267
316
 
268
317
  e.preventDefault();
269
318
  const config = tree.getConfig();
270
- const draggedItems = tree.getState().dnd?.draggedItems;
271
319
 
272
320
  dataRef.current.lastDragCode = undefined;
273
321
 
@@ -4,6 +4,7 @@ export interface DndDataRef {
4
4
  lastDragCode?: string;
5
5
  lastAllowDrop?: boolean;
6
6
  lastDragEnter?: number;
7
+ autoExpandTimeout?: any;
7
8
  windowDragEndListener?: () => void;
8
9
  }
9
10
 
@@ -93,6 +94,9 @@ export type DragAndDropFeatureDef<T> = {
93
94
  target: DragTarget<T>,
94
95
  ) => void | Promise<void>;
95
96
  onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void;
97
+
98
+ /** When dragging for this many ms on a closed folder, the folder will automatically open. Set to zero to disable. */
99
+ openOnDropDelay?: number;
96
100
  };
97
101
  treeInstance: {
98
102
  getDragTarget: () => DragTarget<T> | null;
@@ -7,14 +7,14 @@ export enum ItemDropCategory {
7
7
  LastInGroup,
8
8
  }
9
9
 
10
- enum PlacementType {
10
+ export enum PlacementType {
11
11
  ReorderAbove,
12
12
  ReorderBelow,
13
13
  MakeChild,
14
14
  Reparent,
15
15
  }
16
16
 
17
- type TargetPlacement =
17
+ export type TargetPlacement =
18
18
  | {
19
19
  type:
20
20
  | PlacementType.ReorderAbove
@@ -95,7 +95,7 @@ export const getInsertionIndex = <T>(
95
95
  return childIndex - numberOfDragItemsBeforeTarget;
96
96
  };
97
97
 
98
- const getTargetPlacement = (
98
+ export const getTargetPlacement = (
99
99
  e: any,
100
100
  item: ItemInstance<any>,
101
101
  tree: TreeInstance<any>,
@@ -153,11 +153,9 @@ const getTargetPlacement = (
153
153
  };
154
154
 
155
155
  export const getDragCode = (
156
- e: any,
157
156
  item: ItemInstance<any>,
158
- tree: TreeInstance<any>,
157
+ placement: TargetPlacement,
159
158
  ) => {
160
- const placement = getTargetPlacement(e, item, tree, true);
161
159
  return [
162
160
  item.getId(),
163
161
  placement.type,
@@ -222,6 +220,10 @@ export const getDragTarget = (
222
220
  canBecomeSibling &&
223
221
  placement.type !== PlacementType.MakeChild
224
222
  ) {
223
+ if (draggedItems?.some((item) => item.isDescendentOf(parent.getId()))) {
224
+ // dropping on itself should be illegal, return item, canDrop will then return false
225
+ return itemTarget;
226
+ }
225
227
  return parentTarget;
226
228
  }
227
229
 
@@ -13,6 +13,7 @@ const specialKeys: Record<string, RegExp> = {
13
13
  minus: /^(NumpadSubtract|Minus)$/,
14
14
  control: /^(ControlLeft|ControlRight)$/,
15
15
  shift: /^(ShiftLeft|ShiftRight)$/,
16
+ metaorcontrol: /^(MetaLeft|MetaRight|ControlLeft|ControlRight)$/,
16
17
  };
17
18
 
18
19
  const testHotkeyMatch = (
@@ -191,7 +191,9 @@ export const keyboardDragAndDropFeature: FeatureImplementation = {
191
191
  preventDefault: true,
192
192
  isEnabled: (tree) => !tree.getState().dnd,
193
193
  handler: (_, tree) => {
194
- const selectedItems = tree.getSelectedItems();
194
+ const selectedItems = tree.getSelectedItems?.() ?? [
195
+ tree.getFocusedItem(),
196
+ ];
195
197
  const focusedItem = tree.getFocusedItem();
196
198
 
197
199
  tree.startKeyboardDrag(