Livewire Drag And Drop - v2.0.1

API Reference - Livewire Drag and Drop

Complete reference for ArtisanPack UI Livewire Drag and Drop directives, events, and methods.

Module Exports

Default Export

The package exports a default function for manual plugin registration with Alpine.js.

Syntax

import LivewireDragAndDrop from '@artisanpack-ui/livewire-drag-and-drop'

document.addEventListener('alpine:init', () => {
    LivewireDragAndDrop(Alpine)
})

Parameters

Parameter Type Required Description
Alpine object Yes The Alpine.js instance to register directives with

Named Exports

For advanced usage, the package also exports individual functions:

registerDirectives(Alpine)

Registers the drag-and-drop directives (x-drag-context, x-drag-item) with Alpine.js.

import { registerDirectives } from '@artisanpack-ui/livewire-drag-and-drop'

document.addEventListener('alpine:init', () => {
    registerDirectives(Alpine)
})

registerLivewireHooks(Livewire)

Registers Livewire lifecycle hooks for proper DOM morphing support.

import { registerLivewireHooks } from '@artisanpack-ui/livewire-drag-and-drop'

document.addEventListener('livewire:init', () => {
    registerLivewireHooks(Livewire)
})

Note: When using the default export LivewireDragAndDrop(Alpine), it automatically calls both registerDirectives and registerLivewireHooks (if Livewire is available). The named exports are provided for advanced scenarios where granular control is needed.

Side-Effect Import

For automatic registration, you can use a side-effect import:

import '@artisanpack-ui/livewire-drag-and-drop'

This automatically registers the directives when Alpine.js initializes and Livewire hooks when Livewire initializes.


Alpine.js Directives

x-drag-context

Creates a drag-and-drop context container that manages draggable items and handles drop operations.

Syntax

<div x-drag-context="expression" [additional-attributes]>
    <!-- draggable items -->
</div>

Parameters

Parameter Type Required Description
expression string Yes JavaScript expression to handle drop events

Event Handler Signature

The expression receives an event object with the following detail structure:

{
    orderedIds: []  // Array of item IDs in their new order
}

Attributes Added

The directive automatically adds the following attributes to the container:

  • role="application" - Identifies as an interactive application
  • aria-label="Drag and drop interface" - Provides accessible description

Created Elements

  • Live Region: A hidden div with aria-live="polite" for screen reader announcements
  • Instructions: Global drag instructions element (if not already present)

Example Usage

<div 
    x-data="{ 
        handleReorder(event) {
            const { oldIndex, newIndex } = event.detail;
            $wire.reorderItems(oldIndex, newIndex);
        }
    }"
    x-drag-context="handleReorder($event.detail)"
    @drag:end="handleReorder($event)"
    class="drag-container"
>
    <!-- draggable items -->
</div>

Event Listeners

The context container listens for these events:

  • dragover - Prevents default and sets drop effect
  • drop - Handles item dropping and calculates new position
  • keydown - Handles Escape key for canceling operations

x-drag-item

Designates an element as draggable within a drag context.

Syntax

<div x-drag-item="data" [additional-attributes]>
    Item content
</div>

Parameters

Parameter Type Required Description
data `object string` Yes

Data Object Structure

The data parameter can be any serializable object. Common structure:

interface DragItemData {
    id: number|string;      // Unique identifier
    [key: string]: any;     // Additional properties
}

Attributes Added

The directive automatically adds these attributes:

  • draggable="true" - Enables HTML5 drag and drop
  • tabindex="0" - Makes item keyboard focusable
  • role="button" - Identifies as an interactive button
  • aria-grabbed="false" - Indicates grab state for screen readers
  • aria-describedby="drag-instructions" - Links to instruction text

Example Usage

<div x-drag-item="{{ json_encode(['id' => 1, 'title' => 'Task 1']) }}" class="drag-item">
    <span>Task 1</span>
</div>

State Classes

The following CSS classes are automatically added/removed:

  • .is-grabbing - Added during drag operations
  • Applied inline styles:
    • opacity: 0.5 - During drag
    • outline: 2px solid #007cba - On focus

Keyboard Navigation

Key Action
Space or Enter Grab/release item
ArrowUp or ArrowLeft Move item up/left
ArrowDown or ArrowRight Move item down/right
Escape Cancel drag operation
Tab Navigate between items

Event Listeners

Each draggable item listens for:

  • dragstart - Initiates drag operation
  • dragend - Completes drag operation
  • keydown - Handles keyboard navigation
  • focus - Adds focus styling
  • blur - Removes focus styling

Events

drag:end

Fired when a drag operation completes successfully, either via mouse/touch or keyboard.

Event Detail

interface DragEndEvent extends CustomEvent {
    detail: {
        group: string;          // Context group identifier
        oldIndex: number;       // Original item position
        newIndex: number;       // New item position
        sourceElement: Element; // The dragged element
        targetElement: Element; // Drop target element
    };
    bubbles: true;             // Event bubbles up the DOM
}

Usage Examples

Basic Event Handling:

document.addEventListener('drag:end', (event) => {
    const { oldIndex, newIndex } = event.detail;
    console.log(`Item moved from ${oldIndex} to ${newIndex}`);
});

Livewire Integration:

<div 
    x-data="{
        handleDragEnd(event) {
            const { orderedIds } = event.detail;
            $wire.reorderItems(orderedIds);
        }
    }"
    @drag:end="handleDragEnd($event)"
>
    <!-- Draggable items -->
</div>

Multiple Context Handling:

document.addEventListener('drag:end', (event) => {
    const { group, oldIndex, newIndex } = event.detail;
    
    switch (group) {
        case 'todos':
            updateTodos(oldIndex, newIndex);
            break;
        case 'kanban-todo':
            moveKanbanCard('todo', oldIndex, newIndex);
            break;
    }
});

