@headless-tree/core 0.0.0-20260107220200 → 0.0.0-20260108010329

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,10 +1,12 @@
1
1
  # @headless-tree/core
2
2
 
3
- ## 0.0.0-20260107220200
3
+ ## 0.0.0-20260108010329
4
4
 
5
5
  ### Patch Changes
6
6
 
7
7
  - 39a8b44: Add skipUpdateTree parameter to `updateCachedChildrenIds` and `updateCachedData` in async tree loader
8
+ - 26ecc1b: Fixed an issue where dropping items inside a collapsed folder will make the tree become un-focusable until a new item is clicked with mouse again. This could break usage with keyboard-only drag operations.
9
+ - 0108b7a: Fixed a bug where some hotkeys (like up/down navigation, search and renaming) doesn't work after items are dragged within the tree (#179)
8
10
  - ffd2619: Fixed an issue where `canDropForeignDragObject` was being called in `onDragOver` events without payload. `canDropForeignDragObject` should never be called in `onDragOver` events since there, browsers do not allow access to the data transfer payload. Now, `canDropForeignDragObject` is only called in onDrop events, and `canDragForeignDragObjectOver` is always called in `onDragOver` events.
9
11
 
10
12
  ## 1.6.1
package/dist/index.d.mts CHANGED
@@ -68,6 +68,8 @@ type DragAndDropFeatureDef<T> = {
68
68
  onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void;
69
69
  /** When dragging for this many ms on a closed folder, the folder will automatically open. Set to zero to disable. */
70
70
  openOnDropDelay?: number;
71
+ /** If true, `item.getProps()` will not include drag event handlers. Use `item.getDragHandleProps()` on the handler element. */
72
+ seperateDragHandle?: boolean;
71
73
  };
72
74
  treeInstance: {
73
75
  getDragTarget: () => DragTarget<T> | null;
@@ -84,6 +86,9 @@ type DragAndDropFeatureDef<T> = {
84
86
  isDragTargetAbove: () => boolean;
85
87
  isDragTargetBelow: () => boolean;
86
88
  isDraggingOver: () => boolean;
89
+ /** Note that `item.getProps()` already passes in all drag event handlers by default. Set `seperateDragHandle` to true to
90
+ * disable the default behavior and use this on the handler element instead. */
91
+ getDragHandleProps: () => Record<string, any>;
87
92
  };
88
93
  hotkeys: never;
89
94
  };
@@ -422,6 +427,7 @@ interface PropMemoizationDataRef {
422
427
  memo?: {
423
428
  tree?: Record<string, any>;
424
429
  item?: Record<string, any>;
430
+ drag?: Record<string, any>;
425
431
  search?: Record<string, any>;
426
432
  rename?: Record<string, any>;
427
433
  };
package/dist/index.d.ts CHANGED
@@ -68,6 +68,8 @@ type DragAndDropFeatureDef<T> = {
68
68
  onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void;
69
69
  /** When dragging for this many ms on a closed folder, the folder will automatically open. Set to zero to disable. */
70
70
  openOnDropDelay?: number;
71
+ /** If true, `item.getProps()` will not include drag event handlers. Use `item.getDragHandleProps()` on the handler element. */
72
+ seperateDragHandle?: boolean;
71
73
  };
72
74
  treeInstance: {
73
75
  getDragTarget: () => DragTarget<T> | null;
@@ -84,6 +86,9 @@ type DragAndDropFeatureDef<T> = {
84
86
  isDragTargetAbove: () => boolean;
85
87
  isDragTargetBelow: () => boolean;
86
88
  isDraggingOver: () => boolean;
89
+ /** Note that `item.getProps()` already passes in all drag event handlers by default. Set `seperateDragHandle` to true to
90
+ * disable the default behavior and use this on the handler element instead. */
91
+ getDragHandleProps: () => Record<string, any>;
87
92
  };
88
93
  hotkeys: never;
89
94
  };
@@ -422,6 +427,7 @@ interface PropMemoizationDataRef {
422
427
  memo?: {
423
428
  tree?: Record<string, any>;
424
429
  item?: Record<string, any>;
430
+ drag?: Record<string, any>;
425
431
  search?: Record<string, any>;
426
432
  rename?: Record<string, any>;
427
433
  };
package/dist/index.js CHANGED
@@ -125,6 +125,7 @@ var poll = (fn, interval = 100, timeout = 1e3) => new Promise((resolve) => {
125
125
  }, interval);
126
126
  clear = setTimeout(() => {
127
127
  clearInterval(i);
128
+ resolve();
128
129
  }, timeout);
129
130
  });
