@hkdigital/lib-sveltekit 0.1.75 → 0.1.77
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/dist/assets/autospuiten/car-paint-picker.d.ts +7 -7
- package/dist/components/drag-drop/Draggable.svelte +217 -0
- package/dist/components/drag-drop/Draggable.svelte.d.ts +74 -0
- package/dist/components/drag-drop/Dropzone.svelte +320 -0
- package/dist/components/drag-drop/Dropzone.svelte.d.ts +112 -0
- package/dist/components/drag-drop/Dropzone.svelte__ +282 -0
- package/dist/components/drag-drop/drag-state.svelte.d.ts +6 -0
- package/dist/components/drag-drop/drag-state.svelte.js +19 -0
- package/dist/components/drag-drop/index.d.ts +2 -0
- package/dist/components/drag-drop/index.js +2 -0
- package/dist/config/imagetools.d.ts +14 -13
- package/dist/constants/state-labels/drag-states.d.ts +5 -0
- package/dist/constants/state-labels/drag-states.js +6 -0
- package/dist/constants/state-labels/drop-states.d.ts +6 -0
- package/dist/constants/state-labels/drop-states.js +6 -0
- package/dist/themes/hkdev/components/drag-drop/draggable.css +51 -0
- package/dist/themes/hkdev/components/drag-drop/dropzone.css +73 -0
- package/dist/themes/hkdev/components.css +6 -0
- package/dist/typedef/drag.d.ts +20 -0
- package/dist/typedef/drag.js +9 -0
- package/dist/typedef/drop.d.ts +20 -0
- package/dist/typedef/drop.js +9 -0
- package/dist/{config/typedef.d.ts → typedef/image.d.ts} +4 -0
- package/dist/{config/typedef.js → typedef/image.js} +5 -0
- package/dist/typedef/index.d.ts +3 -0
- package/dist/typedef/index.js +3 -0
- package/dist/util/geo/index.d.ts +10 -0
- package/dist/util/geo/index.js +26 -0
- package/dist/util/image/index.d.ts +1 -1
- package/dist/widgets/image-box/ImageBox.svelte +1 -1
- package/dist/widgets/image-box/ImageBox.svelte.d.ts +2 -2
- package/dist/widgets/image-box/index.d.ts +1 -1
- package/package.json +1 -1
@@ -1,11 +1,11 @@
|
|
1
1
|
export const carPaintImages: {
|
2
|
-
rusty: import("../../
|
3
|
-
"army-green": import("../../
|
4
|
-
"electric-blue": import("../../
|
5
|
-
"lemon-yellow": import("../../
|
6
|
-
"opaque-purple": import("../../
|
7
|
-
"sunset-orange": import("../../
|
8
|
-
"tomato-red": import("../../
|
2
|
+
rusty: import("../../typedef/image.js").ImageMeta[];
|
3
|
+
"army-green": import("../../typedef/image.js").ImageMeta[];
|
4
|
+
"electric-blue": import("../../typedef/image.js").ImageMeta[];
|
5
|
+
"lemon-yellow": import("../../typedef/image.js").ImageMeta[];
|
6
|
+
"opaque-purple": import("../../typedef/image.js").ImageMeta[];
|
7
|
+
"sunset-orange": import("../../typedef/image.js").ImageMeta[];
|
8
|
+
"tomato-red": import("../../typedef/image.js").ImageMeta[];
|
9
9
|
};
|
10
10
|
import Rusty from './car-paint-picker/rusty.jpg?preset=render&responsive';
|
11
11
|
import ArmyGreen from './car-paint-picker/army-green.jpg?preset=render&responsive';
|
@@ -0,0 +1,217 @@
|
|
1
|
+
<script>
|
2
|
+
import { toStateClasses } from '../../util/design-system/index.js';
|
3
|
+
import { useDragState } from './drag-state.svelte.js';
|
4
|
+
|
5
|
+
import {
|
6
|
+
IDLE,
|
7
|
+
DRAGGING,
|
8
|
+
DRAG_PREVIEW,
|
9
|
+
DROPPING,
|
10
|
+
DRAG_DISABLED
|
11
|
+
} from '../../constants/state-labels/drag-states.js';
|
12
|
+
|
13
|
+
const dragState = useDragState();
|
14
|
+
|
15
|
+
/**
|
16
|
+
* @type {{
|
17
|
+
* item: any,
|
18
|
+
* group?: string,
|
19
|
+
* source?: string,
|
20
|
+
* disabled?: boolean,
|
21
|
+
* dragDelay?: number,
|
22
|
+
* base?: string,
|
23
|
+
* classes?: string,
|
24
|
+
* children: import('svelte').Snippet,
|
25
|
+
* isDragging?: boolean,
|
26
|
+
* isDropping?: boolean,
|
27
|
+
* isDragPreview?: boolean,
|
28
|
+
* onDragStart?: (detail: {
|
29
|
+
* event: DragEvent,
|
30
|
+
* item: any,
|
31
|
+
* source: string,
|
32
|
+
* group: string
|
33
|
+
* }) => void,
|
34
|
+
* onDragging?: (detail: {
|
35
|
+
* event: DragEvent,
|
36
|
+
* item: any
|
37
|
+
* }) => void,
|
38
|
+
* onDragEnd?: (detail: {
|
39
|
+
* event: DragEvent,
|
40
|
+
* item: any,
|
41
|
+
* wasDropped: boolean
|
42
|
+
* }) => void,
|
43
|
+
* onDrop?: (detail: {
|
44
|
+
* event: DragEvent,
|
45
|
+
* item: any,
|
46
|
+
* wasDropped: boolean
|
47
|
+
* }) => void,
|
48
|
+
* canDrag?: (item: any) => boolean,
|
49
|
+
* [key: string]: any
|
50
|
+
* }}
|
51
|
+
*/
|
52
|
+
let {
|
53
|
+
item,
|
54
|
+
group = 'default',
|
55
|
+
source = 'default',
|
56
|
+
disabled = false,
|
57
|
+
dragDelay = 0,
|
58
|
+
base = '',
|
59
|
+
classes = '',
|
60
|
+
children,
|
61
|
+
isDragging = $bindable(false),
|
62
|
+
isDropping = $bindable(false),
|
63
|
+
isDragPreview = $bindable(false),
|
64
|
+
onDragStart,
|
65
|
+
onDragging,
|
66
|
+
onDragEnd,
|
67
|
+
onDrop,
|
68
|
+
canDrag = () => true,
|
69
|
+
...attrs
|
70
|
+
} = $props();
|
71
|
+
|
72
|
+
let dragTimeout = null;
|
73
|
+
let currentState = $state(IDLE);
|
74
|
+
|
75
|
+
// Computed state object for CSS classes
|
76
|
+
let stateObject = $derived({
|
77
|
+
idle: currentState === IDLE,
|
78
|
+
dragging: currentState === DRAGGING,
|
79
|
+
'drag-preview': currentState === DRAG_PREVIEW,
|
80
|
+
dropping: currentState === DROPPING,
|
81
|
+
'drag-disabled': disabled || !canDrag(item)
|
82
|
+
});
|
83
|
+
|
84
|
+
let stateClasses = $derived(toStateClasses(stateObject));
|
85
|
+
|
86
|
+
// Update bindable props
|
87
|
+
$effect(() => {
|
88
|
+
isDragging = currentState === DRAGGING;
|
89
|
+
isDropping = currentState === DROPPING;
|
90
|
+
isDragPreview = currentState === DRAG_PREVIEW;
|
91
|
+
});
|
92
|
+
|
93
|
+
/**
|
94
|
+
* Handle drag start
|
95
|
+
* @param {DragEvent} event
|
96
|
+
*/
|
97
|
+
function handleDragStart(event) {
|
98
|
+
if (disabled || !canDrag(item)) {
|
99
|
+
event.preventDefault();
|
100
|
+
return;
|
101
|
+
}
|
102
|
+
|
103
|
+
// Handle drag delay
|
104
|
+
if (dragDelay > 0) {
|
105
|
+
event.preventDefault();
|
106
|
+
currentState = DRAG_PREVIEW;
|
107
|
+
|
108
|
+
dragTimeout = setTimeout(() => {
|
109
|
+
currentState = DRAGGING;
|
110
|
+
startDrag(event);
|
111
|
+
}, dragDelay);
|
112
|
+
return;
|
113
|
+
}
|
114
|
+
|
115
|
+
currentState = DRAGGING;
|
116
|
+
startDrag(event);
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* Start the drag operation
|
121
|
+
* @param {DragEvent} event
|
122
|
+
*/
|
123
|
+
function startDrag(event) {
|
124
|
+
const dragData = {
|
125
|
+
item,
|
126
|
+
source,
|
127
|
+
group,
|
128
|
+
metadata: { timestamp: Date.now() }
|
129
|
+
};
|
130
|
+
|
131
|
+
// Set global drag state
|
132
|
+
dragState.start(item, source, group);
|
133
|
+
|
134
|
+
event.dataTransfer.effectAllowed = 'move';
|
135
|
+
event.dataTransfer.setData('application/json', JSON.stringify(dragData));
|
136
|
+
|
137
|
+
// Set custom drag image if needed
|
138
|
+
if (event.dataTransfer.setDragImage) {
|
139
|
+
// Could set custom drag image here
|
140
|
+
}
|
141
|
+
|
142
|
+
onDragStart?.({ event, item, source, group });
|
143
|
+
}
|
144
|
+
|
145
|
+
/**
|
146
|
+
* Handle during drag
|
147
|
+
* @param {DragEvent} event
|
148
|
+
*/
|
149
|
+
function handleDrag(event) {
|
150
|
+
if (currentState === DRAGGING) {
|
151
|
+
onDragging?.({ event, item });
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
/**
|
156
|
+
* Handle drag end
|
157
|
+
* @param {DragEvent} event
|
158
|
+
*/
|
159
|
+
function handleDragEnd(event) {
|
160
|
+
clearTimeout(dragTimeout);
|
161
|
+
|
162
|
+
// Clear global drag state
|
163
|
+
dragState.end();
|
164
|
+
|
165
|
+
// Check if drop was successful
|
166
|
+
const wasDropped = event.dataTransfer.dropEffect !== 'none';
|
167
|
+
|
168
|
+
if (wasDropped) {
|
169
|
+
currentState = DROPPING;
|
170
|
+
onDrop?.({ event, item, wasDropped: true });
|
171
|
+
|
172
|
+
// Brief dropping state before returning to idle
|
173
|
+
setTimeout(() => {
|
174
|
+
currentState = IDLE;
|
175
|
+
}, 100);
|
176
|
+
} else {
|
177
|
+
currentState = IDLE;
|
178
|
+
}
|
179
|
+
|
180
|
+
onDragEnd?.({ event, item, wasDropped });
|
181
|
+
}
|
182
|
+
|
183
|
+
/**
|
184
|
+
* Handle mouse down for drag delay
|
185
|
+
* @param {MouseEvent} event
|
186
|
+
*/
|
187
|
+
function handleMouseDown(event) {
|
188
|
+
if (dragDelay > 0 && !disabled && canDrag(item)) {
|
189
|
+
// Could add visual feedback here
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
/**
|
194
|
+
* Handle mouse up to cancel drag delay
|
195
|
+
* @param {MouseEvent} event
|
196
|
+
*/
|
197
|
+
function handleMouseUp(event) {
|
198
|
+
if (dragTimeout) {
|
199
|
+
clearTimeout(dragTimeout);
|
200
|
+
currentState = IDLE;
|
201
|
+
}
|
202
|
+
}
|
203
|
+
</script>
|
204
|
+
|
205
|
+
<div
|
206
|
+
data-component="draggable"
|
207
|
+
draggable={!disabled && canDrag(item)}
|
208
|
+
ondragstart={handleDragStart}
|
209
|
+
ondrag={handleDrag}
|
210
|
+
ondragend={handleDragEnd}
|
211
|
+
onmousedown={handleMouseDown}
|
212
|
+
onmouseup={handleMouseUp}
|
213
|
+
class="{base} {classes} {stateClasses}"
|
214
|
+
{...attrs}
|
215
|
+
>
|
216
|
+
{@render children()}
|
217
|
+
</div>
|
@@ -0,0 +1,74 @@
|
|
1
|
+
export default Draggable;
|
2
|
+
type Draggable = {
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
4
|
+
$set?(props: Partial<{
|
5
|
+
[key: string]: any;
|
6
|
+
item: any;
|
7
|
+
group?: string;
|
8
|
+
source?: string;
|
9
|
+
disabled?: boolean;
|
10
|
+
dragDelay?: number;
|
11
|
+
base?: string;
|
12
|
+
classes?: string;
|
13
|
+
children: Snippet<[]>;
|
14
|
+
isDragging?: boolean;
|
15
|
+
isDropping?: boolean;
|
16
|
+
isDragPreview?: boolean;
|
17
|
+
onDragStart?: (detail: {
|
18
|
+
event: DragEvent;
|
19
|
+
item: any;
|
20
|
+
source: string;
|
21
|
+
group: string;
|
22
|
+
}) => void;
|
23
|
+
onDragging?: (detail: {
|
24
|
+
event: DragEvent;
|
25
|
+
item: any;
|
26
|
+
}) => void;
|
27
|
+
onDragEnd?: (detail: {
|
28
|
+
event: DragEvent;
|
29
|
+
item: any;
|
30
|
+
wasDropped: boolean;
|
31
|
+
}) => void;
|
32
|
+
onDrop?: (detail: {
|
33
|
+
event: DragEvent;
|
34
|
+
item: any;
|
35
|
+
wasDropped: boolean;
|
36
|
+
}) => void;
|
37
|
+
canDrag?: (item: any) => boolean;
|
38
|
+
}>): void;
|
39
|
+
};
|
40
|
+
declare const Draggable: import("svelte").Component<{
|
41
|
+
[key: string]: any;
|
42
|
+
item: any;
|
43
|
+
group?: string;
|
44
|
+
source?: string;
|
45
|
+
disabled?: boolean;
|
46
|
+
dragDelay?: number;
|
47
|
+
base?: string;
|
48
|
+
classes?: string;
|
49
|
+
children: import("svelte").Snippet;
|
50
|
+
isDragging?: boolean;
|
51
|
+
isDropping?: boolean;
|
52
|
+
isDragPreview?: boolean;
|
53
|
+
onDragStart?: (detail: {
|
54
|
+
event: DragEvent;
|
55
|
+
item: any;
|
56
|
+
source: string;
|
57
|
+
group: string;
|
58
|
+
}) => void;
|
59
|
+
onDragging?: (detail: {
|
60
|
+
event: DragEvent;
|
61
|
+
item: any;
|
62
|
+
}) => void;
|
63
|
+
onDragEnd?: (detail: {
|
64
|
+
event: DragEvent;
|
65
|
+
item: any;
|
66
|
+
wasDropped: boolean;
|
67
|
+
}) => void;
|
68
|
+
onDrop?: (detail: {
|
69
|
+
event: DragEvent;
|
70
|
+
item: any;
|
71
|
+
wasDropped: boolean;
|
72
|
+
}) => void;
|
73
|
+
canDrag?: (item: any) => boolean;
|
74
|
+
}, {}, "isDragging" | "isDropping" | "isDragPreview">;
|
@@ -0,0 +1,320 @@
|
|
1
|
+
<script>
|
2
|
+
import { onMount, onDestroy } from 'svelte';
|
3
|
+
import { toStateClasses } from '../../util/design-system/index.js';
|
4
|
+
import { useDragState } from './drag-state.svelte.js';
|
5
|
+
|
6
|
+
import {
|
7
|
+
READY,
|
8
|
+
DRAG_OVER,
|
9
|
+
CAN_DROP,
|
10
|
+
CANNOT_DROP,
|
11
|
+
DROP_DISABLED,
|
12
|
+
ACTIVE_DROP
|
13
|
+
} from '../../constants/state-labels/drop-states.js';
|
14
|
+
|
15
|
+
const dragState = useDragState();
|
16
|
+
|
17
|
+
/**
|
18
|
+
* @type {{
|
19
|
+
* zone?: string,
|
20
|
+
* group?: string,
|
21
|
+
* disabled?: boolean,
|
22
|
+
* accepts?: (item: any) => boolean,
|
23
|
+
* maxItems?: number,
|
24
|
+
* base?: string,
|
25
|
+
* classes?: string,
|
26
|
+
* children?: import('svelte').Snippet,
|
27
|
+
* empty?: import('svelte').Snippet,
|
28
|
+
* preview?: import('svelte').Snippet<[{
|
29
|
+
* item: any,
|
30
|
+
* source: string,
|
31
|
+
* group: string,
|
32
|
+
* metadata?: any
|
33
|
+
* }]>,
|
34
|
+
* isDragOver?: boolean,
|
35
|
+
* canDrop?: boolean,
|
36
|
+
* isDropping?: boolean,
|
37
|
+
* itemCount?: number,
|
38
|
+
* onDragEnter?: (detail: {
|
39
|
+
* event: DragEvent,
|
40
|
+
* zone: string,
|
41
|
+
* canDrop: boolean
|
42
|
+
* }) => void,
|
43
|
+
* onDragOver?: (detail: {
|
44
|
+
* event: DragEvent,
|
45
|
+
* zone: string
|
46
|
+
* }) => void,
|
47
|
+
* onDragLeave?: (detail: {
|
48
|
+
* event: DragEvent,
|
49
|
+
* zone: string
|
50
|
+
* }) => void,
|
51
|
+
* onDrop?: (detail: {
|
52
|
+
* event: DragEvent,
|
53
|
+
* zone: string,
|
54
|
+
* item: any,
|
55
|
+
* source: string,
|
56
|
+
* metadata?: any
|
57
|
+
* }) => any | Promise<any>,
|
58
|
+
* onDropStart?: (detail: {
|
59
|
+
* event: DragEvent,
|
60
|
+
* zone: string,
|
61
|
+
* data: any
|
62
|
+
* }) => void,
|
63
|
+
* onDropEnd?: (detail: {
|
64
|
+
* event: DragEvent,
|
65
|
+
* zone: string,
|
66
|
+
* data: any,
|
67
|
+
* success: boolean,
|
68
|
+
* error?: Error
|
69
|
+
* }) => void,
|
70
|
+
* [key: string]: any
|
71
|
+
* }}
|
72
|
+
*/
|
73
|
+
let {
|
74
|
+
zone = 'default',
|
75
|
+
group = 'default',
|
76
|
+
disabled = false,
|
77
|
+
accepts = () => true,
|
78
|
+
maxItems = Infinity,
|
79
|
+
base = '',
|
80
|
+
classes = '',
|
81
|
+
children,
|
82
|
+
empty,
|
83
|
+
preview,
|
84
|
+
isDragOver = $bindable(false),
|
85
|
+
canDrop = $bindable(false),
|
86
|
+
isDropping = $bindable(false),
|
87
|
+
itemCount = $bindable(0),
|
88
|
+
onDragEnter,
|
89
|
+
onDragOver,
|
90
|
+
onDragLeave,
|
91
|
+
onDrop,
|
92
|
+
onDropStart,
|
93
|
+
onDropEnd,
|
94
|
+
...attrs
|
95
|
+
} = $props();
|
96
|
+
|
97
|
+
let currentState = $state(READY);
|
98
|
+
let dropzoneElement; // Reference to the dropzone DOM element
|
99
|
+
|
100
|
+
// We'll use a flag to track if we're currently in the dropzone
|
101
|
+
// without relying on a counter approach
|
102
|
+
let isCurrentlyOver = $state(false);
|
103
|
+
|
104
|
+
// Cleanup function
|
105
|
+
let cleanup;
|
106
|
+
|
107
|
+
onMount(() => {
|
108
|
+
// Global dragend listener to ensure state cleanup
|
109
|
+
const handleGlobalDragEnd = () => {
|
110
|
+
isCurrentlyOver = false;
|
111
|
+
currentState = READY;
|
112
|
+
};
|
113
|
+
|
114
|
+
document.addEventListener('dragend', handleGlobalDragEnd);
|
115
|
+
|
116
|
+
cleanup = () => {
|
117
|
+
document.removeEventListener('dragend', handleGlobalDragEnd);
|
118
|
+
};
|
119
|
+
});
|
120
|
+
|
121
|
+
onDestroy(() => {
|
122
|
+
cleanup?.();
|
123
|
+
});
|
124
|
+
|
125
|
+
// Computed state object for CSS classes
|
126
|
+
let stateObject = $derived({
|
127
|
+
ready: currentState === READY,
|
128
|
+
'drag-over': currentState === DRAG_OVER,
|
129
|
+
'can-drop': currentState === CAN_DROP,
|
130
|
+
'cannot-drop': currentState === CANNOT_DROP,
|
131
|
+
'drop-disabled': disabled,
|
132
|
+
'active-drop': currentState === ACTIVE_DROP
|
133
|
+
});
|
134
|
+
|
135
|
+
let stateClasses = $derived(toStateClasses(stateObject));
|
136
|
+
|
137
|
+
// Update bindable props
|
138
|
+
$effect(() => {
|
139
|
+
isDragOver = [
|
140
|
+
DRAG_OVER,
|
141
|
+
CAN_DROP,
|
142
|
+
CANNOT_DROP
|
143
|
+
].includes(currentState);
|
144
|
+
|
145
|
+
canDrop = currentState === CAN_DROP;
|
146
|
+
isDropping = currentState === ACTIVE_DROP;
|
147
|
+
});
|
148
|
+
|
149
|
+
/**
|
150
|
+
* Check if we can accept the dragged item
|
151
|
+
* @param {Object} data
|
152
|
+
* @returns {boolean}
|
153
|
+
*/
|
154
|
+
function canAcceptDrop(data) {
|
155
|
+
if (disabled) return false;
|
156
|
+
if (!data) return false;
|
157
|
+
if (data.group !== group) return false;
|
158
|
+
if (!accepts(data.item)) return false;
|
159
|
+
if (itemCount >= maxItems) return false;
|
160
|
+
return true;
|
161
|
+
}
|
162
|
+
|
163
|
+
/**
|
164
|
+
* Handle drag enter with improved DOM traversal check
|
165
|
+
* @param {DragEvent} event
|
166
|
+
*/
|
167
|
+
function handleDragEnter(event) {
|
168
|
+
// Prevent default to allow drop
|
169
|
+
event.preventDefault();
|
170
|
+
|
171
|
+
// If we're already in a drag-over state, don't reset anything
|
172
|
+
if (isCurrentlyOver) return;
|
173
|
+
|
174
|
+
// Now we're over this dropzone
|
175
|
+
isCurrentlyOver = true;
|
176
|
+
|
177
|
+
// Get the current drag data
|
178
|
+
const dragData = dragState.current;
|
179
|
+
|
180
|
+
// Update state based on acceptance
|
181
|
+
if (dragData) {
|
182
|
+
currentState = canAcceptDrop(dragData)
|
183
|
+
? CAN_DROP
|
184
|
+
: CANNOT_DROP;
|
185
|
+
} else {
|
186
|
+
currentState = DRAG_OVER;
|
187
|
+
}
|
188
|
+
|
189
|
+
// Notify listeners
|
190
|
+
onDragEnter?.({ event, zone, canDrop: currentState === CAN_DROP });
|
191
|
+
}
|
192
|
+
|
193
|
+
/**
|
194
|
+
* Handle drag over
|
195
|
+
* @param {DragEvent} event
|
196
|
+
*/
|
197
|
+
function handleDragOver(event) {
|
198
|
+
// Prevent default to allow drop
|
199
|
+
event.preventDefault();
|
200
|
+
|
201
|
+
// If we're not currently over this dropzone (despite dragover firing),
|
202
|
+
// treat it as an enter event
|
203
|
+
if (!isCurrentlyOver) {
|
204
|
+
handleDragEnter(event);
|
205
|
+
return;
|
206
|
+
}
|
207
|
+
|
208
|
+
// Re-evaluate acceptance on each dragover in case state changed
|
209
|
+
const dragData = dragState.current;
|
210
|
+
|
211
|
+
if (dragData && [DRAG_OVER, CAN_DROP, CANNOT_DROP].includes(currentState)) {
|
212
|
+
currentState = canAcceptDrop(dragData)
|
213
|
+
? CAN_DROP
|
214
|
+
: CANNOT_DROP;
|
215
|
+
}
|
216
|
+
|
217
|
+
// Set visual feedback based on drop acceptance
|
218
|
+
if (currentState === CAN_DROP) {
|
219
|
+
event.dataTransfer.dropEffect = 'move';
|
220
|
+
} else if (currentState === CANNOT_DROP) {
|
221
|
+
event.dataTransfer.dropEffect = 'none';
|
222
|
+
}
|
223
|
+
|
224
|
+
// Notify listeners
|
225
|
+
onDragOver?.({ event, zone });
|
226
|
+
}
|
227
|
+
|
228
|
+
/**
|
229
|
+
* Handle drag leave with improved DOM traversal check
|
230
|
+
* @param {DragEvent} event
|
231
|
+
*/
|
232
|
+
function handleDragLeave(event) {
|
233
|
+
// We need to check if we're actually leaving the dropzone or just
|
234
|
+
// entering a child element within the dropzone
|
235
|
+
|
236
|
+
// relatedTarget is the element we're moving to
|
237
|
+
const relatedTarget = event.relatedTarget;
|
238
|
+
|
239
|
+
// If relatedTarget is null or outside our dropzone, we're truly leaving
|
240
|
+
const isActuallyLeaving = !relatedTarget ||
|
241
|
+
!dropzoneElement.contains(relatedTarget);
|
242
|
+
|
243
|
+
if (isActuallyLeaving) {
|
244
|
+
isCurrentlyOver = false;
|
245
|
+
currentState = READY;
|
246
|
+
onDragLeave?.({ event, zone });
|
247
|
+
}
|
248
|
+
}
|
249
|
+
|
250
|
+
/**
|
251
|
+
* Handle drop
|
252
|
+
* @param {DragEvent} event
|
253
|
+
*/
|
254
|
+
function handleDrop(event) {
|
255
|
+
// Prevent default browser actions
|
256
|
+
event.preventDefault();
|
257
|
+
|
258
|
+
// Reset our tracking state
|
259
|
+
isCurrentlyOver = false;
|
260
|
+
|
261
|
+
try {
|
262
|
+
// Parse the JSON data from the dataTransfer object
|
263
|
+
const data = JSON.parse(event.dataTransfer.getData('application/json'));
|
264
|
+
|
265
|
+
// Check if we can accept this drop
|
266
|
+
if (canAcceptDrop(data)) {
|
267
|
+
// Update state and notify listeners
|
268
|
+
currentState = ACTIVE_DROP;
|
269
|
+
onDropStart?.({ event, zone, data });
|
270
|
+
|
271
|
+
// Call the onDrop handler and handle Promise resolution
|
272
|
+
const dropResult = onDrop?.({
|
273
|
+
event,
|
274
|
+
zone,
|
275
|
+
item: data.item,
|
276
|
+
source: data.source,
|
277
|
+
metadata: data.metadata
|
278
|
+
});
|
279
|
+
|
280
|
+
// Handle async or sync results
|
281
|
+
Promise.resolve(dropResult).then(() => {
|
282
|
+
currentState = READY;
|
283
|
+
onDropEnd?.({ event, zone, data, success: true });
|
284
|
+
}).catch((error) => {
|
285
|
+
currentState = READY;
|
286
|
+
onDropEnd?.({ event, zone, data, success: false, error });
|
287
|
+
});
|
288
|
+
} else {
|
289
|
+
// Not a valid drop, reset state
|
290
|
+
currentState = READY;
|
291
|
+
}
|
292
|
+
} catch (error) {
|
293
|
+
// Handle parsing errors
|
294
|
+
console.error('Drop error:', error);
|
295
|
+
currentState = READY;
|
296
|
+
}
|
297
|
+
}
|
298
|
+
</script>
|
299
|
+
|
300
|
+
<div
|
301
|
+
data-component="dropzone"
|
302
|
+
bind:this={dropzoneElement}
|
303
|
+
ondragenter={handleDragEnter}
|
304
|
+
ondragover={handleDragOver}
|
305
|
+
ondragleave={handleDragLeave}
|
306
|
+
ondrop={handleDrop}
|
307
|
+
class="{base} {classes} {stateClasses}"
|
308
|
+
data-zone={zone}
|
309
|
+
{...attrs}
|
310
|
+
>
|
311
|
+
{#if children}
|
312
|
+
{@render children()}
|
313
|
+
{:else if currentState === CAN_DROP && preview}
|
314
|
+
{@render preview(dragState.current)}
|
315
|
+
{:else if itemCount === 0 && empty}
|
316
|
+
{@render empty()}
|
317
|
+
{:else}
|
318
|
+
<div data-element="drop-zone-empty">Drop items here</div>
|
319
|
+
{/if}
|
320
|
+
</div>
|