JavaScript API

LivewireDragAndDrop()

Main initialization function that registers Alpine.js directives.

Syntax

LivewireDragAndDrop(Alpine)

Parameters

Parameter Type Required Description
Alpine object Yes Alpine.js instance

Example

import Alpine from 'alpinejs'
import LivewireDragAndDrop from '@artisanpack-ui/livewire-drag-and-drop'

document.addEventListener('alpine:init', () => {
    LivewireDragAndDrop(Alpine)
})

Alpine.start()

Accessibility Features

ARIA Attributes

The package automatically manages these ARIA attributes:

Container Attributes

  • role="application" - Indicates interactive application
  • aria-label="Drag and drop interface" - Describes the interface

Item Attributes

  • role="button" - Identifies items as interactive buttons
  • aria-grabbed="false|true" - Indicates grab state
  • aria-pressed="false|true" - Indicates activation state
  • aria-describedby="drag-instructions" - Links to instructions

Live Region

  • aria-live="polite" - Announces changes to screen readers
  • aria-atomic="true" - Reads entire region on changes

Screen Reader Announcements

The package provides automatic announcements for:

  • Drag Start: "Item grabbed for dragging"
  • Drag End: "Item released"
  • Keyboard Movement: "Moved to position X of Y"
  • Cancel: "Drag operation cancelled"

Focus Management

  • Items receive proper focus indicators
  • Focus remains on moved items after keyboard operations
  • Tab order is maintained during drag operations

CSS Classes

Required Classes

These classes must be defined in your CSS:

.sr-only {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}

Auto-Applied Classes

The package automatically applies these classes:

.is-grabbing

Applied to items during drag operations. Recommended styles:

.is-grabbing {
    opacity: 0.7;
    transform: scale(1.02);
    z-index: 1000;
    cursor: grabbing;
}

Focus Indicators

[x-drag-item]:focus {
    outline: 2px solid #3b82f6;
    outline-offset: 2px;
}

Drag Handles

.drag-handle {
    cursor: grab;
    color: #6b7280;
}

.drag-handle:hover {
    color: #374151;
}

Container Styles

[x-drag-context] {
    position: relative;
}

[x-drag-context]:focus-within {
    /* Highlight active container */
}

Advanced Usage

Custom Group Identifiers

Use the group attribute to handle multiple drag contexts:

<div x-drag-context="handleDrop" data-group="todos">
    <!-- todo items -->
</div>

<div x-drag-context="handleDrop" data-group="in-progress">
    <!-- in-progress items -->
</div>

Cross-Container Dragging

Enable dragging between different containers:

<!-- Source Container -->
<div 
    x-drag-context="handleSourceDrop"
    @dragover.prevent
    class="source-container"
>
    <div x-drag-item="{ id: 1, type: 'task' }">Task 1</div>
</div>

<!-- Target Container -->
<div 
    x-drag-context="handleTargetDrop"
    @dragover.prevent
    @drop="handleCrossContainerDrop($event)"
    class="target-container"
>
    <!-- Drop zone -->
</div>

Custom Data Transfer

Access the HTML5 DataTransfer API for advanced use cases:

<div 
    x-drag-item="{ id: 1, title: 'Task 1' }"
    @dragstart="$event.dataTransfer.setData('text/html', $el.outerHTML)"
    @dragstart="$event.dataTransfer.setDragImage(customImage, 0, 0)"
>
    Task content
</div>

Performance Optimization

For large lists, consider using intersection observers:

<div x-data="{ visible: false }" x-intersect="visible = true">
    <template x-if="visible">
        <div x-drag-context="handleDrop">
            <!-- Heavy content -->
        </div>
    </template>
</div>

Browser Compatibility

Required Features

  • Drag and Drop API: IE 11+, all modern browsers
  • Custom Events: IE 9+, all modern browsers
  • ARIA Attributes: All browsers with screen reader support
  • Alpine.js: As per Alpine.js requirements

Feature Detection

// Check for drag and drop support
if ('draggable' in document.createElement('div')) {
    // Drag and drop is supported
    LivewireDragAndDrop(Alpine)
} else {
    // Provide fallback UI
    console.warn('Drag and drop not supported')
}

Progressive Enhancement

The package gracefully degrades when JavaScript is disabled:

  • Items remain focusable and interactive
  • Form elements continue to work normally
  • Content remains accessible to screen readers

Error Handling

Common Errors

Directive Registration Errors:

// Ensure Alpine is available before registration
if (typeof Alpine !== 'undefined') {
    LivewireDragAndDrop(Alpine)
} else {
    console.error('Alpine.js not found')
}

Context Validation:

// x-drag-item validates its context
if (!dragContext || !dragContext._dragContext) {
    console.warn('x-drag-item must be used within x-drag-context')
    return
}

Debug Mode

Enable detailed logging:

// Add to your Alpine.js setup
Alpine.store('debug', true)

document.addEventListener('drag:end', (event) => {
    if (Alpine.store('debug')) {
        console.log('Drag operation details:', event.detail)
    }
})

Migration Guide

From Version 0.x to 1.x

Breaking Changes:

  1. Event Structure: drag:end now includes sourceElement and targetElement
  2. CSS Classes: .dragging renamed to .is-grabbing
  3. ARIA Attributes: Enhanced accessibility attributes

Update Steps:

<!-- Old (0.x) -->
<div @drag:end="handleDrop($event.detail.oldIndex, $event.detail.newIndex)">

<!-- New (2.x) -->
<div @drag:end="handleDrop($event.detail.orderedIds)">
/* Old (0.x) */
.dragging { opacity: 0.5; }

/* New (1.x) */
.is-grabbing { opacity: 0.7; transform: scale(1.02); }

Related Documentation: