Skip to content

Commit 2078147

Browse files
committed
feat: add interactive elements support to draggable functionality and update version to 0.0.17
1 parent 6e08a96 commit 2078147

File tree

4 files changed

+110
-4
lines changed

4 files changed

+110
-4
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@thisux/sveltednd",
3-
"version": "0.0.16",
3+
"version": "0.0.17",
44
"private": false,
55
"description": "A lightweight, flexible drag and drop library for Svelte 5 applications.",
66
"author": "sanju <[email protected]>",

src/lib/actions/draggable.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,24 @@ import type { DragDropOptions, DragDropState } from '$lib/types/index.js';
33

44
const DEFAULT_DRAGGING_CLASS = 'dragging';
55

6-
export function draggable<T>(node: HTMLElement, options: DragDropOptions<T>) {
6+
interface DraggableOptions<T> extends DragDropOptions<T> {
7+
// Add new option for interactive selectors
8+
interactive?: string[];
9+
}
10+
11+
export function draggable<T>(node: HTMLElement, options: DraggableOptions<T>) {
712
const draggingClass = (options.attributes?.draggingClass || DEFAULT_DRAGGING_CLASS).split(' ');
13+
let initialX: number;
14+
let initialY: number;
15+
16+
function isInteractiveElement(target: HTMLElement): boolean {
17+
if (!options.interactive) return false;
18+
19+
// Check if the target or its parents match any of the interactive selectors
20+
return options.interactive.some(selector =>
21+
target.matches(selector) || target.closest(selector)
22+
);
23+
}
824

925
function handleDragStart(event: DragEvent) {
1026
if (options.disabled) return;
@@ -40,6 +56,15 @@ export function draggable<T>(node: HTMLElement, options: DragDropOptions<T>) {
4056

4157
function handlePointerDown(event: PointerEvent) {
4258
if (options.disabled) return;
59+
60+
// If the target is an interactive element, don't start dragging
61+
if (isInteractiveElement(event.target as HTMLElement)) {
62+
return;
63+
}
64+
65+
// Store initial pointer position
66+
initialX = event.clientX;
67+
initialY = event.clientY;
4368

4469
dndState.isDragging = true;
4570
dndState.draggedItem = options.dragData;
@@ -79,7 +104,7 @@ export function draggable<T>(node: HTMLElement, options: DragDropOptions<T>) {
79104
node.addEventListener('pointerup', handlePointerUp);
80105

81106
return {
82-
update(newOptions: DragDropOptions<T>) {
107+
update(newOptions: DraggableOptions<T>) {
83108
options = newOptions;
84109
node.draggable = !options.disabled;
85110
},

src/routes/+layout.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
{ path: '/grid-sort', title: 'Grid Sort' },
1212
{ path: '/nested', title: 'Nested Containers' },
1313
{ path: '/multiple', title: 'Multiple' },
14-
{ path: '/custom-classes', title: 'Custom Classes' }
14+
{ path: '/custom-classes', title: 'Custom Classes' },
15+
{ path: '/interactive-elements', title: 'Interactives' }
1516
];
1617
1718
const cn = (...classes: string[]) => classes.filter(Boolean).join(' ');
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<script lang="ts">
2+
import { draggable, droppable, type DragDropState } from '$lib/index.js';
3+
4+
interface Item {
5+
id: string;
6+
title: string;
7+
}
8+
9+
let items = $state<Item[]>([
10+
{ id: '1', title: 'This list is interactive' },
11+
{ id: '2', title: 'You can drag and drop items' },
12+
{ id: '3', title: 'You can also select items' },
13+
{ id: '4', title: 'You can also delete items' }
14+
]);
15+
16+
function handleDelete(id: string) {
17+
items = items.filter((item) => item.id !== id);
18+
}
19+
20+
function handleSelect(id: string) {
21+
console.log('Selected item:', id);
22+
alert('Selected item: ' + id);
23+
}
24+
25+
function handleDrop(state: DragDropState<Item>) {
26+
const { draggedItem, sourceContainer, targetContainer } = state;
27+
if (!targetContainer || sourceContainer === targetContainer) return;
28+
29+
const sourceIndex = items.findIndex((item) => item.id === draggedItem.id);
30+
const targetIndex = parseInt(targetContainer);
31+
32+
const [movedItem] = items.splice(sourceIndex, 1);
33+
items.splice(targetIndex, 0, movedItem);
34+
items = [...items]; // Force reactivity
35+
}
36+
</script>
37+
38+
<div class="container mx-auto p-8">
39+
<div class="mb-8 flex flex-col gap-2">
40+
<h1 class="text-2xl font-bold">Interactive Draggable List</h1>
41+
<p class="text-gray-600">
42+
Try clicking the items or delete buttons while also being able to drag and reorder the list.
43+
</p>
44+
</div>
45+
46+
<div class="space-y-2">
47+
{#each items as item, index (item.id)}
48+
<div
49+
use:droppable={{
50+
container: index.toString(),
51+
callbacks: {
52+
onDrop: handleDrop
53+
}
54+
}}
55+
use:draggable={{
56+
container: index.toString(),
57+
dragData: item,
58+
interactive: ['[data-delete-btn]', '[data-select-btn]', '.interactive']
59+
}}
60+
class="flex items-center justify-between rounded-lg bg-white p-4 shadow transition-all hover:shadow-md"
61+
>
62+
<button
63+
data-select-btn
64+
class="interactive flex-1 text-left hover:text-blue-600"
65+
onclick={() => handleSelect(item.id)}
66+
>
67+
{item.title}
68+
</button>
69+
70+
<button
71+
data-delete-btn
72+
class="ml-2 text-red-500 hover:text-red-700"
73+
onclick={() => handleDelete(item.id)}
74+
>
75+
Delete
76+
</button>
77+
</div>
78+
{/each}
79+
</div>
80+
</div>

0 commit comments

Comments
 (0)