Skip to content

Commit

Permalink
feat: add interactive elements support to draggable functionality and…
Browse files Browse the repository at this point in the history
… update version to 0.0.17
  • Loading branch information
Spikeysanju committed Dec 12, 2024
1 parent 6e08a96 commit 2078147
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 4 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@thisux/sveltednd",
"version": "0.0.16",
"version": "0.0.17",
"private": false,
"description": "A lightweight, flexible drag and drop library for Svelte 5 applications.",
"author": "sanju <[email protected]>",
Expand Down
29 changes: 27 additions & 2 deletions src/lib/actions/draggable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,24 @@ import type { DragDropOptions, DragDropState } from '$lib/types/index.js';

const DEFAULT_DRAGGING_CLASS = 'dragging';

export function draggable<T>(node: HTMLElement, options: DragDropOptions<T>) {
interface DraggableOptions<T> extends DragDropOptions<T> {
// Add new option for interactive selectors
interactive?: string[];
}

export function draggable<T>(node: HTMLElement, options: DraggableOptions<T>) {
const draggingClass = (options.attributes?.draggingClass || DEFAULT_DRAGGING_CLASS).split(' ');
let initialX: number;
let initialY: number;

function isInteractiveElement(target: HTMLElement): boolean {
if (!options.interactive) return false;

// Check if the target or its parents match any of the interactive selectors
return options.interactive.some(selector =>
target.matches(selector) || target.closest(selector)
);
}

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

function handlePointerDown(event: PointerEvent) {
if (options.disabled) return;

// If the target is an interactive element, don't start dragging
if (isInteractiveElement(event.target as HTMLElement)) {
return;
}

// Store initial pointer position
initialX = event.clientX;
initialY = event.clientY;

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

return {
update(newOptions: DragDropOptions<T>) {
update(newOptions: DraggableOptions<T>) {
options = newOptions;
node.draggable = !options.disabled;
},
Expand Down
3 changes: 2 additions & 1 deletion src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
{ path: '/grid-sort', title: 'Grid Sort' },
{ path: '/nested', title: 'Nested Containers' },
{ path: '/multiple', title: 'Multiple' },
{ path: '/custom-classes', title: 'Custom Classes' }
{ path: '/custom-classes', title: 'Custom Classes' },
{ path: '/interactive-elements', title: 'Interactives' }
];
const cn = (...classes: string[]) => classes.filter(Boolean).join(' ');
Expand Down
80 changes: 80 additions & 0 deletions src/routes/interactive-elements/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<script lang="ts">
import { draggable, droppable, type DragDropState } from '$lib/index.js';
interface Item {
id: string;
title: string;
}
let items = $state<Item[]>([
{ id: '1', title: 'This list is interactive' },
{ id: '2', title: 'You can drag and drop items' },
{ id: '3', title: 'You can also select items' },
{ id: '4', title: 'You can also delete items' }
]);
function handleDelete(id: string) {
items = items.filter((item) => item.id !== id);
}
function handleSelect(id: string) {
console.log('Selected item:', id);
alert('Selected item: ' + id);
}
function handleDrop(state: DragDropState<Item>) {
const { draggedItem, sourceContainer, targetContainer } = state;
if (!targetContainer || sourceContainer === targetContainer) return;
const sourceIndex = items.findIndex((item) => item.id === draggedItem.id);
const targetIndex = parseInt(targetContainer);
const [movedItem] = items.splice(sourceIndex, 1);
items.splice(targetIndex, 0, movedItem);
items = [...items]; // Force reactivity
}
</script>

<div class="container mx-auto p-8">
<div class="mb-8 flex flex-col gap-2">
<h1 class="text-2xl font-bold">Interactive Draggable List</h1>
<p class="text-gray-600">
Try clicking the items or delete buttons while also being able to drag and reorder the list.
</p>
</div>

<div class="space-y-2">
{#each items as item, index (item.id)}
<div
use:droppable={{
container: index.toString(),
callbacks: {
onDrop: handleDrop
}
}}
use:draggable={{
container: index.toString(),
dragData: item,
interactive: ['[data-delete-btn]', '[data-select-btn]', '.interactive']
}}
class="flex items-center justify-between rounded-lg bg-white p-4 shadow transition-all hover:shadow-md"
>
<button
data-select-btn
class="interactive flex-1 text-left hover:text-blue-600"
onclick={() => handleSelect(item.id)}
>
{item.title}
</button>

<button
data-delete-btn
class="ml-2 text-red-500 hover:text-red-700"
onclick={() => handleDelete(item.id)}
>
Delete
</button>
</div>
{/each}
</div>
</div>

0 comments on commit 2078147

Please sign in to comment.