Skip to content

Commit 414f966

Browse files
authored
Merge pull request #77 from abdel-17/dnd-props
feat: add `canDrag` and `canDrop` props
2 parents 2be9ad3 + a83625c commit 414f966

File tree

7 files changed

+212
-89
lines changed

7 files changed

+212
-89
lines changed

.changeset/petite-frogs-travel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte-file-tree": minor
3+
---
4+
5+
feat: add `canDrag` and `canDrop` props to `Tree`

packages/svelte-file-tree/src/lib/components/Tree.svelte

Lines changed: 104 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
dropTargetForElements,
88
type ElementDropTargetGetFeedbackArgs,
99
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
10-
import { dropTargetForExternal } from "@atlaskit/pragmatic-drag-and-drop/external/adapter";
10+
import {
11+
dropTargetForExternal,
12+
type ExternalDropTargetGetFeedbackArgs,
13+
} from "@atlaskit/pragmatic-drag-and-drop/external/adapter";
1114
import { DEV } from "esm-env";
1215
import { SvelteSet } from "svelte/reactivity";
1316
import { isControlOrMeta, noop, truePredicate } from "$lib/helpers.js";
@@ -19,6 +22,7 @@
1922
type DefaultTFolder,
2023
} from "$lib/tree.svelte.js";
2124
import { setTreeContext } from "./context.js";
25+
import { DragData } from "./data.js";
2226
import type {
2327
TreeCopyToClipboardMethodOptions,
2428
TreeProps,
@@ -72,7 +76,9 @@
7276
onRemove = noop,
7377
onDragEnter = noop,
7478
onDragLeave = noop,
79+
canDrag = truePredicate,
7580
onDrag = noop,
81+
canDrop = truePredicate,
7682
onDrop = noop,
7783
...rest
7884
}: TreeProps<TFile, TFolder, TTree> = $props();
@@ -570,18 +576,6 @@
570576
return true;
571577
}
572578
573-
function getDragData(itemId: string) {
574-
return { itemId };
575-
}
576-
577-
function getItemFromDragData(data: Record<string, unknown>) {
578-
const itemId = data.itemId;
579-
if (typeof itemId !== "string") {
580-
return;
581-
}
582-
return getItem(itemId);
583-
}
584-
585579
function getDropDestination(item: TreeItemState<TFile, TFolder>) {
586580
switch (item.node.type) {
587581
case "file": {
@@ -593,15 +587,19 @@
593587
}
594588
}
595589
596-
function canDrop(item: TreeItemState<TFile, TFolder>, args: ElementDropTargetGetFeedbackArgs) {
590+
function canDropElementOnItem(
591+
item: TreeItemState<TFile, TFolder>,
592+
args: ElementDropTargetGetFeedbackArgs,
593+
) {
597594
if (item.disabled) {
598595
return false;
599596
}
600597
601-
const source = getItemFromDragData(args.source.data);
602-
if (source === undefined) {
598+
const dragData = args.source.data;
599+
if (!(dragData instanceof DragData)) {
603600
return false;
604601
}
602+
const source = dragData.item();
605603
606604
if (item === source) {
607605
// Dropping an item on itself is not allowed.
@@ -627,7 +625,28 @@
627625
}
628626
}
629627
630-
return true;
628+
return canDrop({
629+
type: "item",
630+
source,
631+
input: args.input,
632+
destination: dropDestinationItem?.node ?? root,
633+
});
634+
}
635+
636+
function canDropExternalOnItem(
637+
item: TreeItemState<TFile, TFolder>,
638+
args: ExternalDropTargetGetFeedbackArgs,
639+
) {
640+
if (item.disabled) {
641+
return false;
642+
}
643+
644+
return canDrop({
645+
type: "external",
646+
input: args.input,
647+
items: args.source.items,
648+
destination: getDropDestination(item),
649+
});
631650
}
632651
633652
function getDropDestinationFromLocation(location: DragLocation) {
@@ -637,11 +656,11 @@
637656
return root;
638657
}
639658
case "treeitem": {
640-
const dropTargetItem = getItemFromDragData(dropTarget.data);
641-
if (dropTargetItem === undefined) {
659+
const dropData = dropTarget.data;
660+
if (!(dropData instanceof DragData)) {
642661
return;
643662
}
644-
return getDropDestination(dropTargetItem);
663+
return getDropDestination(dropData.item());
645664
}
646665
}
647666
}
@@ -953,14 +972,26 @@
953972
selectedIds.add(item.node.id);
954973
}
955974
},
956-
getDragData,
957-
getItemFromDragData,
958975
getDropDestination,
959-
canDrag: (item) => !item.disabled,
960-
canDrop: (item, args) => {
976+
canDrag: (item, args) => {
977+
if (item.disabled) {
978+
return false;
979+
}
980+
981+
return canDrag({
982+
input: args.input,
983+
source: item,
984+
});
985+
},
986+
canDropElement: (item, args) => {
961987
// If an item cannot be dropped on, we need to notify the root drop target
962988
// that it cannot be dropped on as well.
963-
const result = canDrop(item, args);
989+
const result = canDropElementOnItem(item, args);
990+
(args.input as any).__canDrop = result;
991+
return result;
992+
},
993+
canDropExternal: (item, args) => {
994+
const result = canDropExternalOnItem(item, args);
964995
(args.input as any).__canDrop = result;
965996
return result;
966997
},
@@ -980,12 +1011,31 @@
9801011
$effect(() => {
9811012
return dropTargetForElements({
9821013
element: ref!,
983-
canDrop: (args) => (args.input as any).__canDrop !== false,
1014+
canDrop: (args) => {
1015+
// Check if an item prevented the drop.
1016+
if ((args.input as any).__canDrop === false) {
1017+
return false;
1018+
}
1019+
1020+
const dragData = args.source.data;
1021+
if (!(dragData instanceof DragData)) {
1022+
return false;
1023+
}
1024+
const source = dragData.item();
1025+
1026+
return canDrop({
1027+
type: "item",
1028+
input: args.input,
1029+
source,
1030+
destination: root,
1031+
});
1032+
},
9841033
onDragEnter: (args) => {
985-
const source = getItemFromDragData(args.source.data);
986-
if (source === undefined) {
987-
return;
1034+
const dragData = args.source.data;
1035+
if (!(dragData instanceof DragData)) {
1036+
return false;
9881037
}
1038+
const source = dragData.item();
9891039
9901040
onDragEnter({
9911041
type: "item",
@@ -995,10 +1045,11 @@
9951045
});
9961046
},
9971047
onDragLeave: (args) => {
998-
const source = getItemFromDragData(args.source.data);
999-
if (source === undefined) {
1000-
return;
1048+
const dragData = args.source.data;
1049+
if (!(dragData instanceof DragData)) {
1050+
return false;
10011051
}
1052+
const source = dragData.item();
10021053
10031054
onDragLeave({
10041055
type: "item",
@@ -1008,10 +1059,11 @@
10081059
});
10091060
},
10101061
onDrag: (args) => {
1011-
const source = getItemFromDragData(args.source.data);
1012-
if (source === undefined) {
1013-
return;
1062+
const dragData = args.source.data;
1063+
if (!(dragData instanceof DragData)) {
1064+
return false;
10141065
}
1066+
const source = dragData.item();
10151067
10161068
const location = args.location.current;
10171069
const dropDestination = getDropDestinationFromLocation(location);
@@ -1027,10 +1079,11 @@
10271079
});
10281080
},
10291081
onDrop: async (args) => {
1030-
const source = getItemFromDragData(args.source.data);
1031-
if (source === undefined) {
1032-
return;
1082+
const dragData = args.source.data;
1083+
if (!(dragData instanceof DragData)) {
1084+
return false;
10331085
}
1086+
const source = dragData.item();
10341087
10351088
const location = args.location.current;
10361089
const dropDestination = getDropDestinationFromLocation(location);
@@ -1060,6 +1113,19 @@
10601113
$effect(() => {
10611114
return dropTargetForExternal({
10621115
element: ref!,
1116+
canDrop: (args) => {
1117+
// Check if an item prevented the drop.
1118+
if ((args.input as any).__canDrop === false) {
1119+
return false;
1120+
}
1121+
1122+
return canDrop({
1123+
type: "external",
1124+
input: args.input,
1125+
items: args.source.items,
1126+
destination: root,
1127+
});
1128+
},
10631129
onDragEnter: (args) => {
10641130
onDragEnter({
10651131
type: "external",

packages/svelte-file-tree/src/lib/components/TreeItem.svelte

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import { composeEventHandlers, noop } from "$lib/helpers.js";
1212
import type { DefaultTFolder, FileNode, FileTree, FolderNode } from "$lib/tree.svelte.js";
1313
import { getTreeContext } from "./context.js";
14+
import { DragData } from "./data.js";
1415
import type { TreeItemProps } from "./types.js";
1516
1617
const context = getTreeContext<TFile, TFolder, TTree>();
@@ -41,10 +42,18 @@
4142
context.onClick(item, event);
4243
};
4344
45+
function getItem() {
46+
return item;
47+
}
48+
49+
function getDragData() {
50+
return new DragData(getItem);
51+
}
52+
4453
$effect(() => {
4554
return draggable({
4655
element: ref!,
47-
getInitialData: () => context.getDragData(item.node.id),
56+
getInitialData: getDragData,
4857
canDrag: (args) => context.canDrag(item, args),
4958
onDragStart: (args) => {
5059
context.onDragStart(item, args);
@@ -55,13 +64,14 @@
5564
$effect(() => {
5665
return dropTargetForElements({
5766
element: ref!,
58-
getData: () => context.getDragData(item.node.id),
59-
canDrop: (args) => context.canDrop(item, args),
67+
getData: getDragData,
68+
canDrop: (args) => context.canDropElement(item, args),
6069
onDragEnter: (args) => {
61-
const source = context.getItemFromDragData(args.source.data);
62-
if (source === undefined) {
70+
const dragData = args.source.data;
71+
if (!(dragData instanceof DragData)) {
6372
return;
6473
}
74+
const source = dragData.item();
6575
6676
onDragEnter({
6777
type: "item",
@@ -71,10 +81,11 @@
7181
});
7282
},
7383
onDragLeave: (args) => {
74-
const source = context.getItemFromDragData(args.source.data);
75-
if (source === undefined) {
84+
const dragData = args.source.data;
85+
if (!(dragData instanceof DragData)) {
7686
return;
7787
}
88+
const source = dragData.item();
7889
7990
onDragLeave({
8091
type: "item",
@@ -84,10 +95,11 @@
8495
});
8596
},
8697
onDrag: (args) => {
87-
const source = context.getItemFromDragData(args.source.data);
88-
if (source === undefined) {
98+
const dragData = args.source.data;
99+
if (!(dragData instanceof DragData)) {
89100
return;
90101
}
102+
const source = dragData.item();
91103
92104
onDrag({
93105
type: "item",
@@ -97,10 +109,11 @@
97109
});
98110
},
99111
onDrop: (args) => {
100-
const source = context.getItemFromDragData(args.source.data);
101-
if (source === undefined) {
112+
const dragData = args.source.data;
113+
if (!(dragData instanceof DragData)) {
102114
return;
103115
}
116+
const source = dragData.item();
104117
105118
onDrop({
106119
type: "item",
@@ -115,7 +128,8 @@
115128
$effect(() => {
116129
return dropTargetForExternal({
117130
element: ref!,
118-
getData: () => context.getDragData(item.node.id),
131+
getData: getDragData,
132+
canDrop: (args) => context.canDropExternal(item, args),
119133
onDragEnter: (args) => {
120134
onDragEnter({
121135
type: "external",

packages/svelte-file-tree/src/lib/components/context.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
ElementEventBasePayload,
44
ElementGetFeedbackArgs,
55
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
6+
import type { ExternalDropTargetGetFeedbackArgs } from "@atlaskit/pragmatic-drag-and-drop/external/adapter";
67
import { DEV } from "esm-env";
78
import { getContext, hasContext, setContext } from "svelte";
89
import type {
@@ -28,11 +29,16 @@ export type TreeContext<
2829
onFocusIn: (item: TreeItemState<TFile, TFolder>, event: TreeItemEvent<FocusEvent>) => void;
2930
onKeyDown: (item: TreeItemState<TFile, TFolder>, event: TreeItemEvent<KeyboardEvent>) => void;
3031
onClick: (item: TreeItemState<TFile, TFolder>, event: TreeItemEvent<MouseEvent>) => void;
31-
getDragData: (itemId: string) => Record<string, unknown>;
32-
getItemFromDragData: (data: Record<string, unknown>) => TreeItemState<TFile, TFolder> | undefined;
3332
getDropDestination: (item: TreeItemState<TFile, TFolder>) => TFolder | TTree;
3433
canDrag: (item: TreeItemState<TFile, TFolder>, args: ElementGetFeedbackArgs) => boolean;
35-
canDrop: (item: TreeItemState<TFile, TFolder>, args: ElementDropTargetGetFeedbackArgs) => boolean;
34+
canDropElement: (
35+
item: TreeItemState<TFile, TFolder>,
36+
args: ElementDropTargetGetFeedbackArgs,
37+
) => boolean;
38+
canDropExternal: (
39+
item: TreeItemState<TFile, TFolder>,
40+
args: ExternalDropTargetGetFeedbackArgs,
41+
) => boolean;
3642
onDragStart: (item: TreeItemState<TFile, TFolder>, args: ElementEventBasePayload) => void;
3743
onDestroyItem: (item: TreeItemState<TFile, TFolder>) => void;
3844
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { DefaultTFolder, FileNode, FolderNode, TreeItemState } from "$lib/tree.svelte.js";
2+
3+
export class DragData<
4+
TFile extends FileNode = FileNode,
5+
TFolder extends FolderNode<TFile | TFolder> = DefaultTFolder<TFile>,
6+
> {
7+
[key: PropertyKey]: unknown;
8+
9+
constructor(readonly item: () => TreeItemState<TFile, TFolder>) {}
10+
}

0 commit comments

Comments
 (0)