@hkdigital/lib-sveltekit 0.1.76 → 0.1.78

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.
Files changed (47) hide show
  1. package/dist/assets/autospuiten/car-paint-picker.d.ts +7 -7
  2. package/dist/components/drag-drop/DragDropContext.svelte +26 -0
  3. package/dist/components/drag-drop/DragDropContext.svelte.d.ts +18 -0
  4. package/dist/components/drag-drop/Draggable.svelte +227 -0
  5. package/dist/components/drag-drop/Draggable.svelte.d.ts +76 -0
  6. package/dist/components/drag-drop/DropZone.svelte +377 -0
  7. package/dist/components/drag-drop/DropZone.svelte.d.ts +114 -0
  8. package/dist/components/drag-drop/drag-state.svelte.d.ts +30 -0
  9. package/dist/components/drag-drop/drag-state.svelte.js +50 -0
  10. package/dist/components/drag-drop/index.d.ts +4 -0
  11. package/dist/components/drag-drop/index.js +5 -0
  12. package/dist/components/drag-drop/util.d.ts +32 -0
  13. package/dist/components/drag-drop/util.js +85 -0
  14. package/dist/components/tab-bar/HkTabBar.state.svelte.d.ts +4 -4
  15. package/dist/components/tab-bar/HkTabBar.svelte +3 -3
  16. package/dist/components/tab-bar/HkTabBar.svelte.d.ts +2 -2
  17. package/dist/components/tab-bar/HkTabBarSelector.state.svelte.d.ts +3 -3
  18. package/dist/components/tab-bar/HkTabBarSelector.svelte +3 -3
  19. package/dist/components/tab-bar/HkTabBarSelector.svelte.d.ts +2 -2
  20. package/dist/components/tab-bar/index.d.ts +1 -0
  21. package/dist/components/tab-bar/typedef.d.ts +3 -1
  22. package/dist/components/tab-bar/typedef.js +3 -0
  23. package/dist/constants/state-labels/drag-states.d.ts +5 -0
  24. package/dist/constants/state-labels/drag-states.js +6 -0
  25. package/dist/constants/state-labels/drop-states.d.ts +6 -0
  26. package/dist/constants/state-labels/drop-states.js +6 -0
  27. package/dist/themes/hkdev/components/drag-drop/draggable.css +51 -0
  28. package/dist/themes/hkdev/components/drag-drop/dropzone.css +73 -0
  29. package/dist/themes/hkdev/components.css +6 -0
  30. package/dist/typedef/context.d.ts +3 -0
  31. package/dist/typedef/context.js +6 -0
  32. package/dist/typedef/drag.d.ts +20 -0
  33. package/dist/typedef/drag.js +9 -0
  34. package/dist/typedef/drop.d.ts +20 -0
  35. package/dist/typedef/drop.js +9 -0
  36. package/dist/typedef/{image-meta.js → image.js} +0 -1
  37. package/dist/typedef/index.d.ts +4 -1
  38. package/dist/typedef/index.js +4 -1
  39. package/dist/util/geo/index.d.ts +10 -0
  40. package/dist/util/geo/index.js +26 -0
  41. package/dist/util/svelte/state-context/index.d.ts +2 -1
  42. package/dist/util/svelte/state-context/index.js +46 -12
  43. package/dist/widgets/game-box/GameBox.svelte +1 -0
  44. package/dist/widgets/hk-app-layout/HkAppLayout.state.svelte.d.ts +3 -3
  45. package/dist/widgets/image-box/ImageBox.svelte +2 -4
  46. package/package.json +3 -1
  47. /package/dist/typedef/{image-meta.d.ts → image.d.ts} +0 -0
