diff --git a/.gitignore b/.gitignore index 8e23bac..61b3b83 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,8 @@ vite.config.js.timestamp-* vite.config.ts.timestamp-* # .github -.github/copilot-instructions.md \ No newline at end of file +.github/copilot-instructions.md + +# pnpm + +pnpm-lock.yaml \ No newline at end of file diff --git a/src/lib/actions/draggable.ts b/src/lib/actions/draggable.ts index c11e05c..ad370ef 100644 --- a/src/lib/actions/draggable.ts +++ b/src/lib/actions/draggable.ts @@ -1,7 +1,11 @@ import { dndState } from '$lib/stores/dnd.svelte.js'; import type { DragDropOptions, DragDropState } from '$lib/types/index.js'; +const DEFAULT_DRAGGING_CLASS = 'dragging'; + export function draggable(node: HTMLElement, options: DragDropOptions) { + const draggingClass = (options.attributes?.draggingClass || DEFAULT_DRAGGING_CLASS).split(' '); + function handleDragStart(event: DragEvent) { if (options.disabled) return; @@ -15,12 +19,12 @@ export function draggable(node: HTMLElement, options: DragDropOptions) { event.dataTransfer.setData('text/plain', JSON.stringify(options.dragData)); } - node.classList.add('dragging'); + node.classList.add(...draggingClass); options.callbacks?.onDragStart?.(dndState as DragDropState); } function handleDragEnd() { - node.classList.remove('dragging'); + node.classList.remove(...draggingClass); options.callbacks?.onDragEnd?.(dndState as DragDropState); // Reset state @@ -39,7 +43,7 @@ export function draggable(node: HTMLElement, options: DragDropOptions) { dndState.targetContainer = null; node.setPointerCapture(event.pointerId); - node.classList.add('dragging'); + node.classList.add(...draggingClass); options.callbacks?.onDragStart?.(dndState as DragDropState); } @@ -53,7 +57,7 @@ export function draggable(node: HTMLElement, options: DragDropOptions) { if (!dndState.isDragging) return; node.releasePointerCapture(event.pointerId); - node.classList.remove('dragging'); + node.classList.remove(...draggingClass); options.callbacks?.onDragEnd?.(dndState as DragDropState); // Reset state diff --git a/src/lib/actions/droppable.ts b/src/lib/actions/droppable.ts index adfed61..9511600 100644 --- a/src/lib/actions/droppable.ts +++ b/src/lib/actions/droppable.ts @@ -1,13 +1,21 @@ import { dndState } from '$lib/stores/dnd.svelte.js'; import type { DragDropOptions, DragDropState } from '$lib/types/index.js'; +const DEFAULT_DRAG_OVER_CLASS = 'drag-over'; + export function droppable(node: HTMLElement, options: DragDropOptions) { + const dragOverClass = (options.attributes?.draggingClass || DEFAULT_DRAG_OVER_CLASS).split(' '); + function handleDragEnter(event: DragEvent) { if (options.disabled) return; event.preventDefault(); + const target = event.target as HTMLElement; + dndState.targetContainer = options.container; - node.classList.add('drag-over'); + dndState.targetElement = target; + + node.classList.add(...dragOverClass); options.callbacks?.onDragEnter?.(dndState as DragDropState); } @@ -15,11 +23,16 @@ export function droppable(node: HTMLElement, options: DragDropOptions) { if (options.disabled) return; const target = event.target as HTMLElement; - if (!node.contains(target)) { - dndState.targetContainer = null; - node.classList.remove('drag-over'); - options.callbacks?.onDragLeave?.(dndState as DragDropState); - } + + // check if element is still being dragged over + if (!dndState.targetElement?.isSameNode(target)) return; + + node.classList.remove(...dragOverClass); + + options.callbacks?.onDragLeave?.(dndState as DragDropState); + + dndState.targetContainer = null; + dndState.targetElement = null; } function handleDragOver(event: DragEvent) { @@ -37,7 +50,7 @@ export function droppable(node: HTMLElement, options: DragDropOptions) { if (options.disabled) return; event.preventDefault(); - node.classList.remove('drag-over'); + node.classList.remove(...dragOverClass); try { if (event.dataTransfer) { @@ -55,7 +68,7 @@ export function droppable(node: HTMLElement, options: DragDropOptions) { if (options.disabled || !dndState.isDragging) return; dndState.targetContainer = options.container; - node.classList.add('drag-over'); + node.classList.add(...dragOverClass); options.callbacks?.onDragEnter?.(dndState as DragDropState); } @@ -63,14 +76,14 @@ export function droppable(node: HTMLElement, options: DragDropOptions) { if (options.disabled || !dndState.isDragging) return; dndState.targetContainer = null; - node.classList.remove('drag-over'); + node.classList.remove(...dragOverClass); options.callbacks?.onDragLeave?.(dndState as DragDropState); } function handlePointerUp(event: PointerEvent) { if (options.disabled || !dndState.isDragging) return; - node.classList.remove('drag-over'); + node.classList.remove(...dragOverClass); options.callbacks?.onDrop?.(dndState as DragDropState); } diff --git a/src/lib/stores/dnd.svelte.ts b/src/lib/stores/dnd.svelte.ts index 4bb7456..fcc6d7b 100644 --- a/src/lib/stores/dnd.svelte.ts +++ b/src/lib/stores/dnd.svelte.ts @@ -5,5 +5,6 @@ export const dndState = $state({ isDragging: false, draggedItem: null, sourceContainer: '', - targetContainer: null + targetContainer: null, + targetElement: null }); diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index 324aa7e..7c9a610 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -3,6 +3,7 @@ export interface DragDropState { draggedItem: T; sourceContainer: string; targetContainer: string | null; + targetElement: HTMLElement | null; } export interface DragDropCallbacks { @@ -14,9 +15,15 @@ export interface DragDropCallbacks { onDragEnd?: (state: DragDropState) => void; } +export interface DragDropAttributes { + draggingClass?: string; + dragOverClass?: string; +} + export interface DragDropOptions { dragData?: T; container: string; disabled?: boolean; callbacks?: DragDropCallbacks; + attributes?: DragDropAttributes; } diff --git a/src/routes/custom-classes/+page.svelte b/src/routes/custom-classes/+page.svelte new file mode 100644 index 0000000..7521f18 --- /dev/null +++ b/src/routes/custom-classes/+page.svelte @@ -0,0 +1,110 @@ + + +
+
+

Sortable List

+

Drag and drop items to reorder them in the list.

+
+ +
+
+
+ {#each items as item, index (item.id)} +
+
+

+ {item.title} +

+ + {item.priority} + +
+

+ {item.description} +

+
+ {/each} +
+
+
+
+ +