@headless-tree/core 0.0.0-20250730213235 → 0.0.0-20250731075124

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,10 @@
1
1
  # @headless-tree/core
2
2
 
3
- ## 0.0.0-20250730213235
3
+ ## 0.0.0-20250731075124
4
+
5
+ ### Minor Changes
6
+
7
+ - 21d1679: add `canDragForeignDragObjectOver` to allow customizing whether a draggable visualization should be shown when dragging foreign data. This allows differentiating logic between drag-over and drop (via the existing `canDropForeignDataObject`), since for the latter `dataTransfer.getData` is not available by default in browsers.
4
8
 
5
9
  ### Patch Changes
6
10
 
package/dist/index.d.mts CHANGED
@@ -45,13 +45,22 @@ type DragAndDropFeatureDef<T> = {
45
45
  createForeignDragObject?: (items: ItemInstance<T>[]) => {
46
46
  format: string;
47
47
  data: any;
48
+ dropEffect?: DataTransfer["dropEffect"];
49
+ effectAllowed?: DataTransfer["effectAllowed"];
48
50
  };
49
51
  setDragImage?: (items: ItemInstance<T>[]) => {
50
52
  imgElement: Element;
51
53
  xOffset?: number;
52
54
  yOffset?: number;
53
55
  };
56
+ /** Checks if a foreign drag object can be dropped on a target, validating that an actual drop can commence based on
57
+ * the data in the DataTransfer object. */
54
58
  canDropForeignDragObject?: (dataTransfer: DataTransfer, target: DragTarget<T>) => boolean;
59
+ /** Checks if a droppable visualization should be displayed when dragging a foreign object over a target. Since this
60
+ * is executed on a dragover event, `dataTransfer.getData()` is not available, so `dataTransfer.effectAllowed` or
61
+ * `dataTransfer.types` should be used instead. Before actually completing the drag, @{link canDropForeignDragObject}
62
+ * will be called by HT before applying the drop. */
63
+ canDragForeignDragObjectOver?: (dataTransfer: DataTransfer, target: DragTarget<T>) => boolean;
55
64
  onDrop?: (items: ItemInstance<T>[], target: DragTarget<T>) => void | Promise<void>;
56
65
  onDropForeignDragObject?: (dataTransfer: DataTransfer, target: DragTarget<T>) => void | Promise<void>;
57
66
  onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void;
package/dist/index.d.ts CHANGED
@@ -45,13 +45,22 @@ type DragAndDropFeatureDef<T> = {
45
45
  createForeignDragObject?: (items: ItemInstance<T>[]) => {
46
46
  format: string;
47
47
  data: any;
48
+ dropEffect?: DataTransfer["dropEffect"];
49
+ effectAllowed?: DataTransfer["effectAllowed"];
48
50
  };
49
51
  setDragImage?: (items: ItemInstance<T>[]) => {
50
52
  imgElement: Element;
51
53
  xOffset?: number;
52
54
  yOffset?: number;
53
55
  };
56
+ /** Checks if a foreign drag object can be dropped on a target, validating that an actual drop can commence based on
57
+ * the data in the DataTransfer object. */
54
58
  canDropForeignDragObject?: (dataTransfer: DataTransfer, target: DragTarget<T>) => boolean;
59
+ /** Checks if a droppable visualization should be displayed when dragging a foreign object over a target. Since this
60
+ * is executed on a dragover event, `dataTransfer.getData()` is not available, so `dataTransfer.effectAllowed` or
61
+ * `dataTransfer.types` should be used instead. Before actually completing the drag, @{link canDropForeignDragObject}
62
+ * will be called by HT before applying the drop. */
63
+ canDragForeignDragObjectOver?: (dataTransfer: DataTransfer, target: DragTarget<T>) => boolean;
55
64
  onDrop?: (items: ItemInstance<T>[], target: DragTarget<T>) => void | Promise<void>;
56
65
  onDropForeignDragObject?: (dataTransfer: DataTransfer, target: DragTarget<T>) => void | Promise<void>;
57
66
  onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void;
package/dist/index.js CHANGED
@@ -1372,12 +1372,14 @@ var getDragTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) =>
1372
1372
  };
1373
1373
 
1374
1374
  // src/features/drag-and-drop/feature.ts