130
131
 
@@ -214,12 +215,16 @@ var treeFeature = {
214
215
  },
215
216
  updateDomFocus: ({ tree }) => {
216
217
  setTimeout(() => __async(null, null, function* () {
217
- var _a, _b;
218
+ var _a, _b, _c, _d, _e;
218
219
  const focusedItem = tree.getFocusedItem();
219
220
  (_b = (_a = tree.getConfig()).scrollToItem) == null ? void 0 : _b.call(_a, focusedItem);
220
- yield poll(() => focusedItem.getElement() !== null, 20);
221
+ yield poll(() => focusedItem.getElement() !== null, 20, 500);
221
222
  const focusedElement = focusedItem.getElement();
222
- if (!focusedElement) return;
223
+ if (!focusedElement) {
224
+ (_c = tree.getItems()[0]) == null ? void 0 : _c.setFocused();
225
+ (_e = (_d = tree.getItems()[0]) == null ? void 0 : _d.getElement()) == null ? void 0 : _e.focus();
226
+ return;
227
+ }
223
228
  focusedElement.focus();
224
229
  }));
225
230
  },
@@ -1615,36 +1620,8 @@ var dragAndDropFeature = {
1615
1620
  }
1616
1621
  },
1617
1622
  itemInstance: {
1618
- getProps: ({ tree, item, prev }) => __spreadProps(__spreadValues({}, prev == null ? void 0 : prev()), {
1619
- draggable: true,
1623
+ getProps: ({ tree, item, prev }) => __spreadProps(__spreadValues(__spreadValues({}, prev == null ? void 0 : prev()), tree.getConfig().seperateDragHandle ? {} : item.getDragHandleProps()), {
1620
1624
  onDragEnter: (e) => e.preventDefault(),
1621
- onDragStart: (e) => {
1622
- var _a, _b, _c, _d;
1623
- const selectedItems = tree.getSelectedItems ? tree.getSelectedItems() : [tree.getFocusedItem()];
1624
- const items = selectedItems.includes(item) ? selectedItems : [item];
1625
- const config = tree.getConfig();
1626
- if (!selectedItems.includes(item)) {
1627
- (_a = tree.setSelectedItems) == null ? void 0 : _a.call(tree, [item.getItemMeta().itemId]);
1628
- }
1629
- if (!((_c = (_b = config.canDrag) == null ? void 0 : _b.call(config, items)) != null ? _c : true)) {
1630
- e.preventDefault();
1631
- return;
1632
- }
1633
- if (config.setDragImage) {
1634
- const { imgElement, xOffset, yOffset } = config.setDragImage(items);
1635
- (_d = e.dataTransfer) == null ? void 0 : _d.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
1636
- }
1637
- if (config.createForeignDragObject && e.dataTransfer) {
1638
- const { format, data, dropEffect, effectAllowed } = config.createForeignDragObject(items);
1639
- e.dataTransfer.setData(format, data);
1640
- if (dropEffect) e.dataTransfer.dropEffect = dropEffect;
1641
- if (effectAllowed) e.dataTransfer.effectAllowed = effectAllowed;
1642
- }
1643
- tree.applySubStateUpdate("dnd", {
1644
- draggedItems: items,
1645
- draggingOverItem: tree.getFocusedItem()
1646
- });
1647
- },
1648
1625
  onDragOver: (e) => {
1649
1626
  var _a, _b, _c;
1650
1627
  e.stopPropagation();
@@ -1688,19 +1665,6 @@ var dragAndDropFeature = {
1688
1665
  }));
1689
1666
  }, 100);
1690
1667
  },
1691
- onDragEnd: (e) => {
1692
- var _a, _b;
1693
- const { onCompleteForeignDrop, canDragForeignDragObjectOver } = tree.getConfig();
1694
- const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
1695
- if (((_b = e.dataTransfer) == null ? void 0 : _b.dropEffect) === "none" || !draggedItems) {
1696
- return;
1697
- }
1698
- const target = getDragTarget(e, item, tree, false);
1699
- if (canDragForeignDragObjectOver && e.dataTransfer && !canDragForeignDragObjectOver(e.dataTransfer, target)) {
1700
- return;
1701
- }
1702
- onCompleteForeignDrop == null ? void 0 : onCompleteForeignDrop(draggedItems);
1703
- },
1704
1668
  onDrop: (e) => __async(null, null, function* () {
1705
1669
  var _a, _b, _c;
1706
1670
  e.stopPropagation();
@@ -1721,11 +1685,57 @@ var dragAndDropFeature = {
1721
1685
  dataRef.current.lastDragCode = void 0;
1722
1686
  if (draggedItems) {
1723
1687
  yield (_b = config.onDrop) == null ? void 0 : _b.call(config, draggedItems, target);
1688
+ draggedItems[0].setFocused();
1724
1689
  } else if (e.dataTransfer) {
1725
1690
  yield (_c = config.onDropForeignDragObject) == null ? void 0 : _c.call(config, e.dataTransfer, target);
1726
1691
  }
1692
+ tree.applySubStateUpdate("dnd", null);
1693
+ tree.updateDomFocus();
1727
1694
  })
1728
1695
  }),
