@headless-tree/core 0.0.0-20250726131941 → 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 +6 -1
- package/dist/index.d.mts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +52 -24
- package/dist/index.mjs +52 -24
- package/package.json +1 -1
- package/src/core/create-tree.ts +26 -15
- package/src/features/async-data-loader/feature.ts +5 -0
- package/src/features/async-data-loader/types.ts +2 -0
- package/src/features/checkboxes/feature.ts +1 -1
- package/src/features/drag-and-drop/drag-and-drop.spec.ts +9 -0
- package/src/features/drag-and-drop/feature.ts +27 -6
- package/src/features/drag-and-drop/types.ts +15 -0
- package/src/features/main/types.ts +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# @headless-tree/core
|
|
2
2
|
|
|
3
|
-
## 0.0.0-
|
|
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
|
|
|
11
|
+
- e8ddbb0: Added `item.updateCachedData(data)` in async tree feature, that works similar to the existing `item.updateCachedChildrenIds(childrenIds)` feature
|
|
7
12
|
- 662e2a8: Added stories and documentation on how to use nested DOM rendering for tree structures instead of flat lists,
|
|
8
13
|
which can be used for animating expand/collapse behavior
|
|
9
14
|
- b41e1d2: fixed a bug where ending drag without successful drop doesn't properly reset drag line (#132)
|
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;
|
|
@@ -150,8 +159,6 @@ type MainFeatureDef<T = any> = {
|
|
|
150
159
|
treeInstance: {
|
|
151
160
|
/** @internal */
|
|
152
161
|
applySubStateUpdate: <K extends keyof TreeState<any>>(stateName: K, updater: Updater<TreeState<T>[K]>) => void;
|
|
153
|
-
/** @internal */
|
|
154
|
-
buildItemInstance: (itemId: string) => ItemInstance<T>;
|
|
155
162
|
setState: SetStateFn<TreeState<T>>;
|
|
156
163
|
getState: () => TreeState<T>;
|
|
157
164
|
setConfig: SetStateFn<TreeConfig<T>>;
|
|
@@ -296,6 +303,7 @@ type AsyncDataLoaderFeatureDef<T> = {
|
|
|
296
303
|
* @param optimistic If true, the item will not trigger a state update on `loadingItemChildrens`, and
|
|
297
304
|
* the tree will continue to display the old data until the new data has loaded. */
|
|
298
305
|
invalidateChildrenIds: (optimistic?: boolean) => Promise<void>;
|
|
306
|
+
updateCachedData: (data: T) => void;
|
|
299
307
|
updateCachedChildrenIds: (childrenIds: string[]) => void;
|
|
300
308
|
isLoading: () => boolean;
|
|
301
309
|
};
|
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;
|
|
@@ -150,8 +159,6 @@ type MainFeatureDef<T = any> = {
|
|
|
150
159
|
treeInstance: {
|
|
151
160
|
/** @internal */
|
|
152
161
|
applySubStateUpdate: <K extends keyof TreeState<any>>(stateName: K, updater: Updater<TreeState<T>[K]>) => void;
|
|
153
|
-
/** @internal */
|
|
154
|
-
buildItemInstance: (itemId: string) => ItemInstance<T>;
|
|
155
162
|
setState: SetStateFn<TreeState<T>>;
|
|
156
163
|
getState: () => TreeState<T>;
|
|
157
164
|
setConfig: SetStateFn<TreeConfig<T>>;
|
|
@@ -296,6 +303,7 @@ type AsyncDataLoaderFeatureDef<T> = {
|
|
|
296
303
|
* @param optimistic If true, the item will not trigger a state update on `loadingItemChildrens`, and
|
|
297
304
|
* the tree will continue to display the old data until the new data has loaded. */
|
|
298
305
|
invalidateChildrenIds: (optimistic?: boolean) => Promise<void>;
|
|
306
|
+
updateCachedData: (data: T) => void;
|
|
299
307
|
updateCachedChildrenIds: (childrenIds: string[]) => void;
|
|
300
308
|
isLoading: () => boolean;
|
|
301
309
|
};
|
package/dist/index.js
CHANGED
|
@@ -547,19 +547,6 @@ var createTree = (initialConfig) => {
|
|
|
547
547
|
const externalStateSetter = config[stateHandlerNames[stateName]];
|
|
548
548
|
externalStateSetter == null ? void 0 : externalStateSetter(state[stateName]);
|
|
549
549
|
},
|
|
550
|
-
buildItemInstance: ({}, itemId) => {
|
|
551
|
-
const [instance, finalizeInstance] = buildInstance(
|
|
552
|
-
features,
|
|
553
|
-
"itemInstance",
|
|
554
|
-
(instance2) => ({
|
|
555
|
-
item: instance2,
|
|
556
|
-
tree: treeInstance,
|
|
557
|
-
itemId
|
|
558
|
-
})
|
|
559
|
-
);
|
|
560
|
-
finalizeInstance();
|
|
561
|
-
return instance;
|
|
562
|
-
},
|
|
563
550
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
564
551
|
rebuildTree: () => {
|
|
565
552
|
var _a2;
|
|
@@ -580,7 +567,23 @@ var createTree = (initialConfig) => {
|
|
|
580
567
|
(_c2 = config.setState) == null ? void 0 : _c2.call(config, state);
|
|
581
568
|
}
|
|
582
569
|
},
|
|
583
|
-
getItemInstance: ({}, itemId) =>
|
|
570
|
+
getItemInstance: ({}, itemId) => {
|
|
571
|
+
const existingInstance = itemInstancesMap[itemId];
|
|
572
|
+
if (!existingInstance) {
|
|
573
|
+
const [instance, finalizeInstance] = buildInstance(
|
|
574
|
+
features,
|
|
575
|
+
"itemInstance",
|
|
576
|
+
(instance2) => ({
|
|
577
|
+
item: instance2,
|
|
578
|
+
tree: treeInstance,
|
|
579
|
+
itemId
|
|
580
|
+
})
|
|
581
|
+
);
|
|
582
|
+
finalizeInstance();
|
|
583
|
+
return instance;
|
|
584
|
+
}
|
|
585
|
+
return existingInstance;
|
|
586
|
+
},
|
|
584
587
|
getItems: () => itemInstances,
|
|
585
588
|
registerElement: ({}, element) => {
|
|
586
589
|
if (treeElement === element) {
|
|
@@ -636,7 +639,17 @@ var createTree = (initialConfig) => {
|
|
|
636
639
|
var _a2;
|
|
637
640
|
return (_a2 = itemDataRefs[itemId]) != null ? _a2 : itemDataRefs[itemId] = { current: {} };
|
|
638
641
|
},
|
|
639
|
-
getItemMeta: ({ itemId }) =>
|
|
642
|
+
getItemMeta: ({ itemId }) => {
|
|
643
|
+
var _a2;
|
|
644
|
+
return (_a2 = itemMetaMap[itemId]) != null ? _a2 : {
|
|
645
|
+
itemId,
|
|
646
|
+
parentId: null,
|
|
647
|
+
level: -1,
|
|
648
|
+
index: -1,
|
|
649
|
+
posInSet: 0,
|
|
650
|
+
setSize: 1
|
|
651
|
+
};
|
|
652
|
+
}
|
|
640
653
|
}
|
|
641
654
|
};
|
|
642
655
|
features.unshift(mainFeature);
|
|
@@ -801,7 +814,7 @@ var selectionFeature = {
|
|
|
801
814
|
|
|
802
815
|
// src/features/checkboxes/feature.ts
|
|
803
816
|
var getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
804
|
-
if (!tree.getConfig().isItemFolder(tree.
|
|
817
|
+
if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) {
|
|
805
818
|
return [itemId];
|
|
806
819
|
}
|
|
807
820
|
const descendants = tree.retrieveChildrenIds(itemId).map((child) => getAllLoadedDescendants(tree, child, includeFolders)).flat();
|
|
@@ -1147,6 +1160,11 @@ var asyncDataLoaderFeature = {
|
|
|
1147
1160
|
const dataRef = tree.getDataRef();
|
|
1148
1161
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
1149
1162
|
tree.rebuildTree();
|
|
1163
|
+
},
|
|
1164
|
+
updateCachedData: ({ tree, itemId }, data) => {
|
|
1165
|
+
const dataRef = tree.getDataRef();
|
|
1166
|
+
dataRef.current.itemData[itemId] = data;
|
|
1167
|
+
tree.rebuildTree();
|
|
1150
1168
|
}
|
|
1151
1169
|
}
|
|
1152
1170
|
};
|
|
@@ -1354,12 +1372,14 @@ var getDragTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) =>
|
|
|
1354
1372
|
};
|
|
1355
1373
|
|
|
1356
1374
|
// src/features/drag-and-drop/feature.ts
|
|
1375
|
+
var defaultCanDropForeignDragObject = () => false;
|
|
1357
1376
|
var dragAndDropFeature = {
|
|
1358
1377
|
key: "drag-and-drop",
|
|
1359
1378
|
deps: ["selection"],
|
|
1360
1379
|
getDefaultConfig: (defaultConfig, tree) => __spreadValues({
|
|
1361
1380
|
canDrop: (_, target) => target.item.isFolder(),
|
|
1362
|
-
canDropForeignDragObject:
|
|
1381
|
+
canDropForeignDragObject: defaultCanDropForeignDragObject,
|
|
1382
|
+
canDragForeignDragObjectOver: defaultConfig.canDropForeignDragObject !== defaultCanDropForeignDragObject ? (dataTransfer) => dataTransfer.effectAllowed !== "none" : () => false,
|
|
1363
1383
|
setDndState: makeStateUpdater("dnd", tree),
|
|
1364
1384
|
canReorder: true
|
|
1365
1385
|
}, defaultConfig),
|
|
@@ -1458,7 +1478,7 @@ var dragAndDropFeature = {
|
|
|
1458
1478
|
draggable: true,
|
|
1459
1479
|
onDragEnter: (e) => e.preventDefault(),
|
|
1460
1480
|
onDragStart: (e) => {
|
|
1461
|
-
var _a, _b, _c
|
|
1481
|
+
var _a, _b, _c;
|
|
1462
1482
|
const selectedItems = tree.getSelectedItems();
|
|
1463
1483
|
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
1464
1484
|
const config = tree.getConfig();
|
|
@@ -1473,9 +1493,11 @@ var dragAndDropFeature = {
|
|
|
1473
1493
|
const { imgElement, xOffset, yOffset } = config.setDragImage(items);
|
|
1474
1494
|
(_c = e.dataTransfer) == null ? void 0 : _c.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
|
|
1475
1495
|
}
|
|
1476
|
-
if (config.createForeignDragObject) {
|
|
1477
|
-
const { format, data } = config.createForeignDragObject(items);
|
|
1478
|
-
|
|
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;
|
|
1479
1501
|
}
|
|
1480
1502
|
tree.applySubStateUpdate("dnd", {
|
|
1481
1503
|
draggedItems: items,
|
|
@@ -1484,6 +1506,7 @@ var dragAndDropFeature = {
|
|
|
1484
1506
|
},
|
|
1485
1507
|
onDragOver: (e) => {
|
|
1486
1508
|
var _a, _b, _c;
|
|
1509
|
+
e.stopPropagation();
|
|
1487
1510
|
const dataRef = tree.getDataRef();
|
|
1488
1511
|
const nextDragCode = getDragCode(e, item, tree);
|
|
1489
1512
|
if (nextDragCode === dataRef.current.lastDragCode) {
|
|
@@ -1494,7 +1517,7 @@ var dragAndDropFeature = {
|
|
|
1494
1517
|
}
|
|
1495
1518
|
dataRef.current.lastDragCode = nextDragCode;
|
|
1496
1519
|
const target = getDragTarget(e, item, tree);
|
|
1497
|
-
if (!((_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems) && (!e.dataTransfer || !((_c = (_b = tree.getConfig()).
|
|
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)))) {
|
|
1498
1521
|
dataRef.current.lastAllowDrop = false;
|
|
1499
1522
|
return;
|
|
1500
1523
|
}
|
|
@@ -1518,12 +1541,17 @@ var dragAndDropFeature = {
|
|
|
1518
1541
|
}));
|
|
1519
1542
|
},
|
|
1520
1543
|
onDragEnd: (e) => {
|
|
1521
|
-
var _a, _b
|
|
1544
|
+
var _a, _b;
|
|
1545
|
+
const { onCompleteForeignDrop, canDragForeignDragObjectOver } = tree.getConfig();
|
|
1522
1546
|
const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
|
|
1523
1547
|
if (((_b = e.dataTransfer) == null ? void 0 : _b.dropEffect) === "none" || !draggedItems) {
|
|
1524
1548
|
return;
|
|
1525
1549
|
}
|
|
1526
|
-
|
|
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);
|
|
1527
1555
|
},
|
|
1528
1556
|
onDrop: (e) => __async(null, null, function* () {
|
|
1529
1557
|
var _a, _b, _c;
|
package/dist/index.mjs
CHANGED
|
@@ -503,19 +503,6 @@ var createTree = (initialConfig) => {
|
|
|
503
503
|
const externalStateSetter = config[stateHandlerNames[stateName]];
|
|
504
504
|
externalStateSetter == null ? void 0 : externalStateSetter(state[stateName]);
|
|
505
505
|
},
|
|
506
|
-
buildItemInstance: ({}, itemId) => {
|
|
507
|
-
const [instance, finalizeInstance] = buildInstance(
|
|
508
|
-
features,
|
|
509
|
-
"itemInstance",
|
|
510
|
-
(instance2) => ({
|
|
511
|
-
item: instance2,
|
|
512
|
-
tree: treeInstance,
|
|
513
|
-
itemId
|
|
514
|
-
})
|
|
515
|
-
);
|
|
516
|
-
finalizeInstance();
|
|
517
|
-
return instance;
|
|
518
|
-
},
|
|
519
506
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
520
507
|
rebuildTree: () => {
|
|
521
508
|
var _a2;
|
|
@@ -536,7 +523,23 @@ var createTree = (initialConfig) => {
|
|
|
536
523
|
(_c2 = config.setState) == null ? void 0 : _c2.call(config, state);
|
|
537
524
|
}
|
|
538
525
|
},
|
|
539
|
-
getItemInstance: ({}, itemId) =>
|
|
526
|
+
getItemInstance: ({}, itemId) => {
|
|
527
|
+
const existingInstance = itemInstancesMap[itemId];
|
|
528
|
+
if (!existingInstance) {
|
|
529
|
+
const [instance, finalizeInstance] = buildInstance(
|
|
530
|
+
features,
|
|
531
|
+
"itemInstance",
|
|
532
|
+
(instance2) => ({
|
|
533
|
+
item: instance2,
|
|
534
|
+
tree: treeInstance,
|
|
535
|
+
itemId
|
|
536
|
+
})
|
|
537
|
+
);
|
|
538
|
+
finalizeInstance();
|
|
539
|
+
return instance;
|
|
540
|
+
}
|
|
541
|
+
return existingInstance;
|
|
542
|
+
},
|
|
540
543
|
getItems: () => itemInstances,
|
|
541
544
|
registerElement: ({}, element) => {
|
|
542
545
|
if (treeElement === element) {
|
|
@@ -592,7 +595,17 @@ var createTree = (initialConfig) => {
|
|
|
592
595
|
var _a2;
|
|
593
596
|
return (_a2 = itemDataRefs[itemId]) != null ? _a2 : itemDataRefs[itemId] = { current: {} };
|
|
594
597
|
},
|
|
595
|
-
getItemMeta: ({ itemId }) =>
|
|
598
|
+
getItemMeta: ({ itemId }) => {
|
|
599
|
+
var _a2;
|
|
600
|
+
return (_a2 = itemMetaMap[itemId]) != null ? _a2 : {
|
|
601
|
+
itemId,
|
|
602
|
+
parentId: null,
|
|
603
|
+
level: -1,
|
|
604
|
+
index: -1,
|
|
605
|
+
posInSet: 0,
|
|
606
|
+
setSize: 1
|
|
607
|
+
};
|
|
608
|
+
}
|
|
596
609
|
}
|
|
597
610
|
};
|
|
598
611
|
features.unshift(mainFeature);
|
|
@@ -757,7 +770,7 @@ var selectionFeature = {
|
|
|
757
770
|
|
|
758
771
|
// src/features/checkboxes/feature.ts
|
|
759
772
|
var getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
|
|
760
|
-
if (!tree.getConfig().isItemFolder(tree.
|
|
773
|
+
if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) {
|
|
761
774
|
return [itemId];
|
|
762
775
|
}
|
|
763
776
|
const descendants = tree.retrieveChildrenIds(itemId).map((child) => getAllLoadedDescendants(tree, child, includeFolders)).flat();
|
|
@@ -1103,6 +1116,11 @@ var asyncDataLoaderFeature = {
|
|
|
1103
1116
|
const dataRef = tree.getDataRef();
|
|
1104
1117
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
1105
1118
|
tree.rebuildTree();
|
|
1119
|
+
},
|
|
1120
|
+
updateCachedData: ({ tree, itemId }, data) => {
|
|
1121
|
+
const dataRef = tree.getDataRef();
|
|
1122
|
+
dataRef.current.itemData[itemId] = data;
|
|
1123
|
+
tree.rebuildTree();
|
|
1106
1124
|
}
|
|
1107
1125
|
}
|
|
1108
1126
|
};
|
|
@@ -1310,12 +1328,14 @@ var getDragTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) =>
|
|
|
1310
1328
|
};
|
|
1311
1329
|
|
|
1312
1330
|
// src/features/drag-and-drop/feature.ts
|
|
1331
|
+
var defaultCanDropForeignDragObject = () => false;
|
|
1313
1332
|
var dragAndDropFeature = {
|
|
1314
1333
|
key: "drag-and-drop",
|
|
1315
1334
|
deps: ["selection"],
|
|
1316
1335
|
getDefaultConfig: (defaultConfig, tree) => __spreadValues({
|
|
1317
1336
|
canDrop: (_, target) => target.item.isFolder(),
|
|
1318
|
-
canDropForeignDragObject:
|
|
1337
|
+
canDropForeignDragObject: defaultCanDropForeignDragObject,
|
|
1338
|
+
canDragForeignDragObjectOver: defaultConfig.canDropForeignDragObject !== defaultCanDropForeignDragObject ? (dataTransfer) => dataTransfer.effectAllowed !== "none" : () => false,
|
|
1319
1339
|
setDndState: makeStateUpdater("dnd", tree),
|
|
1320
1340
|
canReorder: true
|
|
1321
1341
|
}, defaultConfig),
|
|
@@ -1414,7 +1434,7 @@ var dragAndDropFeature = {
|
|
|
1414
1434
|
draggable: true,
|
|
1415
1435
|
onDragEnter: (e) => e.preventDefault(),
|
|
1416
1436
|
onDragStart: (e) => {
|
|
1417
|
-
var _a, _b, _c
|
|
1437
|
+
var _a, _b, _c;
|
|
1418
1438
|
const selectedItems = tree.getSelectedItems();
|
|
1419
1439
|
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
1420
1440
|
const config = tree.getConfig();
|
|
@@ -1429,9 +1449,11 @@ var dragAndDropFeature = {
|
|
|
1429
1449
|
const { imgElement, xOffset, yOffset } = config.setDragImage(items);
|
|
1430
1450
|
(_c = e.dataTransfer) == null ? void 0 : _c.setDragImage(imgElement, xOffset != null ? xOffset : 0, yOffset != null ? yOffset : 0);
|
|
1431
1451
|
}
|
|
1432
|
-
if (config.createForeignDragObject) {
|
|
1433
|
-
const { format, data } = config.createForeignDragObject(items);
|
|
1434
|
-
|
|
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;
|
|
1435
1457
|
}
|
|
1436
1458
|
tree.applySubStateUpdate("dnd", {
|
|
1437
1459
|
draggedItems: items,
|
|
@@ -1440,6 +1462,7 @@ var dragAndDropFeature = {
|
|
|
1440
1462
|
},
|
|
1441
1463
|
onDragOver: (e) => {
|
|
1442
1464
|
var _a, _b, _c;
|
|
1465
|
+
e.stopPropagation();
|
|
1443
1466
|
const dataRef = tree.getDataRef();
|
|
1444
1467
|
const nextDragCode = getDragCode(e, item, tree);
|
|
1445
1468
|
if (nextDragCode === dataRef.current.lastDragCode) {
|
|
@@ -1450,7 +1473,7 @@ var dragAndDropFeature = {
|
|
|
1450
1473
|
}
|
|
1451
1474
|
dataRef.current.lastDragCode = nextDragCode;
|
|
1452
1475
|
const target = getDragTarget(e, item, tree);
|
|
1453
|
-
if (!((_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems) && (!e.dataTransfer || !((_c = (_b = tree.getConfig()).
|
|
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)))) {
|
|
1454
1477
|
dataRef.current.lastAllowDrop = false;
|
|
1455
1478
|
return;
|
|
1456
1479
|
}
|
|
@@ -1474,12 +1497,17 @@ var dragAndDropFeature = {
|
|
|
1474
1497
|
}));
|
|
1475
1498
|
},
|
|
1476
1499
|
onDragEnd: (e) => {
|
|
1477
|
-
var _a, _b
|
|
1500
|
+
var _a, _b;
|
|
1501
|
+
const { onCompleteForeignDrop, canDragForeignDragObjectOver } = tree.getConfig();
|
|
1478
1502
|
const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems;
|
|
1479
1503
|
if (((_b = e.dataTransfer) == null ? void 0 : _b.dropEffect) === "none" || !draggedItems) {
|
|
1480
1504
|
return;
|
|
1481
1505
|
}
|
|
1482
|
-
|
|
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);
|
|
1483
1511
|
},
|
|
1484
1512
|
onDrop: (e) => __async(null, null, function* () {
|
|
1485
1513
|
var _a, _b, _c;
|
package/package.json
CHANGED
package/src/core/create-tree.ts
CHANGED
|
@@ -170,19 +170,6 @@ export const createTree = <T>(
|
|
|
170
170
|
] as Function;
|
|
171
171
|
externalStateSetter?.(state[stateName]);
|
|
172
172
|
},
|
|
173
|
-
buildItemInstance: ({}, itemId) => {
|
|
174
|
-
const [instance, finalizeInstance] = buildInstance(
|
|
175
|
-
features,
|
|
176
|
-
"itemInstance",
|
|
177
|
-
(instance) => ({
|
|
178
|
-
item: instance,
|
|
179
|
-
tree: treeInstance,
|
|
180
|
-
itemId,
|
|
181
|
-
}),
|
|
182
|
-
);
|
|
183
|
-
finalizeInstance();
|
|
184
|
-
return instance;
|
|
185
|
-
},
|
|
186
173
|
// TODO rebuildSubTree: (itemId: string) => void;
|
|
187
174
|
rebuildTree: () => {
|
|
188
175
|
rebuildItemMeta();
|
|
@@ -206,7 +193,23 @@ export const createTree = <T>(
|
|
|
206
193
|
config.setState?.(state);
|
|
207
194
|
}
|
|
208
195
|
},
|
|
209
|
-
getItemInstance: ({}, itemId) =>
|
|
196
|
+
getItemInstance: ({}, itemId) => {
|
|
197
|
+
const existingInstance = itemInstancesMap[itemId];
|
|
198
|
+
if (!existingInstance) {
|
|
199
|
+
const [instance, finalizeInstance] = buildInstance(
|
|
200
|
+
features,
|
|
201
|
+
"itemInstance",
|
|
202
|
+
(instance) => ({
|
|
203
|
+
item: instance,
|
|
204
|
+
tree: treeInstance,
|
|
205
|
+
itemId,
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
finalizeInstance();
|
|
209
|
+
return instance;
|
|
210
|
+
}
|
|
211
|
+
return existingInstance;
|
|
212
|
+
},
|
|
210
213
|
getItems: () => itemInstances,
|
|
211
214
|
registerElement: ({}, element) => {
|
|
212
215
|
if (treeElement === element) {
|
|
@@ -249,7 +252,15 @@ export const createTree = <T>(
|
|
|
249
252
|
getElement: ({ itemId }) => itemElementsMap[itemId],
|
|
250
253
|
// eslint-disable-next-line no-return-assign
|
|
251
254
|
getDataRef: ({ itemId }) => (itemDataRefs[itemId] ??= { current: {} }),
|
|
252
|
-
getItemMeta: ({ itemId }) =>
|
|
255
|
+
getItemMeta: ({ itemId }) =>
|
|
256
|
+
itemMetaMap[itemId] ?? {
|
|
257
|
+
itemId,
|
|
258
|
+
parentId: null,
|
|
259
|
+
level: -1,
|
|
260
|
+
index: -1,
|
|
261
|
+
posInSet: 0,
|
|
262
|
+
setSize: 1,
|
|
263
|
+
},
|
|
253
264
|
},
|
|
254
265
|
};
|
|
255
266
|
|
|
@@ -165,5 +165,10 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
|
|
|
165
165
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
166
166
|
tree.rebuildTree();
|
|
167
167
|
},
|
|
168
|
+
updateCachedData: ({ tree, itemId }, data) => {
|
|
169
|
+
const dataRef = tree.getDataRef<AsyncDataLoaderDataRef>();
|
|
170
|
+
dataRef.current.itemData[itemId] = data;
|
|
171
|
+
tree.rebuildTree();
|
|
172
|
+
},
|
|
168
173
|
},
|
|
169
174
|
};
|
|
@@ -34,6 +34,7 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
34
34
|
waitForItemChildrenLoaded: (itemId: string) => Promise<void>;
|
|
35
35
|
loadItemData: (itemId: string) => Promise<T>;
|
|
36
36
|
loadChildrenIds: (itemId: string) => Promise<string[]>;
|
|
37
|
+
/* idea: recursiveLoadItems: (itemId: string, cancelToken?: { current: boolean }, onLoad: (itemIds: string[]) => void) => Promise<T[]> */
|
|
37
38
|
};
|
|
38
39
|
itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
|
|
39
40
|
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
@@ -46,6 +47,7 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
46
47
|
* the tree will continue to display the old data until the new data has loaded. */
|
|
47
48
|
invalidateChildrenIds: (optimistic?: boolean) => Promise<void>;
|
|
48
49
|
|
|
50
|
+
updateCachedData: (data: T) => void;
|
|
49
51
|
updateCachedChildrenIds: (childrenIds: string[]) => void;
|
|
50
52
|
isLoading: () => boolean;
|
|
51
53
|
};
|
|
@@ -8,7 +8,7 @@ const getAllLoadedDescendants = <T>(
|
|
|
8
8
|
itemId: string,
|
|
9
9
|
includeFolders = false,
|
|
10
10
|
): string[] => {
|
|
11
|
-
if (!tree.getConfig().isItemFolder(tree.
|
|
11
|
+
if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) {
|
|
12
12
|
return [itemId];
|
|
13
13
|
}
|
|
14
14
|
const descendants = tree
|
|
@@ -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:
|
|
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 } =
|
|
167
|
-
|
|
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
|
-
.
|
|
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
|
|
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>,
|
|
@@ -36,8 +36,6 @@ export type MainFeatureDef<T = any> = {
|
|
|
36
36
|
stateName: K,
|
|
37
37
|
updater: Updater<TreeState<T>[K]>,
|
|
38
38
|
) => void;
|
|
39
|
-
/** @internal */
|
|
40
|
-
buildItemInstance: (itemId: string) => ItemInstance<T>;
|
|
41
39
|
setState: SetStateFn<TreeState<T>>;
|
|
42
40
|
getState: () => TreeState<T>;
|
|
43
41
|
setConfig: SetStateFn<TreeConfig<T>>;
|