1375
+ var defaultCanDropForeignDragObject = () => false;
1375
1376
  var dragAndDropFeature = {
1376
1377
  key: "drag-and-drop",
1377
1378
  deps: ["selection"],
1378
1379
  getDefaultConfig: (defaultConfig, tree) => __spreadValues({
1379
1380
  canDrop: (_, target) => target.item.isFolder(),
1380
- canDropForeignDragObject: () => false,
1381
+ canDropForeignDragObject: defaultCanDropForeignDragObject,
1382
+ canDragForeignDragObjectOver: defaultConfig.canDropForeignDragObject !== defaultCanDropForeignDragObject ? (dataTransfer) => dataTransfer.effectAllowed !== "none" : () => false,
1381
1383
  setDndState: makeStateUpdater("dnd", tree),
1382
1384
  canReorder: true
1383
1385
  }, defaultConfig),
@@ -1476,7 +1478,7 @@ var dragAndDropFeature = {
1476
1478
  draggable: true,
1477
1479
  onDragEnter: (e) => e.preventDefault(),
1478
1480
  onDragStart: (e) => {
1479
- var _a, _b, _c, _d;
1481
+ var _a, _b, _c;
1480
1482
  const selectedItems = tree.getSelectedItems();
1481
1483
  const items = selectedItems.includes(item) ? selectedItems : [item];
1482
1484
  const config = tree.getConfig();
@@ -1491,9 +1493,11 @@ var dragAndDropFeature = {
1491
1493
  const { imgElement, xOffset, yOffset } = config.setDragImage(items);
1492
1494
  (_c = e.dataTransfer) == null ? void 0 : _c.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
1493
1495
  }
1494
- if (config.createForeignDragObject) {
1495
- const { format, data } = config.createForeignDragObject(items);
1496
- (_d = e.dataTransfer) == null ? void 0 : _d.setData(format, data);
1496
+ if (config.createForeignDragObject && e.dataTransfer) {
1497
+ const { format, data, dropEffect, effectAllowed } = config.createForeignDragObject(items);
1498
+ e.dataTransfer.setData(format, data);
1499
+ if (dropEffect) e.dataTransfer.dropEffect = dropEffect;
1500
+ if (effectAllowed) e.dataTransfer.effectAllowed = effectAllowed;
1497
1501
  }
1498
1502
  tree.applySubStateUpdate("dnd", {
1499
1503
  draggedItems: items,
@@ -1502,6 +1506,7 @@ var dragAndDropFeature = {
1502
1506
  },
1503
1507
  onDragOver: (e) => {
1504
1508
  var _a, _b, _c;
1509
+ e.stopPropagation();
1505
1510
  const dataRef = tree.getDataRef();
1506
1511
  const nextDragCode = getDragCode(e, item, tree);
1507
1512
  if (nextDragCode === dataRef.current.lastDragCode) {
@@ -1512,7 +1517,7 @@ var dragAndDropFeature = {
1512
1517
  }
1513
1518
  dataRef.current.lastDragCode = nextDragCode;
1514
1519
  const target = getDragTarget(e, item, tree);
1515
- if (!((_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems) && (!e.dataTransfer || !((_c = (_b = tree.getConfig()).canDropForeignDragObject) == null ? void 0 : _c.call(_b, e.dataTransfer, target)))) {
1520
+ 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)))) {
1516
1521
  dataRef.current.lastAllowDrop = false;
1517
1522
  return;
1518
1523
  }
@@ -1536,12 +1541,17 @@ var dragAndDropFeature = {
1536
1541
  }));
1537
1542
  },
1538
1543
  onDragEnd: (e) => {
1539
- var _a, _b, _c, _d;
1544
+ var _a, _b;
1545
+ const { onCompleteForeignDrop, canDragForeignDragObjectOver } = tree.getConfig();
1540
1546
  const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
1541
1547
  if (((_b = e.dataTransfer) == null ? void 0 : _b.dropEffect) === "none" || !draggedItems) {
1542
1548
  return;
1543
1549
  }
1544
- (_d = (_c = tree.getConfig()).onCompleteForeignDrop) == null ? void 0 : _d.call(_c, draggedItems);
1550
+ const target = getDragTarget(e, item, tree);
1551
+ if (canDragForeignDragObjectOver && e.dataTransfer && !canDragForeignDragObjectOver(e.dataTransfer, target)) {
1552
+ return;
1553
+ }
1554
+ onCompleteForeignDrop == null ? void 0 : onCompleteForeignDrop(draggedItems);
1545
1555
  },
1546
1556
  onDrop: (e) => __async(null, null, function* () {
1547
1557
  var _a, _b, _c;
package/dist/index.mjs CHANGED
@@ -1328,12 +1328,14 @@ var getDragTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) =>
1328
1328
  };
1329
1329
 
1330
1330
  // src/features/drag-and-drop/feature.ts
1331
+ var defaultCanDropForeignDragObject = () => false;
1331
1332
  var dragAndDropFeature = {
1332
1333
  key: "drag-and-drop",
1333
1334
  deps: ["selection"],
1334
1335
  getDefaultConfig: (defaultConfig, tree) => __spreadValues({
1335
1336
  canDrop: (_, target) => target.item.isFolder(),
1336
- canDropForeignDragObject: () => false,
1337
+ canDropForeignDragObject: defaultCanDropForeignDragObject,
1338
+ canDragForeignDragObjectOver: defaultConfig.canDropForeignDragObject !== defaultCanDropForeignDragObject ? (dataTransfer) => dataTransfer.effectAllowed !== "none" : () => false,
1337
1339
  setDndState: makeStateUpdater("dnd", tree),
1338
1340
  canReorder: true
1339
1341
  }, defaultConfig),
@@ -1432,7 +1434,7 @@ var dragAndDropFeature = {
1432
1434
  draggable: true,
1433
1435
  onDragEnter: (e) => e.preventDefault(),
1434
1436
  onDragStart: (e) => {
1435
- var _a, _b, _c, _d;
1437
+ var _a, _b, _c;
1436
1438
  const selectedItems = tree.getSelectedItems();
1437
1439
  const items = selectedItems.includes(item) ? selectedItems : [item];
1438
1440
  const config = tree.getConfig();
@@ -1447,9 +1449,11 @@ var dragAndDropFeature = {
1447
1449
  const { imgElement, xOffset, yOffset } = config.setDragImage(items);
1448
1450
  (_c = e.dataTransfer) == null ? void 0 : _c.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
1449
1451
  }
1450
- if (config.createForeignDragObject) {
1451
- const { format, data } = config.createForeignDragObject(items);
1452
- (_d = e.dataTransfer) == null ? void 0 : _d.setData(format, data);
1452
+ if (config.createForeignDragObject && e.dataTransfer) {
1453
+ const { format, data, dropEffect, effectAllowed } = config.createForeignDragObject(items);
1454
+ e.dataTransfer.setData(format, data);
1455
+ if (dropEffect) e.dataTransfer.dropEffect = dropEffect;
1456
+ if (effectAllowed) e.dataTransfer.effectAllowed = effectAllowed;
1453
1457
  }
1454
1458
  tree.applySubStateUpdate("dnd", {
1455
1459
  draggedItems: items,
@@ -1458,6 +1462,7 @@ var dragAndDropFeature = {
1458
1462
  },
1459
1463
  onDragOver: (e) => {
1460
1464
  var _a, _b, _c;
1465
+ e.stopPropagation();
1461
1466
  const dataRef = tree.getDataRef();
1462
1467
  const nextDragCode = getDragCode(e, item, tree);
1463
1468
  if (nextDragCode === dataRef.current.lastDragCode) {
@@ -1468,7 +1473,7 @@ var dragAndDropFeature = {
1468
1473
  }
1469
1474
  dataRef.current.lastDragCode = nextDragCode;
1470
1475
  const target = getDragTarget(e, item, tree);
1471
- if (!((_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems) && (!e.dataTransfer || !((_c = (_b = tree.getConfig()).canDropForeignDragObject) == null ? void 0 : _c.call(_b, e.dataTransfer, target)))) {
1476
+ 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)))) {
1472
1477
  dataRef.current.lastAllowDrop = false;
1473
1478
  return;
1474
1479
  }
@@ -1492,12 +1497,17 @@ var dragAndDropFeature = {
1492
1497
  }));
1493
1498
  },
1494
1499
  onDragEnd: (e) => {
1495
- var _a, _b, _c, _d;
1500
+ var _a, _b;
1501
+ const { onCompleteForeignDrop, canDragForeignDragObjectOver } = tree.getConfig();
1496
1502
  const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
1497
1503
  if (((_b = e.dataTransfer) == null ? void 0 : _b.dropEffect) === "none" || !draggedItems) {
1498
1504
  return;
1499
1505
  }
1500
- (_d = (_c = tree.getConfig()).onCompleteForeignDrop) == null ? void 0 : _d.call(_c, draggedItems);
1506
+ const target = getDragTarget(e, item, tree);
1507
+ if (canDragForeignDragObjectOver && e.dataTransfer && !canDragForeignDragObjectOver(e.dataTransfer, target)) {
1508
+ return;
1509
+ }
1510
+ onCompleteForeignDrop == null ? void 0 : onCompleteForeignDrop(draggedItems);
1501
1511
  },
1502
1512
  onDrop: (e) => __async(null, null, function* () {
1503
1513
  var _a, _b, _c;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-tree/core",
3
- "version": "0.0.0-20250730213235",
3
+ "version": "0.0.0-20250731075124",
4
4
  "main": "dist/index.d.ts",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.mts",
@@ -300,6 +300,9 @@ describe("core-feature/drag-and-drop", () => {
300
300
 
301
301
  it("drags foreign object inside tree, on folder", () => {
302
302
  tree.mockedHandler("canDropForeignDragObject").mockReturnValue(true);
303
+ tree
304
+ .mockedHandler("canDragForeignDragObjectOver")
305
+ .mockReturnValue(true);
303
306
  const onDropForeignDragObject = tree.mockedHandler(
304
307
  "onDropForeignDragObject",
305
308
  );
@@ -318,6 +321,9 @@ describe("core-feature/drag-and-drop", () => {
318
321
  tree
319
322
  .mockedHandler("canDropForeignDragObject")
320
323
  .mockImplementation((_, target) => target.item.isFolder());
324
+ tree
325
+ .mockedHandler("canDragForeignDragObjectOver")
326
+ .mockImplementation((_, target) => target.item.isFolder());
321
327
  const onDropForeignDragObject = tree.mockedHandler(
322
328
  "onDropForeignDragObject",
323
329
  );
@@ -340,6 +346,9 @@ describe("core-feature/drag-and-drop", () => {
340
346
 
341
347
  it("doesnt drag foreign object inside tree if not allowed", () => {
342
348
  tree.mockedHandler("canDropForeignDragObject").mockReturnValue(false);
349
+ tree
350
+ .mockedHandler("canDragForeignDragObjectOver")
351
+ .mockReturnValue(false);
343
352
  const onDropForeignDragObject = tree.mockedHandler(
344
353
  "onDropForeignDragObject",
345
354
  );
@@ -8,13 +8,18 @@ import {
8
8
  } from "./utils";
9
9
  import { makeStateUpdater } from "../../utils";
10
10
 
11
+ const defaultCanDropForeignDragObject = () => false;
11
12
  export const dragAndDropFeature: FeatureImplementation = {
12
13
  key: "drag-and-drop",
13
14
  deps: ["selection"],
14
15
 
15
16
  getDefaultConfig: (defaultConfig, tree) => ({
16
17
  canDrop: (_, target) => target.item.isFolder(),
17
- canDropForeignDragObject: () => false,
18
+ canDropForeignDragObject: defaultCanDropForeignDragObject,
19
+ canDragForeignDragObjectOver:
20
+ defaultConfig.canDropForeignDragObject !== defaultCanDropForeignDragObject
21
+ ? (dataTransfer) => dataTransfer.effectAllowed !== "none"
22
+ : () => false,
18
23
  setDndState: makeStateUpdater("dnd", tree),
19
24
  canReorder: true,
20
25
  ...defaultConfig,
@@ -162,9 +167,13 @@ export const dragAndDropFeature: FeatureImplementation = {
162
167
  e.dataTransfer?.setDragImage(imgElement, xOffset ?? 0, yOffset ?? 0);
163
168
  }
164
169
 
165
- if (config.createForeignDragObject) {
166
- const { format, data } = config.createForeignDragObject(items);
167
- e.dataTransfer?.setData(format, data);
170
+ if (config.createForeignDragObject && e.dataTransfer) {
171
+ const { format, data, dropEffect, effectAllowed } =
172
+ config.createForeignDragObject(items);
173
+ e.dataTransfer.setData(format, data);
174
+
175
+ if (dropEffect) e.dataTransfer.dropEffect = dropEffect;
176
+ if (effectAllowed) e.dataTransfer.effectAllowed = effectAllowed;
168
177
  }
169
178
 
170
179
  tree.applySubStateUpdate("dnd", {
@@ -174,6 +183,7 @@ export const dragAndDropFeature: FeatureImplementation = {
174
183
  },
175
184
 
176
185
  onDragOver: (e: DragEvent) => {
186
+ e.stopPropagation(); // don't bubble up to container dragover
177
187
  const dataRef = tree.getDataRef<DndDataRef>();
178
188
  const nextDragCode = getDragCode(e, item, tree);
179
189
  if (nextDragCode === dataRef.current.lastDragCode) {
@@ -191,7 +201,7 @@ export const dragAndDropFeature: FeatureImplementation = {
191
201
  (!e.dataTransfer ||
192
202
  !tree
193
203
  .getConfig()
194
- .canDropForeignDragObject?.(e.dataTransfer, target))
204
+ .canDragForeignDragObjectOver?.(e.dataTransfer, target))
195
205
  ) {
196
206
  dataRef.current.lastAllowDrop = false;
197
207
  return;
@@ -222,13 +232,24 @@ export const dragAndDropFeature: FeatureImplementation = {
222
232
  },
223
233
 
224
234
  onDragEnd: (e: DragEvent) => {
235
+ const { onCompleteForeignDrop, canDragForeignDragObjectOver } =
236
+ tree.getConfig();
225
237
  const draggedItems = tree.getState().dnd?.draggedItems;
226
238
 
227
239
  if (e.dataTransfer?.dropEffect === "none" || !draggedItems) {
228
240
  return;
229
241
  }
230
242
 
231
- tree.getConfig().onCompleteForeignDrop?.(draggedItems);
243
+ const target = getDragTarget(e, item, tree);
244
+ if (
245
+ canDragForeignDragObjectOver &&
246
+ e.dataTransfer &&
247
+ !canDragForeignDragObjectOver(e.dataTransfer, target)
248
+ ) {
249
+ return;
250
+ }
251
+
252
+ onCompleteForeignDrop?.(draggedItems);
232
253
  },
233
254
 
234
255
  onDrop: async (e: DragEvent) => {
@@ -58,16 +58,31 @@ export type DragAndDropFeatureDef<T> = {
58
58
  createForeignDragObject?: (items: ItemInstance<T>[]) => {
59
59
  format: string;
60
60
  data: any;
61
+ dropEffect?: DataTransfer["dropEffect"];
62
+ effectAllowed?: DataTransfer["effectAllowed"];
61
63
  };
62
64
  setDragImage?: (items: ItemInstance<T>[]) => {
63
65
  imgElement: Element;
64
66
  xOffset?: number;
65
67
  yOffset?: number;
66
68
  };
69
+
70
+ /** Checks if a foreign drag object can be dropped on a target, validating that an actual drop can commence based on
71
+ * the data in the DataTransfer object. */
67
72
  canDropForeignDragObject?: (
68
73
  dataTransfer: DataTransfer,
69
74
  target: DragTarget<T>,
70
75
  ) => boolean;
76
+
77
+ /** Checks if a droppable visualization should be displayed when dragging a foreign object over a target. Since this
78
+ * is executed on a dragover event, `dataTransfer.getData()` is not available, so `dataTransfer.effectAllowed` or
79
+ * `dataTransfer.types` should be used instead. Before actually completing the drag, @{link canDropForeignDragObject}
80
+ * will be called by HT before applying the drop. */
81
+ canDragForeignDragObjectOver?: (
82
+ dataTransfer: DataTransfer,
83
+ target: DragTarget<T>,
84
+ ) => boolean;
85
+
71
86
  onDrop?: (
72
87
  items: ItemInstance<T>[],
73
88
  target: DragTarget<T>,