1696
+ getDragHandleProps: ({ tree, item, prev }) => __spreadProps(__spreadValues({}, prev == null ? void 0 : prev()), {
1697
+ draggable: true,
1698
+ onDragStart: (e) => {
1699
+ var _a, _b, _c, _d;
1700
+ const selectedItems = tree.getSelectedItems ? tree.getSelectedItems() : [tree.getFocusedItem()];
1701
+ const items = selectedItems.includes(item) ? selectedItems : [item];
1702
+ const config = tree.getConfig();
1703
+ if (!selectedItems.includes(item)) {
1704
+ (_a = tree.setSelectedItems) == null ? void 0 : _a.call(tree, [item.getItemMeta().itemId]);
1705
+ }
1706
+ if (!((_c = (_b = config.canDrag) == null ? void 0 : _b.call(config, items)) != null ? _c : true)) {
1707
+ e.preventDefault();
1708
+ return;
1709
+ }
1710
+ if (config.setDragImage) {
1711
+ const { imgElement, xOffset, yOffset } = config.setDragImage(items);
1712
+ (_d = e.dataTransfer) == null ? void 0 : _d.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
1713
+ }
1714
+ if (config.createForeignDragObject && e.dataTransfer) {
1715
+ const { format, data, dropEffect, effectAllowed } = config.createForeignDragObject(items);
1716
+ e.dataTransfer.setData(format, data);
1717
+ if (dropEffect) e.dataTransfer.dropEffect = dropEffect;
1718
+ if (effectAllowed) e.dataTransfer.effectAllowed = effectAllowed;
1719
+ }
1720
+ tree.applySubStateUpdate("dnd", {
1721
+ draggedItems: items,
1722
+ draggingOverItem: tree.getFocusedItem()
1723
+ });
1724
+ },
1725
+ onDragEnd: (e) => {
1726
+ var _a, _b;
1727
+ const { onCompleteForeignDrop, canDragForeignDragObjectOver } = tree.getConfig();
1728
+ const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
1729
+ if (((_b = e.dataTransfer) == null ? void 0 : _b.dropEffect) === "none" || !draggedItems) {
1730
+ return;
1731
+ }
1732
+ const target = getDragTarget(e, item, tree, false);
1733
+ if (canDragForeignDragObjectOver && e.dataTransfer && !canDragForeignDragObjectOver(e.dataTransfer, target)) {
1734
+ return;
1735
+ }
1736
+ onCompleteForeignDrop == null ? void 0 : onCompleteForeignDrop(draggedItems);
1737
+ }
1738
+ }),
1729
1739
  isDragTarget: ({ tree, item }) => {
1730
1740
  const target = tree.getDragTarget();
1731
1741
  return target ? target.item.getId() === item.getId() : false;
@@ -1949,6 +1959,7 @@ var keyboardDragAndDropFeature = {
1949
1959
  } else if (dataTransfer) {
1950
1960
  yield (_d = config.onDropForeignDragObject) == null ? void 0 : _d.call(config, dataTransfer, target);
1951
1961
  }
1962
+ tree.updateDomFocus();
1952
1963
  tree.applySubStateUpdate(
1953
1964
  "assistiveDndState",
1954
1965
  3 /* Completed */
@@ -2299,6 +2310,14 @@ var propMemoizationFeature = {
2299
2310
  (_e = (_d = dataRef.current.memo).item) != null ? _e : _d.item = {};
2300
2311
  return memoize(props, dataRef.current.memo.item);
2301
2312
  },
2313
+ getDragHandleProps: ({ item, prev }) => {
2314
+ var _a, _b, _c, _d, _e;
2315
+ const dataRef = item.getDataRef();
2316
+ const props = (_a = prev == null ? void 0 : prev()) != null ? _a : {};
2317
+ (_c = (_b = dataRef.current).memo) != null ? _c : _b.memo = {};
2318
+ (_e = (_d = dataRef.current.memo).drag) != null ? _e : _d.drag = {};
2319
+ return memoize(props, dataRef.current.memo.drag);
2320
+ },
2302
2321
  getRenameInputProps: ({ item, prev }) => {
2303
2322
  var _a, _b, _c, _d, _e;
2304
2323
  const dataRef = item.getDataRef();
package/dist/index.mjs CHANGED
@@ -81,6 +81,7 @@ var poll = (fn, interval = 100, timeout = 1e3) => new Promise((resolve) => {
81
81
  }, interval);
82
82
  clear = setTimeout(() => {
83
83
  clearInterval(i);
84
+ resolve();
84
85
  }, timeout);
85
86
  });
86
87
 
@@ -170,12 +171,16 @@ var treeFeature = {
170
171
  },
171
172
  updateDomFocus: ({ tree }) => {
172
173
  setTimeout(() => __async(null, null, function* () {
173
- var _a, _b;
174
+ var _a, _b, _c, _d, _e;
174
175
  const focusedItem = tree.getFocusedItem();
175
176
  (_b = (_a = tree.getConfig()).scrollToItem) == null ? void 0 : _b.call(_a, focusedItem);
176
- yield poll(() => focusedItem.getElement() !== null, 20);
177
+ yield poll(() => focusedItem.getElement() !== null, 20, 500);
177
178
  const focusedElement = focusedItem.getElement();
178
- if (!focusedElement) return;
179
+ if (!focusedElement) {
180
+ (_c = tree.getItems()[0]) == null ? void 0 : _c.setFocused();
181
+ (_e = (_d = tree.getItems()[0]) == null ? void 0 : _d.getElement()) == null ? void 0 : _e.focus();
182
+ return;
183
+ }
179
184
  focusedElement.focus();
180
185
  }));
181
186
  },
@@ -1571,36 +1576,8 @@ var dragAndDropFeature = {
1571
1576
  }
1572
1577
  },