@@ -0,0 +1,377 @@
1
+ <script>
2
+ import { onMount, onDestroy } from 'svelte';
3
+ import { toStateClasses } from '../../util/design-system/index.js';
4
+
5
+ import { createOrGetDragState } from './drag-state.svelte.js';
6
+
7
+ import {
8
+ findDraggableSource,
9
+ getDraggableIdFromEvent,
10
+ processDropWithData
11
+ } from './util.js';
12
+
13
+ import {
14
+ READY,
15
+ DRAG_OVER,
16
+ CAN_DROP,
17
+ CANNOT_DROP,
18
+ DROP_DISABLED,
19
+ ACTIVE_DROP
20
+ } from '../../constants/state-labels/drop-states.js';
21
+
22
+ /**
23
+ * @type {{
24
+ * zone?: string,
25
+ * group?: string,
26
+ * disabled?: boolean,
27
+ * accepts?: (item: any) => boolean,
28
+ * maxItems?: number,
29
+ * base?: string,
30
+ * classes?: string,
31
+ * children?: import('svelte').Snippet,
32
+ * contextKey?: import('../../typedef').ContextKey,
33
+ * empty?: import('svelte').Snippet,
34
+ * preview?: import('svelte').Snippet<[{
35
+ * item: any,
36
+ * source: string,
37
+ * group: string,
38
+ * metadata?: any
39
+ * }]>,
40
+ * isDragOver?: boolean,
41
+ * canDrop?: boolean,
42
+ * isDropping?: boolean,
43
+ * itemCount?: number,
44
+ * onDragEnter?: (detail: {
45
+ * event: DragEvent,
46
+ * zone: string,
47
+ * canDrop: boolean
48
+ * }) => void,
49
+ * onDragOver?: (detail: {
50
+ * event: DragEvent,
51
+ * zone: string
52
+ * }) => void,
53
+ * onDragLeave?: (detail: {
54
+ * event: DragEvent,
55
+ * zone: string
56
+ * }) => void,
57
+ * onDrop?: (detail: {
58
+ * event: DragEvent,
59
+ * zone: string,
60
+ * item: any,
61
+ * source: string,
62
+ * metadata?: any
63
+ * }) => any | Promise<any>,
64
+ * onDropStart?: (detail: {
65
+ * event: DragEvent,
66
+ * zone: string,
67
+ * data: any
68
+ * }) => void,
69
+ * onDropEnd?: (detail: {
70
+ * event: DragEvent,
71
+ * zone: string,
72
+ * data: any,
73
+ * success: boolean,
74
+ * error?: Error
75
+ * }) => void,
76
+ * [key: string]: any
77
+ * }}
78
+ */
79
+ let {
80
+ zone = 'default',
81
+ group = 'default',
82
+ disabled = false,
83
+ accepts = () => true,
84
+ maxItems = Infinity,
85
+ base = '',
86
+ classes = '',
87
+ children,
88
+ contextKey,
89
+ empty,
90
+ preview,
91
+ isDragOver = $bindable(false),
92
+ canDrop = $bindable(false),
93
+ isDropping = $bindable(false),
94
+ itemCount = $bindable(0),
95
+ onDragEnter,
96
+ onDragOver,
97
+ onDragLeave,
98
+ onDrop,
99
+ onDropStart,
100
+ onDropEnd,
101
+ ...attrs
102
+ } = $props();
103
+
104
+
105
+ const dragState = createOrGetDragState(contextKey);
106
+
107
+ // console.debug('DropZone contextKey:', contextKey);
108
+
109
+ let currentState = $state(READY);
110
+ let dropzoneElement; // Reference to the dropzone DOM element
111
+
112
+ // We'll use a flag to track if we're currently in the dropzone
113
+ // without relying on a counter approach
114
+ let isCurrentlyOver = $state(false);
115
+
116
+ // Cleanup function
117
+ let cleanup;
118
+
119
+ onMount(() => {
120
+ // Global dragend listener to ensure state cleanup
121
+ const handleGlobalDragEnd = () => {
122
+ isCurrentlyOver = false;
123
+ currentState = READY;
124
+ };
125
+
126
+ document.addEventListener('dragend', handleGlobalDragEnd);
127
+
128
+ cleanup = () => {
129
+ document.removeEventListener('dragend', handleGlobalDragEnd);
130
+ };
131
+ });
132
+
133
+ onDestroy(() => {
134
+ cleanup?.();
135
+ });
136
+
137
+ // Computed state object for CSS classes
138
+ let stateObject = $derived({
139
+ ready: currentState === READY,
140
+ 'drag-over': currentState === DRAG_OVER,
141
+ 'can-drop': currentState === CAN_DROP,
142
+ 'cannot-drop': currentState === CANNOT_DROP,
143
+ 'drop-disabled': disabled,
144
+ 'active-drop': currentState === ACTIVE_DROP
145
+ });
146
+
147
+ let stateClasses = $derived(toStateClasses(stateObject));
148
+
149
+ // Update bindable props
150
+ $effect(() => {
151
+ isDragOver = [
152
+ DRAG_OVER,
153
+ CAN_DROP,
154
+ CANNOT_DROP
155
+ ].includes(currentState);
156
+
157
+ canDrop = currentState === CAN_DROP;
158
+ isDropping = currentState === ACTIVE_DROP;
159
+ });
160
+
161
+ /**
162
+ * Check if we can accept the dragged item
163
+ *
164
+ * @param {Object} data
165
+ *
166
+ * @returns {boolean}
167
+ */
168
+ function canAcceptDrop(data) {
169
+ // console.debug('canAcceptDrop', data, {group});
170
+
171
+ if (disabled) return false;
172
+ if (!data) return false;
173
+ if (data.group !== group) return false;
174
+ if (!accepts(data.item)) return false;
175
+ if (itemCount >= maxItems) return false;
176
+ return true;
177
+ }
178
+
179
+ /**
180
+ * Handle drag enter with improved DOM traversal check
181
+ * @param {DragEvent} event
182
+ */
183
+ function handleDragEnter(event) {
184
+ // Prevent default to allow drop
185
+ event.preventDefault();
186
+
187
+ // If we're already in a drag-over state, don't reset anything
188
+ if (isCurrentlyOver) return;
189
+
190
+ // Now we're over this dropzone
191
+ isCurrentlyOver = true;
192
+
193
+ // Get the draggable ID from the event
194
+ const draggableId = getDraggableIdFromEvent(event);
195
+
196
+ if (draggableId) {
197
+ // Get the drag data for this specific draggable
198
+ const dragData = dragState.getDraggable(draggableId);
199
+
200
+ // Update state based on acceptance
201
+ if (dragData) {
202
+ currentState = canAcceptDrop(dragData)
203
+ ? CAN_DROP
204
+ : CANNOT_DROP;
205
+ } else {
206
+ currentState = DRAG_OVER;
207
+ }
208
+ } else {
209
+ // Fallback to the current drag data (for compatibility)
210
+ const dragData = dragState.current;
211
+
212
+ if (dragData) {
213
+ currentState = canAcceptDrop(dragData)
214
+ ? CAN_DROP
215
+ : CANNOT_DROP;
216
+ } else {
217
+ currentState = DRAG_OVER;
218
+ }
219
+ }
220
+
221
+ // Notify listeners
222
+ onDragEnter?.({ event, zone, canDrop: currentState === CAN_DROP });
223
+ }
224
+
225
+ /**
226
+ * Handle drag over
227
+ * @param {DragEvent} event
228
+ */
229
+ function handleDragOver(event) {
230
+ // Prevent default to allow drop
231
+ event.preventDefault();
232
+
233
+ // If we're not currently over this dropzone (despite dragover firing),
234
+ // treat it as an enter event
235
+ if (!isCurrentlyOver) {
236
+ handleDragEnter(event);
237
+ return;
238
+ }
239
+
240
+ // Get the draggable ID from the event
241
+ const draggableId = getDraggableIdFromEvent(event);
242
+ let dragData;
243
+
244
+ if (draggableId) {
245
+ // Get the drag data for this specific draggable
246
+ dragData = dragState.getDraggable(draggableId);
247
+ } else {
248
+ // Fallback to the current drag data (for compatibility)
249
+ dragData = dragState.current;
250
+ }
251
+
252
+ // Re-evaluate acceptance
253
+ if (dragData && [DRAG_OVER, CAN_DROP, CANNOT_DROP].includes(currentState)) {
254
+ currentState = canAcceptDrop(dragData)
255
+ ? CAN_DROP
256
+ : CANNOT_DROP;
257
+ }
258
+
259
+ // Set visual feedback based on drop acceptance
260
+ if (currentState === CAN_DROP) {
261
+ event.dataTransfer.dropEffect = 'move';
262
+ } else if (currentState === CANNOT_DROP) {
263
+ event.dataTransfer.dropEffect = 'none';
264
+ }
265
+
266
+ // Notify listeners
267
+ onDragOver?.({ event, zone });
268
+ }
269
+
270
+ /**
271
+ * Handle drag leave with improved DOM traversal check
272
+ * @param {DragEvent} event
273
+ */
274
+ function handleDragLeave(event) {
275
+ // We need to check if we're actually leaving the dropzone or just
276
+ // entering a child element within the dropzone
277
+
278
+ // relatedTarget is the element we're moving to
279
+ const relatedTarget = event.relatedTarget;
280
+
281
+ // If relatedTarget is null or outside our dropzone, we're truly leaving
282
+ const isActuallyLeaving = !relatedTarget ||
283
+ !dropzoneElement.contains(relatedTarget);
284
+
285
+ if (isActuallyLeaving) {
286
+ isCurrentlyOver = false;
287
+ currentState = READY;
288
+ onDragLeave?.({ event, zone });
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Handle drop
294
+ * @param {DragEvent} event
295
+ */
296
+ function handleDrop(event) {
297
+ // Prevent default browser actions
298
+ event.preventDefault();
299
+
300
+ // Reset our tracking state
301
+ isCurrentlyOver = false;
302
+
303
+ try {
304
+ // First try to get the draggable ID from the event
305
+ const draggableId = getDraggableIdFromEvent(event);
306
+ let dragData;
307
+
308
+ if (draggableId) {
309
+ // Get the drag data from state using the draggable ID
310
+ dragData = dragState.getDraggable(draggableId);
311
+ }
312
+
313
+ // If we couldn't get it from the element attribute, try dataTransfer
314
+ if (!dragData) {
315
+ // Parse the JSON data from the dataTransfer object (only works during drop)
316
+ const jsonData = event.dataTransfer.getData('application/json');
317
+ if (jsonData) {
318
+ dragData = JSON.parse(jsonData);
319
+ }
320
+ }
321
+
322
+ // Check if we can accept this drop
323
+ if (dragData && canAcceptDrop(dragData)) {
324
+ // Update state and notify listeners
325
+ currentState = ACTIVE_DROP;
326
+ onDropStart?.({ event, zone, data: dragData });
327
+
328
+ // Call the onDrop handler and handle Promise resolution
329
+ const dropResult = onDrop?.({
330
+ event,
331
+ zone,
332
+ item: dragData.item,
333
+ source: dragData.source,
334
+ metadata: dragData.metadata
335
+ });
336
+
337
+ // Handle async or sync results
338
+ Promise.resolve(dropResult).then(() => {
339
+ currentState = READY;
340
+ onDropEnd?.({ event, zone, data: dragData, success: true });
341
+ }).catch((error) => {
342
+ currentState = READY;
343
+ onDropEnd?.({ event, zone, data: dragData, success: false, error });
344
+ });
345
+ } else {
346
+ // Not a valid drop, reset state
347
+ currentState = READY;
348
+ }
349
+ } catch (error) {
350
+ // Handle parsing errors
351
+ console.error('Drop error:', error);
352
+ currentState = READY;
353
+ }
354
+ }
355
+ </script>
356
+
357
+ <div
358
+ data-component="dropzone"
359
+ bind:this={dropzoneElement}
360
+ ondragenter={handleDragEnter}
361
+ ondragover={handleDragOver}
362
+ ondragleave={handleDragLeave}
363
+ ondrop={handleDrop}
364
+ class="{base} {classes} {stateClasses}"
365
+ data-zone={zone}
366
+ {...attrs}
367
+ >
368
+ {#if children}
369
+ {@render children()}
370
+ {:else if currentState === CAN_DROP && preview}
371
+ {@render preview(dragState.current)}
372
+ {:else if itemCount === 0 && empty}
373
+ {@render empty()}
374
+ {:else}
375
+ <div data-element="drop-zone-empty">Drop items here</div>
376
+ {/if}
377
+ </div>
@@ -0,0 +1,114 @@
1
+ export default DropZone;
2
+ type DropZone = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<{
5
+ [key: string]: any;
6
+ zone?: string;
7
+ group?: string;
8
+ disabled?: boolean;
9
+ accepts?: (item: any) => boolean;
10
+ maxItems?: number;
11
+ base?: string;
12
+ classes?: string;
13
+ children?: Snippet<[]>;
14
+ contextKey?: ContextKey;
15
+ empty?: Snippet<[]>;
16
+ preview?: Snippet<[{
17
+ item: any;
18
+ source: string;
19
+ group: string;
20
+ metadata?: any;
21
+ }]>;
22
+ isDragOver?: boolean;
23
+ canDrop?: boolean;
24
+ isDropping?: boolean;
25
+ itemCount?: number;
26
+ onDragEnter?: (detail: {
27
+ event: DragEvent;
28
+ zone: string;
29
+ canDrop: boolean;
30
+ }) => void;
31
+ onDragOver?: (detail: {
32
+ event: DragEvent;
33
+ zone: string;
34
+ }) => void;
35
+ onDragLeave?: (detail: {
36
+ event: DragEvent;
37
+ zone: string;
38
+ }) => void;
39
+ onDrop?: (detail: {
40
+ event: DragEvent;
41
+ zone: string;
42
+ item: any;
43
+ source: string;
44
+ metadata?: any;
45
+ }) => any;
46
+ onDropStart?: (detail: {
47
+ event: DragEvent;
48
+ zone: string;
49
+ data: any;
50
+ }) => void;
51
+ onDropEnd?: (detail: {
52
+ event: DragEvent;
53
+ zone: string;
54
+ data: any;
55
+ success: boolean;
56
+ error?: Error;
57
+ }) => void;
58
+ }>): void;
59
+ };
60
+ declare const DropZone: import("svelte").Component<{
61
+ [key: string]: any;
62
+ zone?: string;
63
+ group?: string;
64
+ disabled?: boolean;
65
+ accepts?: (item: any) => boolean;
66
+ maxItems?: number;
67
+ base?: string;
68
+ classes?: string;
69
+ children?: import("svelte").Snippet;
70
+ contextKey?: import("../../typedef").ContextKey;
71
+ empty?: import("svelte").Snippet;
72
+ preview?: import("svelte").Snippet<[{
73
+ item: any;
74
+ source: string;
75
+ group: string;
76
+ metadata?: any;
77
+ }]>;
78
+ isDragOver?: boolean;
79
+ canDrop?: boolean;
80
+ isDropping?: boolean;
81
+ itemCount?: number;
82
+ onDragEnter?: (detail: {
83
+ event: DragEvent;
84
+ zone: string;
85
+ canDrop: boolean;
86
+ }) => void;
87
+ onDragOver?: (detail: {
88
+ event: DragEvent;
89
+ zone: string;
90
+ }) => void;
91
+ onDragLeave?: (detail: {
92
+ event: DragEvent;
93
+ zone: string;
94
+ }) => void;
95
+ onDrop?: (detail: {
96
+ event: DragEvent;
97
+ zone: string;
98
+ item: any;
99
+ source: string;
100
+ metadata?: any;
101
+ }) => any | Promise<any>;
102
+ onDropStart?: (detail: {
103
+ event: DragEvent;
104
+ zone: string;
105
+ data: any;
106
+ }) => void;
107
+ onDropEnd?: (detail: {
108
+ event: DragEvent;
109
+ zone: string;
110
+ data: any;
111
+ success: boolean;
112
+ error?: Error;
113
+ }) => void;
114
+ }, {}, "isDropping" | "isDragOver" | "canDrop" | "itemCount">;
@@ -0,0 +1,30 @@
1
+ export const createOrGetDragState: (contextKey: import("../../typedef").ContextKey) => DragState;
2
+ export const createDragState: (contextKey: import("../../typedef").ContextKey) => DragState;
3
+ export const getDragState: (contextKey: import("../../typedef").ContextKey) => DragState;
4
+ declare class DragState {
5
+ draggables: Map<any, any>;
6
+ /**
7
+ * @param {string} draggableId
8
+ * @param {import('../../typedef/drag.js').DragData} dragData
9
+ */
10
+ start(draggableId: string, dragData: import("../../typedef/drag.js").DragData): void;
11
+ /**
12
+ * @param {string} draggableId
13
+ */
14
+ end(draggableId: string): void;
15
+ /**
16
+ * @param {string} draggableId
17
+ * @returns {import('../../typedef/drag.js').DragData|undefined}
18
+ */
19
+ getDraggable(draggableId: string): import("../../typedef/drag.js").DragData | undefined;
20
+ /**
21
+ * Get the most recently started drag operation (convenience method)
22
+ * @returns {import('../../typedef/drag.js').DragData|undefined}
23
+ */
24
+ get current(): import("../../typedef/drag.js").DragData | undefined;
25
+ /**
26
+ * @returns {boolean}
27
+ */
28
+ isDragging(): boolean;
29
+ }
30
+ export {};
@@ -0,0 +1,50 @@
1
+ // drag-state.svelte.js
2
+ import { defineStateContext } from '../../util/svelte/state-context/index.js';
3
+
4
+ class DragState {
5
+ // Replace the single 'current' with a Map of draggable IDs
6
+ draggables = $state(new Map());
7
+
8
+ /**
9
+ * @param {string} draggableId
10
+ * @param {import('../../typedef/drag.js').DragData} dragData
11
+ */
12
+ start(draggableId, dragData) {
13
+ this.draggables.set(draggableId, dragData);
14
+ }
15
+
16
+ /**
17
+ * @param {string} draggableId
18
+ */
19
+ end(draggableId) {
20
+ this.draggables.delete(draggableId);
21
+ }
22
+
23
+ /**
24
+ * @param {string} draggableId
25
+ * @returns {import('../../typedef/drag.js').DragData|undefined}
26
+ */
27
+ getDraggable(draggableId) {
28
+ return this.draggables.get(draggableId);
29
+ }
30
+
31
+ /**
32
+ * Get the most recently started drag operation (convenience method)
33
+ * @returns {import('../../typedef/drag.js').DragData|undefined}
34
+ */
35
+ get current() {
36
+ // For backward compatibility with existing code
37
+ const entries = Array.from(this.draggables.entries());
38
+ return entries.length > 0 ? entries[entries.length - 1][1] : undefined;
39
+ }
40
+
41
+ /**
42
+ * @returns {boolean}
43
+ */
44
+ isDragging() {
45
+ return this.draggables.size > 0;
46
+ }
47
+ }
48
+
49
+ export const [createOrGetDragState, createDragState, getDragState] =
50
+ defineStateContext(DragState);
@@ -0,0 +1,4 @@
1
+ export { default as Draggable } from "./Draggable.svelte";
2
+ export { default as DropZone } from "./DropZone.svelte";
3
+ export { default as DragDropContext } from "./DragDropContext.svelte";
4
+ export * from "./drag-state.svelte.js";
@@ -0,0 +1,5 @@
1
+ export { default as Draggable } from './Draggable.svelte';
2
+ export { default as DropZone } from './DropZone.svelte';
3
+ export { default as DragDropContext } from './DragDropContext.svelte';
4
+
5
+ export * from './drag-state.svelte.js';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Find the source draggable element from an event
3
+ *
4
+ * @param {DragEvent} event
5
+ * @returns {HTMLElement|null}
6
+ */
7
+ export function findDraggableSource(event: DragEvent): HTMLElement | null;
8
+ /**
9
+ * Get draggable ID from an event, if available
10
+ * @param {DragEvent} event
11
+ * @returns {string|null}
12
+ */
13
+ export function getDraggableIdFromEvent(event: DragEvent): string | null;
14
+ /**
15
+ * Process a drop event with the provided data and handlers
16
+ * @param {DragEvent} event
17
+ * @param {any} data The drag data
18
+ * @param {Object} options
19
+ * @param {Function} options.onDropStart Optional drop start handler
20
+ * @param {Function} options.onDrop Main drop handler
21
+ * @param {Function} options.onDropEnd Optional drop end handler
22
+ * @param {string} options.zone The drop zone identifier
23
+ * @param {Function} options.setState Function to update component state
24
+ * @returns {Promise<boolean>} Success status
25
+ */
26
+ export function processDropWithData(event: DragEvent, data: any, { onDropStart, onDrop, onDropEnd, zone, setState }: {
27
+ onDropStart: Function;
28
+ onDrop: Function;
29
+ onDropEnd: Function;
30
+ zone: string;
31
+ setState: Function;
32
+ }): Promise<boolean>;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Find the source draggable element from an event
3
+ *
4
+ * @param {DragEvent} event
5
+ * @returns {HTMLElement|null}
6
+ */
7
+ export function findDraggableSource(event) {
8
+ const target = /** @type {Element|EventTarget|null} */ (event.target);
9
+
10
+ if (!(target instanceof Element)) {
11
+ return null;
12
+ }
13
+
14
+ let element = /** @type {Element|null} */ (target);
15
+
16
+ // Walk up the DOM tree
17
+ while (element !== null && element !== document.body) {
18
+ if (element.hasAttribute('data-id')) {
19
+ // Return as HTMLElement if needed
20
+ return /** @type {HTMLElement} */ (element);
21
+ }
22
+
23
+ element = element.parentElement;
24
+ }
25
+
26
+ return null;
27
+ }
28
+
29
+ /**
30
+ * Get draggable ID from an event, if available
31
+ * @param {DragEvent} event
32
+ * @returns {string|null}
33
+ */
34
+ export function getDraggableIdFromEvent(event) {
35
+ const element = findDraggableSource(event);
36
+ return element ? element.getAttribute('data-id') : null;
37
+ }
38
+
39
+ /**
40
+ * Process a drop event with the provided data and handlers
41
+ * @param {DragEvent} event
42
+ * @param {any} data The drag data
43
+ * @param {Object} options
44
+ * @param {Function} options.onDropStart Optional drop start handler
45
+ * @param {Function} options.onDrop Main drop handler
46
+ * @param {Function} options.onDropEnd Optional drop end handler
47
+ * @param {string} options.zone The drop zone identifier
48
+ * @param {Function} options.setState Function to update component state
49
+ * @returns {Promise<boolean>} Success status
50
+ */
51
+ export async function processDropWithData(event, data, {
52
+ onDropStart,
53
+ onDrop,
54
+ onDropEnd,
55
+ zone,
56
+ setState
57
+ }) {
58
+ try {
59
+ // Update state and notify listeners
60
+ setState('ACTIVE_DROP');
61
+ onDropStart?.({ event, zone, data });
62
+
63
+ // Call the onDrop handler
64
+ const dropResult = onDrop?.({
65
+ event,
66
+ zone,
67
+ item: data.item,
68
+ source: data.source,
69
+ metadata: data.metadata
70
+ });
71
+
72
+ // Handle async or sync results
73
+ await Promise.resolve(dropResult);
74
+
75
+ // Success path
76
+ setState('READY');
77
+ onDropEnd?.({ event, zone, data, success: true });
78
+ return true;
79
+ } catch (error) {
80
+ // Error path
81
+ setState('READY');
82
+ onDropEnd?.({ event, zone, data, success: false, error });
83
+ return false;
84
+ }
85
+ }