1573
1578
  itemInstance: {
1574
- getProps: ({ tree, item, prev }) => __spreadProps(__spreadValues({}, prev == null ? void 0 : prev()), {
1575
- draggable: true,
1579
+ getProps: ({ tree, item, prev }) => __spreadProps(__spreadValues(__spreadValues({}, prev == null ? void 0 : prev()), tree.getConfig().seperateDragHandle ? {} : item.getDragHandleProps()), {
1576
1580
  onDragEnter: (e) => e.preventDefault(),
1577
- onDragStart: (e) => {
1578
- var _a, _b, _c, _d;
1579
- const selectedItems = tree.getSelectedItems ? tree.getSelectedItems() : [tree.getFocusedItem()];
1580
- const items = selectedItems.includes(item) ? selectedItems : [item];
1581
- const config = tree.getConfig();
1582
- if (!selectedItems.includes(item)) {
1583
- (_a = tree.setSelectedItems) == null ? void 0 : _a.call(tree, [item.getItemMeta().itemId]);
1584
- }
1585
- if (!((_c = (_b = config.canDrag) == null ? void 0 : _b.call(config, items)) != null ? _c : true)) {
1586
- e.preventDefault();
1587
- return;
1588
- }
1589
- if (config.setDragImage) {
1590
- const { imgElement, xOffset, yOffset } = config.setDragImage(items);
1591
- (_d = e.dataTransfer) == null ? void 0 : _d.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
1592
- }
1593
- if (config.createForeignDragObject && e.dataTransfer) {
1594
- const { format, data, dropEffect, effectAllowed } = config.createForeignDragObject(items);
1595
- e.dataTransfer.setData(format, data);
1596
- if (dropEffect) e.dataTransfer.dropEffect = dropEffect;
1597
- if (effectAllowed) e.dataTransfer.effectAllowed = effectAllowed;
1598
- }
1599
- tree.applySubStateUpdate("dnd", {
1600
- draggedItems: items,
1601
- draggingOverItem: tree.getFocusedItem()
1602
- });
1603
- },
1604
1581
  onDragOver: (e) => {
1605
1582
  var _a, _b, _c;
1606
1583
  e.stopPropagation();
@@ -1644,19 +1621,6 @@ var dragAndDropFeature = {
1644
1621
  }));
1645
1622
  }, 100);
1646
1623
  },
1647
- onDragEnd: (e) => {
1648
- var _a, _b;
1649
- const { onCompleteForeignDrop, canDragForeignDragObjectOver } = tree.getConfig();
1650
- const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
1651
- if (((_b = e.dataTransfer) == null ? void 0 : _b.dropEffect) === "none" || !draggedItems) {
1652
- return;
1653
- }
1654
- const target = getDragTarget(e, item, tree, false);
1655
- if (canDragForeignDragObjectOver && e.dataTransfer && !canDragForeignDragObjectOver(e.dataTransfer, target)) {
1656
- return;
1657
- }
1658
- onCompleteForeignDrop == null ? void 0 : onCompleteForeignDrop(draggedItems);
1659
- },
1660
1624
  onDrop: (e) => __async(null, null, function* () {
1661
1625
  var _a, _b, _c;
1662
1626
  e.stopPropagation();
@@ -1677,11 +1641,57 @@ var dragAndDropFeature = {
1677
1641
  dataRef.current.lastDragCode = void 0;
1678
1642
  if (draggedItems) {
1679
1643
  yield (_b = config.onDrop) == null ? void 0 : _b.call(config, draggedItems, target);
1644
+ draggedItems[0].setFocused();
1680
1645
  } else if (e.dataTransfer) {
1681
1646
  yield (_c = config.onDropForeignDragObject) == null ? void 0 : _c.call(config, e.dataTransfer, target);
1682
1647
  }
1648
+ tree.applySubStateUpdate("dnd", null);
1649
+ tree.updateDomFocus();
1683
1650
  })
1684
1651
  }),
1652
+ getDragHandleProps: ({ tree, item, prev }) => __spreadProps(__spreadValues({}, prev == null ? void 0 : prev()), {
1653
+ draggable: true,
1654
+ onDragStart: (e) => {
1655
+ var _a, _b, _c, _d;
1656
+ const selectedItems = tree.getSelectedItems ? tree.getSelectedItems() : [tree.getFocusedItem()];
1657
+ const items = selectedItems.includes(item) ? selectedItems : [item];
1658
+ const config = tree.getConfig();
1659
+ if (!selectedItems.includes(item)) {
1660
+ (_a = tree.setSelectedItems) == null ? void 0 : _a.call(tree, [item.getItemMeta().itemId]);
1661
+ }
1662
+ if (!((_c = (_b = config.canDrag) == null ? void 0 : _b.call(config, items)) != null ? _c : true)) {
1663
+ e.preventDefault();
1664
+ return;
1665
+ }
1666
+ if (config.setDragImage) {
1667
+ const { imgElement, xOffset, yOffset } = config.setDragImage(items);
1668
+ (_d = e.dataTransfer) == null ? void 0 : _d.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
1669
+ }
1670
+ if (config.createForeignDragObject && e.dataTransfer) {
1671
+ const { format, data, dropEffect, effectAllowed } = config.createForeignDragObject(items);
1672
+ e.dataTransfer.setData(format, data);
1673
+ if (dropEffect) e.dataTransfer.dropEffect = dropEffect;
1674
+ if (effectAllowed) e.dataTransfer.effectAllowed = effectAllowed;
1675
+ }
1676
+ tree.applySubStateUpdate("dnd", {
1677
+ draggedItems: items,
1678
+ draggingOverItem: tree.getFocusedItem()
1679
+ });
1680
+ },
1681
+ onDragEnd: (e) => {
1682
+ var _a, _b;
1683
+ const { onCompleteForeignDrop, canDragForeignDragObjectOver } = tree.getConfig();
1684
+ const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
1685
+ if (((_b = e.dataTransfer) == null ? void 0 : _b.dropEffect) === "none" || !draggedItems) {
1686
+ return;
1687
+ }
1688
+ const target = getDragTarget(e, item, tree, false);
1689
+ if (canDragForeignDragObjectOver && e.dataTransfer && !canDragForeignDragObjectOver(e.dataTransfer, target)) {
1690
+ return;
1691
+ }
1692
+ onCompleteForeignDrop == null ? void 0 : onCompleteForeignDrop(draggedItems);
1693
+ }
1694
+ }),
1685
1695
  isDragTarget: ({ tree, item }) => {
1686
1696
  const target = tree.getDragTarget();
1687
1697
  return target ? target.item.getId() === item.getId() : false;
@@ -1905,6 +1915,7 @@ var keyboardDragAndDropFeature = {
1905
1915
  } else if (dataTransfer) {
1906
1916
  yield (_d = config.onDropForeignDragObject) == null ? void 0 : _d.call(config, dataTransfer, target);
1907
1917
  }
1918
+ tree.updateDomFocus();
1908
1919
  tree.applySubStateUpdate(
1909
1920
  "assistiveDndState",
1910
1921
  3 /* Completed */
@@ -2255,6 +2266,14 @@ var propMemoizationFeature = {
2255
2266
  (_e = (_d = dataRef.current.memo).item) != null ? _e : _d.item = {};
2256
2267
  return memoize(props, dataRef.current.memo.item);
2257
2268
  },
2269
+ getDragHandleProps: ({ item, prev }) => {
2270
+ var _a, _b, _c, _d, _e;
2271
+ const dataRef = item.getDataRef();
2272
+ const props = (_a = prev == null ? void 0 : prev()) != null ? _a : {};
2273
+ (_c = (_b = dataRef.current).memo) != null ? _c : _b.memo = {};
2274
+ (_e = (_d = dataRef.current.memo).drag) != null ? _e : _d.drag = {};
2275
+ return memoize(props, dataRef.current.memo.drag);
2276
+ },
2258
2277
  getRenameInputProps: ({ item, prev }) => {
2259
2278
  var _a, _b, _c, _d, _e;
2260
2279
  const dataRef = item.getDataRef();
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "checkbox",
14
14
  "hook"
15
15
  ],
16
- "version": "0.0.0-20260107220200",
16
+ "version": "0.0.0-20260108010329",
17
17
  "main": "dist/index.d.ts",
18
18
  "module": "dist/index.mjs",
19
19
  "types": "dist/index.d.mts",
@@ -729,6 +729,54 @@ describe("core-feature/drag-and-drop", () => {
729
729
  });
730
730
  });
731
731
 
732
+ describe("seperateDragHandle", () => {
733
+ it("includes all drag handlers in getProps() when seperateDragHandle is false", () => {
734
+ const props = tree.instance.getItemInstance("x111").getProps();
735
+ expect(props.draggable).toBe(true);
736
+ expect(props.onDragStart).toBeDefined();
737
+ expect(props.onDragEnd).toBeDefined();
738
+ expect(props.onDragEnter).toBeDefined();
739
+ expect(props.onDragOver).toBeDefined();
740
+ expect(props.onDragLeave).toBeDefined();
741
+ expect(props.onDrop).toBeDefined();
742
+ });
743
+
744
+ it("includes drag handle handlers in getDragHandleProps() when seperateDragHandle is false", () => {
745
+ const props = tree.instance
746
+ .getItemInstance("x111")
747
+ .getDragHandleProps();
748
+ expect(props.draggable).toBe(true);
749
+ expect(props.onDragStart).toBeDefined();
750
+ expect(props.onDragEnd).toBeDefined();
751
+ });
752
+
753
+ it("excludes drag handle handlers from getProps() but includes drop handlers when seperateDragHandle is true", async () => {
754
+ const testTree = await tree
755
+ .with({ seperateDragHandle: true })
756
+ .createTestCaseTree();
757
+ const props = testTree.instance.getItemInstance("x111").getProps();
758
+ expect(props.draggable).toBeUndefined();
759
+ expect(props.onDragStart).toBeUndefined();
760
+ expect(props.onDragEnd).toBeUndefined();
761
+ expect(props.onDragEnter).toBeDefined();
762
+ expect(props.onDragOver).toBeDefined();
763
+ expect(props.onDragLeave).toBeDefined();
764
+ expect(props.onDrop).toBeDefined();
765
+ });
766
+
767
+ it("includes drag handle handlers in getDragHandleProps() when seperateDragHandle is true", async () => {
768
+ const testTree = await tree
769
+ .with({ seperateDragHandle: true })
770
+ .createTestCaseTree();
771
+ const props = testTree.instance
772
+ .getItemInstance("x111")
773
+ .getDragHandleProps();
774
+ expect(props.draggable).toBe(true);
775
+ expect(props.onDragStart).toBeDefined();
776
+ expect(props.onDragEnd).toBeDefined();
777
+ });
778
+ });
779
+
732
780
  describe("retains last drag state with dragcode", () => {
733
781
  it("uses constant number of calls to canDrop", () => {
734
782
  const canDrop = tree.mockedHandler("canDrop").mockReturnValue(true);
@@ -178,47 +178,10 @@ export const dragAndDropFeature: FeatureImplementation = {
178
178
  itemInstance: {
179
179
  getProps: ({ tree, item, prev }) => ({
180
180
  ...prev?.(),
181
-
182
- draggable: true,
181
+ ...(tree.getConfig().seperateDragHandle ? {} : item.getDragHandleProps()),
183
182
 
184
183
  onDragEnter: (e: DragEvent) => e.preventDefault(),
185
184
 
186
- onDragStart: (e: DragEvent) => {
187
- const selectedItems = tree.getSelectedItems
188
- ? tree.getSelectedItems()
189
- : [tree.getFocusedItem()];
190
- const items = selectedItems.includes(item) ? selectedItems : [item];
191
- const config = tree.getConfig();
192
-
193
- if (!selectedItems.includes(item)) {
194
- tree.setSelectedItems?.([item.getItemMeta().itemId]);
195
- }
196
-
197
- if (!(config.canDrag?.(items) ?? true)) {
198
- e.preventDefault();
199
- return;
200
- }
201
-
202
- if (config.setDragImage) {
203
- const { imgElement, xOffset, yOffset } = config.setDragImage(items);
204
- e.dataTransfer?.setDragImage(imgElement, xOffset ?? 0, yOffset ?? 0);
205
- }
206
-
207
- if (config.createForeignDragObject && e.dataTransfer) {
208
- const { format, data, dropEffect, effectAllowed } =
209
- config.createForeignDragObject(items);
210
- e.dataTransfer.setData(format, data);
211
-
212
- if (dropEffect) e.dataTransfer.dropEffect = dropEffect;
213
- if (effectAllowed) e.dataTransfer.effectAllowed = effectAllowed;
214
- }
215
-
216
- tree.applySubStateUpdate("dnd", {
217
- draggedItems: items,
218
- draggingOverItem: tree.getFocusedItem(),
219
- });
220
- },
221
-
222
185
  onDragOver: (e: DragEvent) => {
223
186
  e.stopPropagation(); // don't bubble up to container dragover
224
187
  const dataRef = tree.getDataRef<DndDataRef>();
@@ -277,27 +240,6 @@ export const dragAndDropFeature: FeatureImplementation = {
277
240
  }, 100);
278
241
  },
279
242
 
280
- onDragEnd: (e: DragEvent) => {
281
- const { onCompleteForeignDrop, canDragForeignDragObjectOver } =
282
- tree.getConfig();
283
- const draggedItems = tree.getState().dnd?.draggedItems;
284
-
285
- if (e.dataTransfer?.dropEffect === "none" || !draggedItems) {
286
- return;
287
- }
288
-
289
- const target = getDragTarget(e, item, tree, false);
290
- if (
291
- canDragForeignDragObjectOver &&
292
- e.dataTransfer &&
293
- !canDragForeignDragObjectOver(e.dataTransfer, target)
294
- ) {
295
- return;
296
- }
297
-
298
- onCompleteForeignDrop?.(draggedItems);
299
- },
300
-
301
243
  onDrop: async (e: DragEvent) => {
302
244
  e.stopPropagation();
303
245
  const dataRef = tree.getDataRef<DndDataRef>();
@@ -322,9 +264,76 @@ export const dragAndDropFeature: FeatureImplementation = {
322
264
 
323
265
  if (draggedItems) {
324
266
  await config.onDrop?.(draggedItems, target);
267
+ draggedItems[0].setFocused();
325
268
  } else if (e.dataTransfer) {
326
269
  await config.onDropForeignDragObject?.(e.dataTransfer, target);
327
270
  }
271
+
272
+ tree.applySubStateUpdate("dnd", null);
273
+ tree.updateDomFocus();
274
+ },
275
+ }),
276
+
277
+ getDragHandleProps: ({ tree, item, prev }) => ({
278
+ ...prev?.(),
279
+
280
+ draggable: true,
281
+
282
+ onDragStart: (e: DragEvent) => {
283
+ const selectedItems = tree.getSelectedItems
284
+ ? tree.getSelectedItems()
285
+ : [tree.getFocusedItem()];
286
+ const items = selectedItems.includes(item) ? selectedItems : [item];
287
+ const config = tree.getConfig();
288
+
289
+ if (!selectedItems.includes(item)) {
290
+ tree.setSelectedItems?.([item.getItemMeta().itemId]);
291
+ }
292
+
293
+ if (!(config.canDrag?.(items) ?? true)) {
294
+ e.preventDefault();
295
+ return;
296
+ }
297
+
298
+ if (config.setDragImage) {
299
+ const { imgElement, xOffset, yOffset } = config.setDragImage(items);
300
+ e.dataTransfer?.setDragImage(imgElement, xOffset ?? 0, yOffset ?? 0);
301
+ }
302
+
303
+ if (config.createForeignDragObject && e.dataTransfer) {
304
+ const { format, data, dropEffect, effectAllowed } =
305
+ config.createForeignDragObject(items);
306
+ e.dataTransfer.setData(format, data);
307
+
308
+ if (dropEffect) e.dataTransfer.dropEffect = dropEffect;
309
+ if (effectAllowed) e.dataTransfer.effectAllowed = effectAllowed;
310
+ }
311
+
312
+ tree.applySubStateUpdate("dnd", {
313
+ draggedItems: items,
314
+ draggingOverItem: tree.getFocusedItem(),
315
+ });
316
+ },
317
+
318
+ onDragEnd: (e: DragEvent) => {
319
+ const { onCompleteForeignDrop, canDragForeignDragObjectOver } =
320
+ tree.getConfig();
321
+ const draggedItems = tree.getState().dnd?.draggedItems;
322
+
323
+ if (e.dataTransfer?.dropEffect === "none" || !draggedItems) {
324
+ return;
325
+ }
326
+
327
+ const target = getDragTarget(e, item, tree, false);
328
+ if (
329
+ canDragForeignDragObjectOver &&
330
+ e.dataTransfer &&
331
+ !canDragForeignDragObjectOver(e.dataTransfer, target)
332
+ ) {
333
+ return;
334
+ }
335
+
336
+ onCompleteForeignDrop?.(draggedItems);
328
337
  },
329
338
  }),
330
339
 
@@ -97,6 +97,9 @@ export type DragAndDropFeatureDef<T> = {
97
97
 
98
98
  /** When dragging for this many ms on a closed folder, the folder will automatically open. Set to zero to disable. */
99
99
  openOnDropDelay?: number;
100
+
101
+ /** If true, `item.getProps()` will not include drag event handlers. Use `item.getDragHandleProps()` on the handler element. */
102
+ seperateDragHandle?: boolean;
100
103
  };
101
104
  treeInstance: {
102
105
  getDragTarget: () => DragTarget<T> | null;
@@ -118,6 +121,10 @@ export type DragAndDropFeatureDef<T> = {
118
121
  isDragTargetAbove: () => boolean;
119
122
  isDragTargetBelow: () => boolean;
120
123
  isDraggingOver: () => boolean;
124
+
125
+ /** Note that `item.getProps()` already passes in all drag event handlers by default. Set `seperateDragHandle` to true to
126
+ * disable the default behavior and use this on the handler element instead. */
127
+ getDragHandleProps: () => Record<string, any>;
121
128
  };
122
129
  hotkeys: never;
123
130
  };
@@ -254,6 +254,7 @@ export const keyboardDragAndDropFeature: FeatureImplementation = {
254
254
  await config.onDropForeignDragObject?.(dataTransfer, target);
255
255
  }
256
256
 
257
+ tree.updateDomFocus();
257
258
  tree.applySubStateUpdate(
258
259
  "assistiveDndState",
259
260
  AssistiveDndState.Completed,
@@ -59,6 +59,14 @@ export const propMemoizationFeature: FeatureImplementation = {
59
59
  return memoize(props, dataRef.current.memo.item);
60
60
  },
61
61
 
62
+ getDragHandleProps: ({ item, prev }) => {
63
+ const dataRef = item.getDataRef<PropMemoizationDataRef>();
64
+ const props = prev?.() ?? {};
65
+ dataRef.current.memo ??= {};
66
+ dataRef.current.memo.drag ??= {};
67
+ return memoize(props, dataRef.current.memo.drag);
68
+ },
69
+
62
70
  getRenameInputProps: ({ item, prev }) => {
63
71
  const dataRef = item.getDataRef<PropMemoizationDataRef>();
64
72
  const props = prev?.() ?? {};
@@ -2,6 +2,7 @@ export interface PropMemoizationDataRef {
2
2
  memo?: {
3
3
  tree?: Record<string, any>;
4
4
  item?: Record<string, any>;
5
+ drag?: Record<string, any>;
5
6
  search?: Record<string, any>;
6
7
  rename?: Record<string, any>;
7
8
  };
@@ -107,9 +107,13 @@ export const treeFeature: FeatureImplementation<any> = {
107
107
  setTimeout(async () => {
108
108
  const focusedItem = tree.getFocusedItem();
109
109
  tree.getConfig().scrollToItem?.(focusedItem);
110
- await poll(() => focusedItem.getElement() !== null, 20);
110
+ await poll(() => focusedItem.getElement() !== null, 20, 500);
111
111
  const focusedElement = focusedItem.getElement();
112
- if (!focusedElement) return;
112
+ if (!focusedElement) {
113
+ tree.getItems()[0]?.setFocused();
114
+ tree.getItems()[0]?.getElement()?.focus();
115
+ return;
116
+ }
113
117
  focusedElement.focus();
114
118
  });
115
119
  },
package/src/utils.ts CHANGED
@@ -64,5 +64,6 @@ export const poll = (fn: () => boolean, interval = 100, timeout = 1000) =>
64
64
  }, interval);
65
65
  clear = setTimeout(() => {
66
66
  clearInterval(i);
67
+ resolve();
67
68
  }, timeout);
68
69
  });