{"_id":"@artisanpack-ui/livewire-drag-and-drop","_rev":"3-b46edbab4e136545ef3165c4b3f3b0bc","name":"@artisanpack-ui/livewire-drag-and-drop","dist-tags":{"latest":"2.0.1"},"versions":{"1.0.0":{"name":"@artisanpack-ui/livewire-drag-and-drop","version":"1.0.0","keywords":["livewire","laravel-livewire","alpine","alpinejs","laravel","tall-stack","drag-and-drop","sortable","kanban","accessible","accessibility","a11y","artisanpack-ui"],"author":{"name":"Jacob Martella"},"license":"MIT","_id":"@artisanpack-ui/livewire-drag-and-drop@1.0.0","maintainers":[{"name":"viewfromthebox","email":"viewfromthebox94@gmail.com"}],"homepage":"https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop#readme","bugs":{"url":"https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop/issues"},"dist":{"shasum":"5ebdc9372ca83a58069c80b05b0f4fd9812f0378","tarball":"https://registry.npmjs.org/@artisanpack-ui/livewire-drag-and-drop/-/livewire-drag-and-drop-1.0.0.tgz","fileCount":5,"integrity":"sha512-Aw0175yKOm/3lDsrE7aaRFgBA1/V1mYNIAMLv3yLM9Jeg5BjbZAe4qHGlnEPIWq88FXg8n7LMyMP0gq4yyaiTA==","signatures":[{"sig":"MEQCIEZomGkz6OoOFVsTQ2oGl8brV3QrmlehyVlRkSjyzjQ/AiAXw1MGyiekHvWLQ1QCRUe0oygmbgkXu/OvEEy56FSIdg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":66561},"main":"dist/livewire-drag-and-drop.umd.js","type":"module","module":"dist/livewire-drag-and-drop.es.js","gitHead":"585e0b8bdc489ad8122b8d10657bb810ee555074","scripts":{"test":"node --experimental-vm-modules node_modules/.bin/jest","build":"vite build","test:ci":"node --experimental-vm-modules node_modules/.bin/jest --ci --coverage --watchAll=false","test:watch":"node --experimental-vm-modules node_modules/.bin/jest --watch","test:coverage":"node --experimental-vm-modules node_modules/.bin/jest --coverage","prepublishOnly":"npm run build","test:accessibility":"node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=accessibility"},"_npmUser":{"name":"viewfromthebox","email":"viewfromthebox94@gmail.com"},"repository":{"url":"git+https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop.git","type":"git"},"_npmVersion":"11.6.2","description":"An accessibility-first drag-and-drop utility for Livewire and Alpine.js.","directories":{"doc":"docs"},"_nodeVersion":"23.7.0","dependencies":{"alpinejs":"^3.15.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","vite":"^7.1.9","jest-axe":"^10.0.0","puppeteer":"^24.23.0","@testing-library/dom":"^10.4.1","jest-environment-jsdom":"^29.7.0","@testing-library/jest-dom":"^6.9.1","@testing-library/user-event":"^14.6.1"},"_npmOperationalInternal":{"tmp":"tmp/livewire-drag-and-drop_1.0.0_1760213807935_0.7415849399718517","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@artisanpack-ui/livewire-drag-and-drop","version":"2.0.0","keywords":["livewire","laravel-livewire","alpine","alpinejs","laravel","tall-stack","drag-and-drop","sortable","kanban","accessible","accessibility","a11y","artisanpack-ui"],"author":{"name":"Jacob Martella"},"license":"MIT","_id":"@artisanpack-ui/livewire-drag-and-drop@2.0.0","maintainers":[{"name":"viewfromthebox","email":"viewfromthebox94@gmail.com"}],"homepage":"https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop#readme","bugs":{"url":"https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop/issues"},"dist":{"shasum":"6e3993e0a58019ffdc5fb66fbf3c70869430724d","tarball":"https://registry.npmjs.org/@artisanpack-ui/livewire-drag-and-drop/-/livewire-drag-and-drop-2.0.0.tgz","fileCount":5,"integrity":"sha512-sjkeBJfJoQdqasFr8TpAxv+zJdFWe58JKgIMr5+hs+vMIAiHIShfGOz/lT5NN0jXI/U9k3Rw7McT2JQ/491sbQ==","signatures":[{"sig":"MEUCIQCIjwrjk6HyPTePPhqMoJnjDBse35bh+l30afg0sy1kxwIgFIfBXMzr3mQD6CWpBec0DX04oN4RaeWXcJBPm7thWk0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":70247},"main":"dist/livewire-drag-and-drop.umd.js","type":"module","module":"dist/livewire-drag-and-drop.es.js","gitHead":"8a10df4dd7306108217590447fb062d1e909453a","scripts":{"test":"node --experimental-vm-modules node_modules/.bin/jest","build":"vite build","test:ci":"node --experimental-vm-modules node_modules/.bin/jest --ci --coverage --watchAll=false","test:watch":"node --experimental-vm-modules node_modules/.bin/jest --watch","test:coverage":"node --experimental-vm-modules node_modules/.bin/jest --coverage","prepublishOnly":"npm run build","test:accessibility":"node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=accessibility"},"_npmUser":{"name":"viewfromthebox","email":"viewfromthebox94@gmail.com"},"repository":{"url":"git+https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop.git","type":"git"},"_npmVersion":"8.19.4","description":"An accessibility-first drag-and-drop utility for Livewire and Alpine.js.","directories":{"doc":"docs"},"_nodeVersion":"18.17.1","dependencies":{"alpinejs":"^3.15.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","vite":"^7.1.9","jest-axe":"^10.0.0","puppeteer":"^24.23.0","@testing-library/dom":"^10.4.1","jest-environment-jsdom":"^29.7.0","@testing-library/jest-dom":"^6.9.1","@testing-library/user-event":"^14.6.1"},"_npmOperationalInternal":{"tmp":"tmp/livewire-drag-and-drop_2.0.0_1760312119032_0.7694190753529531","host":"s3://npm-registry-packages-npm-production"}},"2.0.1":{"name":"@artisanpack-ui/livewire-drag-and-drop","version":"2.0.1","description":"An accessibility-first drag-and-drop utility for Livewire and Alpine.js.","keywords":["livewire","laravel-livewire","alpine","alpinejs","laravel","tall-stack","drag-and-drop","sortable","kanban","accessible","accessibility","a11y","artisanpack-ui"],"homepage":"https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop#readme","bugs":{"url":"https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop/issues"},"repository":{"type":"git","url":"git+https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop.git"},"license":"MIT","author":{"name":"Jacob Martella"},"type":"module","main":"dist/livewire-drag-and-drop.umd.js","module":"dist/livewire-drag-and-drop.es.js","directories":{"doc":"docs"},"scripts":{"test":"node --experimental-vm-modules node_modules/.bin/jest","test:watch":"node --experimental-vm-modules node_modules/.bin/jest --watch","test:coverage":"node --experimental-vm-modules node_modules/.bin/jest --coverage","test:ci":"node --experimental-vm-modules node_modules/.bin/jest --ci --coverage --watchAll=false","test:accessibility":"node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=accessibility","build":"vite build","prepublishOnly":"npm run build"},"devDependencies":{"@testing-library/dom":"^10.4.1","@testing-library/jest-dom":"^6.9.1","@testing-library/user-event":"^14.6.1","jest":"^29.7.0","jest-axe":"^10.0.0","jest-environment-jsdom":"^29.7.0","puppeteer":"^24.23.0","vite":"^7.1.9"},"dependencies":{"alpinejs":"^3.15.0"},"gitHead":"0e8b8eecbb3535cfc3508e829d67378d9fbfa00d","_id":"@artisanpack-ui/livewire-drag-and-drop@2.0.1","_nodeVersion":"23.7.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-TXMAF+JOP6IO3+fRERal3ywYFx1XGv7FG7GwfYdbFFtN+cRL8xszXT1xFgqwexwMQukeX+l9p26zkbmcL0L73A==","shasum":"116e9b53588f602ae75ef1c8eec2f31deb8e99f4","tarball":"https://registry.npmjs.org/@artisanpack-ui/livewire-drag-and-drop/-/livewire-drag-and-drop-2.0.1.tgz","fileCount":5,"unpackedSize":71080,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQClqw6r1P/0wl0L0gotwDZeGm6EwMq6i82mjB8BjAIO7gIgYiBqmjRyG7sJ0VGhugPzlkhYMCPPFG20hLg9KG6IpI4="}]},"_npmUser":{"name":"viewfromthebox","email":"viewfromthebox94@gmail.com"},"maintainers":[{"name":"viewfromthebox","email":"viewfromthebox94@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/livewire-drag-and-drop_2.0.1_1764955869362_0.3063422097305426"},"_hasShrinkwrap":false}},"time":{"created":"2025-10-11T20:16:47.820Z","modified":"2025-12-05T17:31:09.732Z","1.0.0":"2025-10-11T20:16:48.147Z","2.0.0":"2025-10-12T23:35:19.239Z","2.0.1":"2025-12-05T17:31:09.514Z"},"bugs":{"url":"https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop/issues"},"author":{"name":"Jacob Martella"},"license":"MIT","homepage":"https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop#readme","keywords":["livewire","laravel-livewire","alpine","alpinejs","laravel","tall-stack","drag-and-drop","sortable","kanban","accessible","accessibility","a11y","artisanpack-ui"],"repository":{"type":"git","url":"git+https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop.git"},"description":"An accessibility-first drag-and-drop utility for Livewire and Alpine.js.","maintainers":[{"name":"viewfromthebox","email":"viewfromthebox94@gmail.com"}],"readme":"# ArtisanPack UI: Livewire Drag and Drop\n\nAn accessibility-first drag-and-drop utility for Livewire and Alpine.js applications. Built with WCAG 2.1 AA compliance in mind, featuring full keyboard navigation, screen reader support, and seamless integration with Laravel's TALL stack.\n\n## Features\n\n- 🎯 **Accessibility-First**: Full WCAG 2.1 AA compliance with ARIA attributes and screen reader support\n- ⌨️ **Keyboard Navigation**: Complete keyboard control with arrow keys, space/enter, and escape\n- 🎨 **Alpine.js Integration**: Native Alpine.js directives for seamless TALL stack integration\n- 🔄 **Livewire Compatible**: Custom events for easy Livewire backend integration\n- 📱 **Touch Friendly**: Works on desktop, tablet, and mobile devices\n- 🎪 **Multiple Use Cases**: Perfect for lists, kanban boards, and complex drag-and-drop interfaces\n\n## Installation\n\n### Via NPM\n\n```bash\nnpm install @artisanpack-ui/livewire-drag-and-drop\n```\n\n### Via CDN\n\n```html\n<script src=\"https://unpkg.com/@artisanpack-ui/livewire-drag-and-drop@latest/dist/livewire-drag-and-drop.js\"></script>\n```\n\n### Setup with Alpine.js\n\nThe package supports two import methods:\n\n#### Method 1: Manual Registration (Recommended)\n\nGives you full control over plugin initialization.\n\n```javascript\nimport Alpine from 'alpinejs'\nimport LivewireDragAndDrop from '@artisanpack-ui/livewire-drag-and-drop'\n\ndocument.addEventListener('alpine:init', () => {\n    LivewireDragAndDrop(Alpine)\n})\n\nAlpine.start()\n```\n\n#### Method 2: Automatic Registration\n\nAutomatically registers when Alpine.js is ready.\n\n```javascript\nimport Alpine from 'alpinejs'\nimport '@artisanpack-ui/livewire-drag-and-drop'  // Auto-registers\n\nAlpine.start()\n```\n\n#### CDN Setup\n\n```html\n<script defer src=\"https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js\"></script>\n<script defer src=\"https://unpkg.com/@artisanpack-ui/livewire-drag-and-drop@latest/dist/livewire-drag-and-drop.js\"></script>\n<script>\ndocument.addEventListener('alpine:init', () => {\n    LivewireDragAndDrop(Alpine)\n})\n</script>\n```\n\n## Usage\n\n### Basic CSS Styles\n\nAdd these essential CSS styles to your application:\n\n```css\n.sr-only {\n    position: absolute !important;\n    width: 1px !important;\n    height: 1px !important;\n    padding: 0 !important;\n    margin: -1px !important;\n    overflow: hidden !important;\n    clip: rect(0, 0, 0, 0) !important;\n    white-space: nowrap !important;\n    border: 0 !important;\n}\n\n.is-grabbing {\n    opacity: 0.7;\n    transform: scale(1.02);\n    z-index: 1000;\n}\n```\n\n## Example 1: Simple Reorderable List\n\nThis example demonstrates a basic reorderable todo list using Livewire and the drag-and-drop functionality.\n\n### Livewire Component (ReorderableList.php)\n\n```php\n<?php\n\ndeclare( strict_types=1 );\n\nnamespace App\\Livewire;\n\nuse Illuminate\\Contracts\\View\\View;\nuse Illuminate\\Support\\Collection;\nuse Livewire\\Attributes\\On;\nuse Livewire\\Component;\n\n/**\n * Reorderable List Component\n *\n * A simple todo list component that demonstrates drag-and-drop reordering\n * functionality with full accessibility support and Livewire integration.\n *\n * @since 1.0.0\n */\nclass ReorderableList extends Component\n{\n    /**\n     * Collection of todo items.\n     *\n     * @since 1.0.0\n     * @var array<int, array<string, mixed>>\n     */\n    public array $todos = [\n        [ 'id' => 1, 'text' => 'Complete project documentation', 'completed' => false ],\n        [ 'id' => 2, 'text' => 'Review pull requests', 'completed' => false ],\n        [ 'id' => 3, 'text' => 'Update dependencies', 'completed' => true ],\n        [ 'id' => 4, 'text' => 'Deploy to production', 'completed' => false ],\n        [ 'id' => 5, 'text' => 'Send team updates', 'completed' => false ],\n    ];\n\n    /**\n     * Handle the reorder event from the drag-and-drop functionality.\n     *\n     * This method is called when items are reordered via drag-and-drop,\n     * either through mouse/touch interaction or keyboard navigation.\n     *\n     * @since 1.0.0\n     *\n     * @param int $oldIndex The original position of the item.\n     * @param int $newIndex The new position of the item.\n     * @return void\n     */\n    #[On('reorder-todos')]\n    public function reorderTodos( int $oldIndex, int $newIndex ): void\n    {\n        // Validate indices\n        if ( $oldIndex < 0 || $oldIndex >= count( $this->todos ) || $newIndex < 0 || $newIndex >= count( $this->todos ) ) {\n            return;\n        }\n\n        // Remove item from old position\n        $item = array_splice( $this->todos, $oldIndex, 1 )[0];\n\n        // Insert item at new position\n        array_splice( $this->todos, $newIndex, 0, [ $item ] );\n\n        // Re-index the array to ensure consistency\n        $this->todos = array_values( $this->todos );\n\n        // Optional: Persist to database\n        // $this->saveTodoOrder();\n\n        // Announce change for screen readers\n        $this->dispatch( 'todo-reordered', [\n            'message' => sprintf(\n                'Todo item \"%s\" moved from position %d to position %d',\n                $item['text'],\n                $oldIndex + 1,\n                $newIndex + 1\n            ),\n        ] );\n    }\n\n    /**\n     * Toggle the completion status of a todo item.\n     *\n     * @since 1.0.0\n     *\n     * @param int $index The index of the todo item to toggle.\n     * @return void\n     */\n    public function toggleTodo( int $index ): void\n    {\n        if ( isset( $this->todos[ $index ] ) ) {\n            $this->todos[ $index ]['completed'] = ! $this->todos[ $index ]['completed'];\n            \n            // Optional: Persist to database\n            // $this->saveTodo( $this->todos[ $index ] );\n        }\n    }\n\n    /**\n     * Add a new todo item.\n     *\n     * @since 1.0.0\n     *\n     * @param string $text The text content of the new todo.\n     * @return void\n     */\n    public function addTodo( string $text ): void\n    {\n        if ( empty( trim( $text ) ) ) {\n            return;\n        }\n\n        $newId = max( array_column( $this->todos, 'id' ) ) + 1;\n        $this->todos[] = [\n            'id'        => $newId,\n            'text'      => trim( $text ),\n            'completed' => false,\n        ];\n\n        // Optional: Persist to database\n        // $this->saveTodo( end( $this->todos ) );\n    }\n\n    /**\n     * Remove a todo item.\n     *\n     * @since 1.0.0\n     *\n     * @param int $index The index of the todo item to remove.\n     * @return void\n     */\n    public function removeTodo( int $index ): void\n    {\n        if ( isset( $this->todos[ $index ] ) ) {\n            // Optional: Remove from database\n            // $this->deleteTodo( $this->todos[ $index ]['id'] );\n            \n            unset( $this->todos[ $index ] );\n            $this->todos = array_values( $this->todos );\n        }\n    }\n\n    /**\n     * Render the component.\n     *\n     * @since 1.0.0\n     *\n     * @return View The component view.\n     */\n    public function render(): View\n    {\n        return view( 'livewire.reorderable-list' );\n    }\n}\n```\n\n### Blade View (resources/views/livewire/reorderable-list.blade.php)\n\n```blade\n{{--\n/**\n * Reorderable List Component View\n *\n * Displays a drag-and-drop enabled todo list with full accessibility support.\n * Follows ArtisanPack UI styling conventions and WordPress Documentation Standards.\n *\n * @since 1.0.0\n */\n--}}\n\n<div class=\"reorderable-list-container\">\n    {{-- Component Header --}}\n    <div class=\"reorderable-list-header\">\n        <h2 class=\"reorderable-list-title\">\n            {{ __( 'My Todo List' ) }}\n        </h2>\n        <p class=\"reorderable-list-description\">\n            {{ __( 'Drag items to reorder, or use keyboard navigation (Space/Enter to grab, Arrow keys to move, Space/Enter to drop, Escape to cancel).' ) }}\n        </p>\n    </div>\n\n    {{-- Add New Todo Form --}}\n    <form wire:submit=\"addTodo($wire.newTodoText)\" class=\"add-todo-form\">\n        <div class=\"add-todo-input-group\">\n            <label for=\"newTodoText\" class=\"sr-only\">\n                {{ __( 'Add new todo item' ) }}\n            </label>\n            <input\n                type=\"text\"\n                id=\"newTodoText\"\n                wire:model=\"newTodoText\"\n                placeholder=\"{{ __( 'Add a new todo item...' ) }}\"\n                class=\"add-todo-input\"\n                maxlength=\"255\"\n                required\n            >\n            <button type=\"submit\" class=\"add-todo-button\">\n                <span class=\"sr-only\">{{ __( 'Add todo' ) }}</span>\n                <svg class=\"add-todo-icon\" aria-hidden=\"true\" viewBox=\"0 0 24 24\">\n                    <path d=\"M12 4v16m8-8H4\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n                </svg>\n            </button>\n        </div>\n    </form>\n\n    {{-- Drag and Drop Todo List --}}\n    <div \n        x-data=\"{\n            handleReorder: function(event) {\n                const { oldIndex, newIndex } = event.detail;\n                if (oldIndex !== newIndex) {\n                    $wire.reorderTodos(oldIndex, newIndex);\n                }\n            }\n        }\"\n        x-drag-context=\"handleReorder($event.detail)\"\n        @drag:end=\"handleReorder($event)\"\n        class=\"todo-list\"\n        role=\"list\"\n        aria-label=\"{{ __( 'Draggable todo list' ) }}\"\n    >\n        @forelse ( $todos as $index => $todo )\n            <div\n                x-drag-item=\"{{ json_encode( [ 'id' => $todo['id'], 'index' => $index, 'text' => $todo['text'] ] ) }}\"\n                class=\"todo-item {{ $todo['completed'] ? 'todo-item--completed' : '' }}\"\n                role=\"listitem\"\n                aria-describedby=\"todo-{{ $todo['id'] }}-description\"\n            >\n                {{-- Todo Content --}}\n                <div class=\"todo-item-content\">\n                    {{-- Drag Handle --}}\n                    <div class=\"todo-drag-handle\" aria-hidden=\"true\">\n                        <svg class=\"drag-handle-icon\" viewBox=\"0 0 24 24\">\n                            <path d=\"M10 4h4v2h-4V4zm0 7h4v2h-4v-2zm0 7h4v2h-4v-2z\" fill=\"currentColor\"/>\n                        </svg>\n                    </div>\n\n                    {{-- Todo Checkbox --}}\n                    <div class=\"todo-checkbox-container\">\n                        <input\n                            type=\"checkbox\"\n                            id=\"todo-{{ $todo['id'] }}\"\n                            wire:click=\"toggleTodo({{ $index }})\"\n                            {{ $todo['completed'] ? 'checked' : '' }}\n                            class=\"todo-checkbox\"\n                            aria-describedby=\"todo-{{ $todo['id'] }}-description\"\n                        >\n                        <label for=\"todo-{{ $todo['id'] }}\" class=\"todo-checkbox-label\">\n                            <span class=\"sr-only\">\n                                {{ $todo['completed'] ? __( 'Mark as incomplete' ) : __( 'Mark as complete' ) }}\n                            </span>\n                        </label>\n                    </div>\n\n                    {{-- Todo Text --}}\n                    <div class=\"todo-text-container\">\n                        <span \n                            class=\"todo-text {{ $todo['completed'] ? 'todo-text--completed' : '' }}\"\n                            id=\"todo-{{ $todo['id'] }}-description\"\n                        >\n                            {{ $todo['text'] }}\n                        </span>\n                        @if ( $todo['completed'] )\n                            <span class=\"sr-only\">{{ __( '(completed)' ) }}</span>\n                        @endif\n                    </div>\n\n                    {{-- Todo Actions --}}\n                    <div class=\"todo-actions\">\n                        <button\n                            type=\"button\"\n                            wire:click=\"removeTodo({{ $index }})\"\n                            class=\"todo-remove-button\"\n                            aria-label=\"{{ __( 'Remove todo: :todo', [ 'todo' => $todo['text'] ] ) }}\"\n                        >\n                            <svg class=\"remove-icon\" aria-hidden=\"true\" viewBox=\"0 0 24 24\">\n                                <path d=\"M6 18L18 6M6 6l12 12\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n                            </svg>\n                        </button>\n                    </div>\n                </div>\n\n                {{-- Drag Instructions (Screen Reader Only) --}}\n                <div class=\"sr-only\">\n                    {{ __( 'Use Space or Enter to grab this item, arrow keys to move, Space or Enter to drop, Escape to cancel.' ) }}\n                </div>\n            </div>\n        @empty\n            <div class=\"empty-state\">\n                <p class=\"empty-state-message\">\n                    {{ __( 'No todos yet. Add one above to get started!' ) }}\n                </p>\n            </div>\n        @endforelse\n    </div>\n\n    {{-- Live Region for Announcements --}}\n    <div \n        x-data\n        @todo-reordered.window=\"$el.textContent = $event.detail.message\"\n        class=\"sr-only\" \n        aria-live=\"polite\" \n        aria-atomic=\"true\"\n    ></div>\n</div>\n\n{{-- Component Styles --}}\n<style>\n.reorderable-list-container {\n    max-width: 600px;\n    margin: 0 auto;\n    padding: 1.5rem;\n    background: #ffffff;\n    border-radius: 0.5rem;\n    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n}\n\n.reorderable-list-header {\n    margin-bottom: 1.5rem;\n    text-align: center;\n}\n\n.reorderable-list-title {\n    font-size: 1.5rem;\n    font-weight: 600;\n    color: #1f2937;\n    margin-bottom: 0.5rem;\n}\n\n.reorderable-list-description {\n    color: #6b7280;\n    font-size: 0.875rem;\n    line-height: 1.5;\n}\n\n.add-todo-form {\n    margin-bottom: 1.5rem;\n}\n\n.add-todo-input-group {\n    display: flex;\n    gap: 0.5rem;\n}\n\n.add-todo-input {\n    flex: 1;\n    padding: 0.75rem;\n    border: 1px solid #d1d5db;\n    border-radius: 0.375rem;\n    font-size: 0.875rem;\n    transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n.add-todo-input:focus {\n    outline: none;\n    border-color: #3b82f6;\n    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n}\n\n.add-todo-button {\n    padding: 0.75rem;\n    background: #3b82f6;\n    border: none;\n    border-radius: 0.375rem;\n    color: white;\n    cursor: pointer;\n    transition: background-color 0.15s ease-in-out;\n}\n\n.add-todo-button:hover {\n    background: #2563eb;\n}\n\n.add-todo-button:focus {\n    outline: none;\n    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5);\n}\n\n.add-todo-icon {\n    width: 1.25rem;\n    height: 1.25rem;\n}\n\n.todo-list {\n    border: 1px solid #e5e7eb;\n    border-radius: 0.375rem;\n    overflow: hidden;\n}\n\n.todo-item {\n    background: #ffffff;\n    border-bottom: 1px solid #e5e7eb;\n    transition: all 0.15s ease-in-out;\n    cursor: grab;\n}\n\n.todo-item:last-child {\n    border-bottom: none;\n}\n\n.todo-item:hover {\n    background: #f9fafb;\n}\n\n.todo-item:focus-within {\n    background: #f0f9ff;\n    outline: 2px solid #3b82f6;\n    outline-offset: -2px;\n}\n\n.todo-item--completed {\n    background: #f3f4f6;\n    opacity: 0.8;\n}\n\n.todo-item-content {\n    display: flex;\n    align-items: center;\n    gap: 0.75rem;\n    padding: 1rem;\n}\n\n.todo-drag-handle {\n    color: #9ca3af;\n    cursor: grab;\n}\n\n.drag-handle-icon {\n    width: 1.25rem;\n    height: 1.25rem;\n}\n\n.todo-checkbox-container {\n    display: flex;\n    align-items: center;\n}\n\n.todo-checkbox {\n    width: 1.125rem;\n    height: 1.125rem;\n    border: 2px solid #d1d5db;\n    border-radius: 0.25rem;\n    cursor: pointer;\n}\n\n.todo-checkbox:checked {\n    background: #3b82f6;\n    border-color: #3b82f6;\n}\n\n.todo-checkbox:focus {\n    outline: none;\n    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);\n}\n\n.todo-text-container {\n    flex: 1;\n}\n\n.todo-text {\n    font-size: 0.875rem;\n    color: #374151;\n    line-height: 1.5;\n    word-wrap: break-word;\n}\n\n.todo-text--completed {\n    text-decoration: line-through;\n    color: #6b7280;\n}\n\n.todo-actions {\n    display: flex;\n    gap: 0.5rem;\n}\n\n.todo-remove-button {\n    padding: 0.25rem;\n    background: transparent;\n    border: none;\n    color: #ef4444;\n    cursor: pointer;\n    border-radius: 0.25rem;\n    transition: background-color 0.15s ease-in-out;\n}\n\n.todo-remove-button:hover {\n    background: #fef2f2;\n}\n\n.todo-remove-button:focus {\n    outline: none;\n    box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.5);\n}\n\n.remove-icon {\n    width: 1rem;\n    height: 1rem;\n}\n\n.empty-state {\n    padding: 3rem 1rem;\n    text-align: center;\n}\n\n.empty-state-message {\n    color: #6b7280;\n    font-style: italic;\n}\n</style>\n```\n\n## Example 2: Robust Post Reordering\n\nThis example demonstrates a more complete and realistic usage that matches the event payload dispatched by the drag-and-drop component. It shows proper database integration and more robust error handling.\n\n### Livewire Component (ReorderablePosts.php)\n\n```php\n<?php\n\nnamespace App\\Livewire;\n\nuse App\\Models\\Post;\nuse Livewire\\Component;\nuse Livewire\\Attributes\\On;\n\nclass ReorderablePosts extends Component\n{\n    public $posts;\n\n    public function mount(): void\n    {\n        $this->posts = Post::orderBy( 'position' )->get();\n    }\n\n    /**\n     * Updates the order of posts after a drag-and-drop operation.\n     *\n     * @since 1.0.0\n     *\n     * @param array $payload The event payload from the drag-and-drop component.\n     * Contains 'oldIndex', 'newIndex', etc.\n     * @return void\n     */\n    #[On( 'drag:end' )]\n    public function updateOrder( array $payload ): void\n    {\n        $postsArray = $this->posts->toArray();\n        $movedItem  = array_splice( $postsArray, $payload['oldIndex'], 1 )[0];\n        array_splice( $postsArray, $payload['newIndex'], 0, [$movedItem] );\n\n        // Update the position for each post in the database.\n        foreach ( $postsArray as $index => $post ) {\n            Post::find( $post['id'] )->update( [ 'position' => $index ] );\n        }\n\n        // Refresh the component's data.\n        $this->posts = collect( $postsArray );\n    }\n\n    public function render()\n    {\n        return view( 'livewire.reorderable-posts' );\n    }\n}\n```\n\n### Blade View (reorderable-posts.blade.php)\n\n```blade\n<div\n    {{-- Pass the Livewire data to the context for accurate index tracking --}}\n    x-drag-context=\"$wire.posts\"\n    class=\"space-y-2\"\n>\n    @foreach ( $posts as $post )\n        <div\n            wire:key=\"post-{{ $post->id }}\"\n            x-drag-item\n            class=\"p-4 bg-white border rounded-md shadow cursor-grab\"\n        >\n            {{ $post->title }}\n        </div>\n    @endforeach\n</div>\n```\n\n## Example 3: Advanced Kanban Board\n\nThis example demonstrates a sophisticated kanban board with multiple columns and cross-column drag-and-drop functionality.\n\n### Livewire Component (KanbanBoard.php)\n\n```php\n<?php\n\ndeclare( strict_types=1 );\n\nnamespace App\\Livewire;\n\nuse Illuminate\\Contracts\\View\\View;\nuse Livewire\\Attributes\\On;\nuse Livewire\\Component;\n\n/**\n * Kanban Board Component\n *\n * An advanced kanban board component with multiple columns and cross-column\n * drag-and-drop functionality. Demonstrates complex state management and\n * accessibility features.\n *\n * @since 1.0.0\n */\nclass KanbanBoard extends Component\n{\n    /**\n     * Kanban board columns with their tasks.\n     *\n     * @since 1.0.0\n     * @var array<string, array<string, mixed>>\n     */\n    public array $columns = [\n        'todo' => [\n            'title' => 'To Do',\n            'color' => 'blue',\n            'tasks' => [\n                [ 'id' => 1, 'title' => 'Design new homepage', 'priority' => 'high', 'assignee' => 'John Doe' ],\n                [ 'id' => 2, 'title' => 'Write API documentation', 'priority' => 'medium', 'assignee' => 'Jane Smith' ],\n                [ 'id' => 3, 'title' => 'Setup CI/CD pipeline', 'priority' => 'high', 'assignee' => 'Mike Johnson' ],\n            ],\n        ],\n        'progress' => [\n            'title' => 'In Progress',\n            'color' => 'yellow',\n            'tasks' => [\n                [ 'id' => 4, 'title' => 'Implement user authentication', 'priority' => 'high', 'assignee' => 'Sarah Wilson' ],\n                [ 'id' => 5, 'title' => 'Create dashboard mockups', 'priority' => 'medium', 'assignee' => 'John Doe' ],\n            ],\n        ],\n        'review' => [\n            'title' => 'Review',\n            'color' => 'purple',\n            'tasks' => [\n                [ 'id' => 6, 'title' => 'Test payment integration', 'priority' => 'high', 'assignee' => 'Mike Johnson' ],\n            ],\n        ],\n        'done' => [\n            'title' => 'Done',\n            'color' => 'green',\n            'tasks' => [\n                [ 'id' => 7, 'title' => 'Setup development environment', 'priority' => 'low', 'assignee' => 'Jane Smith' ],\n                [ 'id' => 8, 'title' => 'Create project wireframes', 'priority' => 'medium', 'assignee' => 'Sarah Wilson' ],\n            ],\n        ],\n    ];\n\n    /**\n     * Handle task reordering within the same column.\n     *\n     * @since 1.0.0\n     *\n     * @param string $columnId The column identifier.\n     * @param int    $oldIndex The original position of the task.\n     * @param int    $newIndex The new position of the task.\n     * @return void\n     */\n    #[On('reorder-tasks')]\n    public function reorderTasks( string $columnId, int $oldIndex, int $newIndex ): void\n    {\n        if ( ! isset( $this->columns[ $columnId ] ) ) {\n            return;\n        }\n\n        $tasks = $this->columns[ $columnId ]['tasks'];\n\n        // Validate indices\n        if ( $oldIndex < 0 || $oldIndex >= count( $tasks ) || $newIndex < 0 || $newIndex >= count( $tasks ) ) {\n            return;\n        }\n\n        // Remove task from old position\n        $task = array_splice( $tasks, $oldIndex, 1 )[0];\n\n        // Insert task at new position\n        array_splice( $tasks, $newIndex, 0, [ $task ] );\n\n        // Update the column tasks\n        $this->columns[ $columnId ]['tasks'] = array_values( $tasks );\n\n        // Optional: Persist to database\n        // $this->saveTaskOrder( $columnId, $this->columns[ $columnId ]['tasks'] );\n\n        // Announce change for screen readers\n        $this->dispatch( 'task-reordered', [\n            'message' => sprintf(\n                'Task \"%s\" moved from position %d to position %d in %s column',\n                $task['title'],\n                $oldIndex + 1,\n                $newIndex + 1,\n                $this->columns[ $columnId ]['title']\n            ),\n        ] );\n    }\n\n    /**\n     * Handle task movement between columns.\n     *\n     * @since 1.0.0\n     *\n     * @param int    $taskId        The ID of the task being moved.\n     * @param string $sourceColumn  The source column identifier.\n     * @param string $targetColumn  The target column identifier.\n     * @param int    $targetIndex   The target position within the target column.\n     * @return void\n     */\n    #[On('move-task')]\n    public function moveTask( int $taskId, string $sourceColumn, string $targetColumn, int $targetIndex ): void\n    {\n        // Validate columns exist\n        if ( ! isset( $this->columns[ $sourceColumn ] ) || ! isset( $this->columns[ $targetColumn ] ) ) {\n            return;\n        }\n\n        // Find and remove task from source column\n        $sourceTasks = $this->columns[ $sourceColumn ]['tasks'];\n        $taskIndex   = array_search( $taskId, array_column( $sourceTasks, 'id' ), true );\n\n        if ( $taskIndex === false ) {\n            return;\n        }\n\n        $task = array_splice( $sourceTasks, $taskIndex, 1 )[0];\n        $this->columns[ $sourceColumn ]['tasks'] = array_values( $sourceTasks );\n\n        // Add task to target column\n        $targetTasks = $this->columns[ $targetColumn ]['tasks'];\n        $targetIndex = max( 0, min( $targetIndex, count( $targetTasks ) ) );\n        array_splice( $targetTasks, $targetIndex, 0, [ $task ] );\n        $this->columns[ $targetColumn ]['tasks'] = array_values( $targetTasks );\n\n        // Optional: Persist to database\n        // $this->updateTaskColumn( $taskId, $targetColumn, $targetIndex );\n\n        // Announce change for screen readers\n        $this->dispatch( 'task-moved', [\n            'message' => sprintf(\n                'Task \"%s\" moved from %s to %s column',\n                $task['title'],\n                $this->columns[ $sourceColumn ]['title'],\n                $this->columns[ $targetColumn ]['title']\n            ),\n        ] );\n    }\n\n    /**\n     * Add a new task to a specific column.\n     *\n     * @since 1.0.0\n     *\n     * @param string $columnId The column identifier.\n     * @param string $title    The task title.\n     * @param string $priority The task priority.\n     * @param string $assignee The task assignee.\n     * @return void\n     */\n    public function addTask( string $columnId, string $title, string $priority = 'medium', string $assignee = '' ): void\n    {\n        if ( ! isset( $this->columns[ $columnId ] ) || empty( trim( $title ) ) ) {\n            return;\n        }\n\n        // Generate new task ID\n        $allTasks = [];\n        foreach ( $this->columns as $column ) {\n            $allTasks = array_merge( $allTasks, $column['tasks'] );\n        }\n        $newId = empty( $allTasks ) ? 1 : max( array_column( $allTasks, 'id' ) ) + 1;\n\n        // Create new task\n        $newTask = [\n            'id'       => $newId,\n            'title'    => trim( $title ),\n            'priority' => $priority,\n            'assignee' => trim( $assignee ),\n        ];\n\n        // Add to column\n        $this->columns[ $columnId ]['tasks'][] = $newTask;\n\n        // Optional: Persist to database\n        // $this->saveTask( $newTask, $columnId );\n\n        // Announce addition\n        $this->dispatch( 'task-added', [\n            'message' => sprintf(\n                'New task \"%s\" added to %s column',\n                $newTask['title'],\n                $this->columns[ $columnId ]['title']\n            ),\n        ] );\n    }\n\n    /**\n     * Remove a task from the board.\n     *\n     * @since 1.0.0\n     *\n     * @param int    $taskId   The task ID.\n     * @param string $columnId The column identifier.\n     * @return void\n     */\n    public function removeTask( int $taskId, string $columnId ): void\n    {\n        if ( ! isset( $this->columns[ $columnId ] ) ) {\n            return;\n        }\n\n        $tasks     = $this->columns[ $columnId ]['tasks'];\n        $taskIndex = array_search( $taskId, array_column( $tasks, 'id' ), true );\n\n        if ( $taskIndex !== false ) {\n            $task = $tasks[ $taskIndex ];\n            unset( $tasks[ $taskIndex ] );\n            $this->columns[ $columnId ]['tasks'] = array_values( $tasks );\n\n            // Optional: Remove from database\n            // $this->deleteTask( $taskId );\n\n            // Announce removal\n            $this->dispatch( 'task-removed', [\n                'message' => sprintf(\n                    'Task \"%s\" removed from %s column',\n                    $task['title'],\n                    $this->columns[ $columnId ]['title']\n                ),\n            ] );\n        }\n    }\n\n    /**\n     * Update a task's details.\n     *\n     * @since 1.0.0\n     *\n     * @param int    $taskId   The task ID.\n     * @param string $columnId The column identifier.\n     * @param array  $updates  The fields to update.\n     * @return void\n     */\n    public function updateTask( int $taskId, string $columnId, array $updates ): void\n    {\n        if ( ! isset( $this->columns[ $columnId ] ) ) {\n            return;\n        }\n\n        $tasks     = &$this->columns[ $columnId ]['tasks'];\n        $taskIndex = array_search( $taskId, array_column( $tasks, 'id' ), true );\n\n        if ( $taskIndex !== false ) {\n            foreach ( $updates as $field => $value ) {\n                if ( in_array( $field, [ 'title', 'priority', 'assignee' ], true ) ) {\n                    $tasks[ $taskIndex ][ $field ] = $value;\n                }\n            }\n\n            // Optional: Persist to database\n            // $this->saveTask( $tasks[ $taskIndex ], $columnId );\n        }\n    }\n\n    /**\n     * Get task count for a column.\n     *\n     * @since 1.0.0\n     *\n     * @param string $columnId The column identifier.\n     * @return int The number of tasks in the column.\n     */\n    public function getTaskCount( string $columnId ): int\n    {\n        return isset( $this->columns[ $columnId ] ) ? count( $this->columns[ $columnId ]['tasks'] ) : 0;\n    }\n\n    /**\n     * Get total task count across all columns.\n     *\n     * @since 1.0.0\n     *\n     * @return int The total number of tasks.\n     */\n    public function getTotalTaskCount(): int\n    {\n        $total = 0;\n        foreach ( $this->columns as $column ) {\n            $total += count( $column['tasks'] );\n        }\n        return $total;\n    }\n\n    /**\n     * Render the component.\n     *\n     * @since 1.0.0\n     *\n     * @return View The component view.\n     */\n    public function render(): View\n    {\n        return view( 'livewire.kanban-board' );\n    }\n}\n```\n\n### Blade View (resources/views/livewire/kanban-board.blade.php)\n\n```blade\n{{--\n/**\n * Kanban Board Component View\n *\n * Advanced kanban board with multiple columns and cross-column drag-and-drop.\n * Features sophisticated task management and accessibility support.\n * Follows ArtisanPack UI styling conventions and WordPress Documentation Standards.\n *\n * @since 1.0.0\n */\n--}}\n\n<div class=\"kanban-board-container\">\n    {{-- Board Header --}}\n    <div class=\"kanban-board-header\">\n        <h2 class=\"kanban-board-title\">\n            {{ __( 'Project Kanban Board' ) }}\n        </h2>\n        <div class=\"kanban-board-stats\">\n            <span class=\"kanban-stat\">\n                {{ __( 'Total Tasks: :count', [ 'count' => $this->getTotalTaskCount() ] ) }}\n            </span>\n        </div>\n        <p class=\"kanban-board-description\">\n            {{ __( 'Drag tasks between columns to update their status. Use keyboard navigation for accessibility.' ) }}\n        </p>\n    </div>\n\n    {{-- Kanban Columns --}}\n    <div class=\"kanban-columns-container\">\n        @foreach ( $columns as $columnId => $column )\n            <div class=\"kanban-column\" data-column=\"{{ $columnId }}\">\n                {{-- Column Header --}}\n                <div class=\"kanban-column-header kanban-column-header--{{ $column['color'] }}\">\n                    <h3 class=\"kanban-column-title\">\n                        {{ $column['title'] }}\n                        <span class=\"kanban-column-count\">{{ count( $column['tasks'] ) }}</span>\n                    </h3>\n                    \n                    {{-- Add Task Button --}}\n                    <button \n                        type=\"button\"\n                        x-data\n                        @click=\"$dispatch('open-add-task-modal', { columnId: '{{ $columnId }}' })\"\n                        class=\"kanban-add-task-btn\"\n                        aria-label=\"{{ __( 'Add new task to :column', [ 'column' => $column['title'] ] ) }}\"\n                    >\n                        <svg class=\"kanban-add-icon\" aria-hidden=\"true\" viewBox=\"0 0 24 24\">\n                            <path d=\"M12 4v16m8-8H4\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n                        </svg>\n                    </button>\n                </div>\n\n                {{-- Tasks Container --}}\n                <div \n                    x-data=\"{\n                        handleReorder: function(event) {\n                            const { oldIndex, newIndex } = event.detail;\n                            if (oldIndex !== newIndex) {\n                                $wire.reorderTasks('{{ $columnId }}', oldIndex, newIndex);\n                            }\n                        },\n                        handleCrossColumnDrop: function(event) {\n                            const draggedData = JSON.parse(event.dataTransfer.getData('text/plain'));\n                            const sourceColumn = draggedData.sourceColumn;\n                            const taskId = draggedData.taskId;\n                            \n                            if (sourceColumn !== '{{ $columnId }}') {\n                                // Calculate target index based on drop position\n                                const tasks = Array.from(event.currentTarget.querySelectorAll('[x-drag-item]'));\n                                const dropY = event.clientY;\n                                let targetIndex = tasks.length;\n                                \n                                for (let i = 0; i < tasks.length; i++) {\n                                    const rect = tasks[i].getBoundingClientRect();\n                                    if (dropY < rect.top + rect.height / 2) {\n                                        targetIndex = i;\n                                        break;\n                                    }\n                                }\n                                \n                                $wire.moveTask(taskId, sourceColumn, '{{ $columnId }}', targetIndex);\n                            }\n                        }\n                    }\"\n                    x-drag-context=\"handleReorder($event.detail)\"\n                    @drag:end=\"handleReorder($event)\"\n                    @dragover.prevent=\"$event.dataTransfer.dropEffect = 'move'\"\n                    @drop=\"handleCrossColumnDrop($event)\"\n                    class=\"kanban-tasks-container\"\n                    role=\"list\"\n                    aria-label=\"{{ __( 'Tasks in :column column', [ 'column' => $column['title'] ] ) }}\"\n                >\n                    @forelse ( $column['tasks'] as $index => $task )\n                        <div\n                            x-drag-item=\"{{ json_encode( [ \n                                'taskId' => $task['id'], \n                                'index' => $index, \n                                'title' => $task['title'],\n                                'sourceColumn' => $columnId\n                            ] ) }}\"\n                            @dragstart=\"$event.dataTransfer.setData('text/plain', JSON.stringify({\n                                taskId: {{ $task['id'] }},\n                                sourceColumn: '{{ $columnId }}',\n                                title: '{{ addslashes( $task['title'] ) }}'\n                            }))\"\n                            class=\"kanban-task-card kanban-task-card--{{ $task['priority'] }}\"\n                            role=\"listitem\"\n                            aria-describedby=\"task-{{ $task['id'] }}-details\"\n                        >\n                            {{-- Task Header --}}\n                            <div class=\"kanban-task-header\">\n                                {{-- Drag Handle --}}\n                                <div class=\"kanban-task-drag-handle\" aria-hidden=\"true\">\n                                    <svg class=\"drag-handle-icon\" viewBox=\"0 0 24 24\">\n                                        <path d=\"M10 4h4v2h-4V4zm0 7h4v2h-4v-2zm0 7h4v2h-4v-2z\" fill=\"currentColor\"/>\n                                    </svg>\n                                </div>\n\n                                {{-- Priority Badge --}}\n                                <span class=\"kanban-task-priority kanban-task-priority--{{ $task['priority'] }}\">\n                                    {{ ucfirst( $task['priority'] ) }}\n                                </span>\n\n                                {{-- Task Actions Dropdown --}}\n                                <div class=\"kanban-task-actions\" x-data=\"{ open: false }\">\n                                    <button \n                                        @click=\"open = !open\"\n                                        @click.away=\"open = false\"\n                                        class=\"kanban-task-menu-btn\"\n                                        aria-expanded=\"false\"\n                                        :aria-expanded=\"open\"\n                                        aria-label=\"{{ __( 'Task actions for :title', [ 'title' => $task['title'] ] ) }}\"\n                                    >\n                                        <svg class=\"kanban-menu-icon\" viewBox=\"0 0 24 24\">\n                                            <path d=\"M12 8v4m0 4h.01M12 2a10 10 0 100 20 10 10 0 000-20z\" stroke=\"currentColor\" stroke-width=\"2\"/>\n                                        </svg>\n                                    </button>\n\n                                    <div \n                                        x-show=\"open\"\n                                        x-transition:enter=\"transition ease-out duration-100\"\n                                        x-transition:enter-start=\"transform opacity-0 scale-95\"\n                                        x-transition:enter-end=\"transform opacity-100 scale-100\"\n                                        x-transition:leave=\"transition ease-in duration-75\"\n                                        x-transition:leave-start=\"transform opacity-100 scale-100\"\n                                        x-transition:leave-end=\"transform opacity-0 scale-95\"\n                                        class=\"kanban-task-menu\"\n                                        x-cloak\n                                    >\n                                        <button \n                                            @click=\"$dispatch('edit-task', { taskId: {{ $task['id'] }}, columnId: '{{ $columnId }}' }); open = false\"\n                                            class=\"kanban-menu-item\"\n                                        >\n                                            {{ __( 'Edit Task' ) }}\n                                        </button>\n                                        <button \n                                            @click=\"$wire.removeTask({{ $task['id'] }}, '{{ $columnId }}'); open = false\"\n                                            class=\"kanban-menu-item kanban-menu-item--danger\"\n                                        >\n                                            {{ __( 'Delete Task' ) }}\n                                        </button>\n                                    </div>\n                                </div>\n                            </div>\n\n                            {{-- Task Content --}}\n                            <div class=\"kanban-task-content\">\n                                <h4 class=\"kanban-task-title\" id=\"task-{{ $task['id'] }}-details\">\n                                    {{ $task['title'] }}\n                                </h4>\n                                \n                                @if ( ! empty( $task['assignee'] ) )\n                                    <div class=\"kanban-task-assignee\">\n                                        <span class=\"kanban-assignee-label\">{{ __( 'Assigned to:' ) }}</span>\n                                        <span class=\"kanban-assignee-name\">{{ $task['assignee'] }}</span>\n                                    </div>\n                                @endif\n                            </div>\n\n                            {{-- Screen Reader Instructions --}}\n                            <div class=\"sr-only\">\n                                {{ __( 'Priority: :priority. Use Space or Enter to grab, arrow keys to move within column, or drag to other columns. Space or Enter to drop, Escape to cancel.', [ 'priority' => $task['priority'] ] ) }}\n                            </div>\n                        </div>\n                    @empty\n                        <div class=\"kanban-empty-column\">\n                            <p class=\"kanban-empty-message\">\n                                {{ __( 'No tasks in :column yet', [ 'column' => $column['title'] ] ) }}\n                            </p>\n                            <button \n                                type=\"button\"\n                                x-data\n                                @click=\"$dispatch('open-add-task-modal', { columnId: '{{ $columnId }}' })\"\n                                class=\"kanban-empty-add-btn\"\n                            >\n                                {{ __( 'Add First Task' ) }}\n                            </button>\n                        </div>\n                    @endforelse\n                </div>\n            </div>\n        @endforeach\n    </div>\n\n    {{-- Live Region for Announcements --}}\n    <div \n        x-data\n        @task-reordered.window=\"$el.textContent = $event.detail.message\"\n        @task-moved.window=\"$el.textContent = $event.detail.message\"\n        @task-added.window=\"$el.textContent = $event.detail.message\"\n        @task-removed.window=\"$el.textContent = $event.detail.message\"\n        class=\"sr-only\" \n        aria-live=\"polite\" \n        aria-atomic=\"true\"\n    ></div>\n</div>\n\n{{-- Component Styles --}}\n<style>\n.kanban-board-container {\n    padding: 1.5rem;\n    background: #f8fafc;\n    min-height: 100vh;\n}\n\n.kanban-board-header {\n    margin-bottom: 2rem;\n    text-align: center;\n}\n\n.kanban-board-title {\n    font-size: 2rem;\n    font-weight: 700;\n    color: #1f2937;\n    margin-bottom: 0.5rem;\n}\n\n.kanban-board-stats {\n    margin-bottom: 0.75rem;\n}\n\n.kanban-stat {\n    background: #e5e7eb;\n    padding: 0.375rem 0.75rem;\n    border-radius: 0.375rem;\n    font-size: 0.875rem;\n    font-weight: 500;\n    color: #4b5563;\n}\n\n.kanban-board-description {\n    color: #6b7280;\n    font-size: 0.875rem;\n    max-width: 600px;\n    margin: 0 auto;\n    line-height: 1.5;\n}\n\n.kanban-columns-container {\n    display: flex;\n    gap: 1.5rem;\n    overflow-x: auto;\n    padding-bottom: 1rem;\n}\n\n.kanban-column {\n    flex: 0 0 300px;\n    background: #ffffff;\n    border-radius: 0.5rem;\n    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n    display: flex;\n    flex-direction: column;\n    max-height: 80vh;\n}\n\n.kanban-column-header {\n    padding: 1rem;\n    border-radius: 0.5rem 0.5rem 0 0;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n}\n\n.kanban-column-header--blue { background: #dbeafe; }\n.kanban-column-header--yellow { background: #fef3c7; }\n.kanban-column-header--purple { background: #ede9fe; }\n.kanban-column-header--green { background: #d1fae5; }\n\n.kanban-column-title {\n    font-size: 1.125rem;\n    font-weight: 600;\n    color: #374151;\n    display: flex;\n    align-items: center;\n    gap: 0.5rem;\n}\n\n.kanban-column-count {\n    background: rgba(255, 255, 255, 0.8);\n    padding: 0.125rem 0.5rem;\n    border-radius: 9999px;\n    font-size: 0.75rem;\n    font-weight: 500;\n}\n\n.kanban-add-task-btn {\n    padding: 0.375rem;\n    background: rgba(255, 255, 255, 0.8);\n    border: none;\n    border-radius: 0.375rem;\n    cursor: pointer;\n    transition: all 0.15s ease-in-out;\n}\n\n.kanban-add-task-btn:hover {\n    background: rgba(255, 255, 255, 1);\n    transform: scale(1.05);\n}\n\n.kanban-add-icon {\n    width: 1rem;\n    height: 1rem;\n    color: #4b5563;\n}\n\n.kanban-tasks-container {\n    flex: 1;\n    padding: 0.75rem;\n    overflow-y: auto;\n    display: flex;\n    flex-direction: column;\n    gap: 0.75rem;\n}\n\n.kanban-task-card {\n    background: #ffffff;\n    border: 1px solid #e5e7eb;\n    border-radius: 0.375rem;\n    padding: 1rem;\n    cursor: grab;\n    transition: all 0.15s ease-in-out;\n    border-left: 4px solid;\n}\n\n.kanban-task-card--high { border-left-color: #ef4444; }\n.kanban-task-card--medium { border-left-color: #f59e0b; }\n.kanban-task-card--low { border-left-color: #10b981; }\n\n.kanban-task-card:hover {\n    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n    transform: translateY(-1px);\n}\n\n.kanban-task-card:focus-within {\n    outline: 2px solid #3b82f6;\n    outline-offset: 2px;\n}\n\n.kanban-task-header {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    margin-bottom: 0.75rem;\n}\n\n.kanban-task-drag-handle {\n    color: #9ca3af;\n    cursor: grab;\n}\n\n.kanban-task-priority {\n    padding: 0.125rem 0.5rem;\n    border-radius: 9999px;\n    font-size: 0.75rem;\n    font-weight: 500;\n    text-transform: uppercase;\n}\n\n.kanban-task-priority--high {\n    background: #fef2f2;\n    color: #dc2626;\n}\n\n.kanban-task-priority--medium {\n    background: #fffbeb;\n    color: #d97706;\n}\n\n.kanban-task-priority--low {\n    background: #ecfdf5;\n    color: #059669;\n}\n\n.kanban-task-actions {\n    position: relative;\n}\n\n.kanban-task-menu-btn {\n    padding: 0.25rem;\n    background: transparent;\n    border: none;\n    color: #6b7280;\n    cursor: pointer;\n    border-radius: 0.25rem;\n}\n\n.kanban-task-menu-btn:hover {\n    background: #f3f4f6;\n    color: #374151;\n}\n\n.kanban-menu-icon {\n    width: 1rem;\n    height: 1rem;\n}\n\n.kanban-task-menu {\n    position: absolute;\n    right: 0;\n    top: 100%;\n    background: #ffffff;\n    border: 1px solid #e5e7eb;\n    border-radius: 0.375rem;\n    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);\n    z-index: 50;\n    min-width: 120px;\n}\n\n.kanban-menu-item {\n    display: block;\n    width: 100%;\n    padding: 0.5rem 0.75rem;\n    background: none;\n    border: none;\n    text-align: left;\n    font-size: 0.875rem;\n    cursor: pointer;\n    transition: background-color 0.15s ease-in-out;\n}\n\n.kanban-menu-item:hover {\n    background: #f3f4f6;\n}\n\n.kanban-menu-item--danger {\n    color: #ef4444;\n}\n\n.kanban-menu-item--danger:hover {\n    background: #fef2f2;\n}\n\n.kanban-task-content {\n    margin-bottom: 0.5rem;\n}\n\n.kanban-task-title {\n    font-size: 0.875rem;\n    font-weight: 600;\n    color: #374151;\n    line-height: 1.4;\n    margin-bottom: 0.5rem;\n    word-wrap: break-word;\n}\n\n.kanban-task-assignee {\n    font-size: 0.75rem;\n    color: #6b7280;\n}\n\n.kanban-assignee-label {\n    font-weight: 500;\n}\n\n.kanban-assignee-name {\n    color: #374151;\n}\n\n.kanban-empty-column {\n    padding: 2rem 1rem;\n    text-align: center;\n    color: #6b7280;\n}\n\n.kanban-empty-message {\n    font-style: italic;\n    margin-bottom: 1rem;\n}\n\n.kanban-empty-add-btn {\n    background: #f3f4f6;\n    border: 2px dashed #d1d5db;\n    border-radius: 0.375rem;\n    padding: 0.75rem 1rem;\n    font-size: 0.875rem;\n    color: #4b5563;\n    cursor: pointer;\n    transition: all 0.15s ease-in-out;\n}\n\n.kanban-empty-add-btn:hover {\n    background: #e5e7eb;\n    border-color: #9ca3af;\n}\n\n.drag-handle-icon {\n    width: 1rem;\n    height: 1rem;\n}\n\n@media (max-width: 768px) {\n    .kanban-columns-container {\n        flex-direction: column;\n    }\n    \n    .kanban-column {\n        flex: none;\n        max-height: 400px;\n    }\n}\n</style>\n```\n\n## API Reference\n\n### Alpine.js Directives\n\n#### `x-drag-context`\n\nCreates a drag-and-drop context for a group of related items.\n\n**Usage:**\n```html\n<div x-drag-context=\"handleDrop($event)\" class=\"drag-container\">\n    <!-- draggable items -->\n</div>\n```\n\n**Parameters:**\n- `expression`: JavaScript expression to handle drop events\n- Receives `$event.detail` with: `{ group, oldIndex, newIndex, sourceElement, targetElement }`\n\n**Accessibility Features:**\n- Adds `role=\"application\"` and `aria-label=\"Drag and drop interface\"`\n- Creates persistent `aria-live` region for screen reader announcements\n- Supports keyboard navigation with Escape key to cancel operations\n\n#### `x-drag-item`\n\nDesignates an element as draggable within a drag context.\n\n**Usage:**\n```html\n<div x-drag-item=\"{ id: 1, text: 'Item 1' }\" class=\"drag-item\">\n    Item content\n</div>\n```\n\n**Parameters:**\n- `expression`: Data object associated with the draggable item\n\n**Accessibility Features:**\n- Adds `role=\"button\"`, `aria-grabbed=\"false\"`, and `draggable=\"true\"`\n- Supports full keyboard navigation:\n  - Space/Enter: Grab/release item\n  - Arrow keys: Move item within container\n  - Escape: Cancel drag operation\n- Provides screen reader instructions and announcements\n\n### Events\n\n#### `drag:end`\n\nFired when a drag operation completes successfully.\n\n**Event Detail:**\n```javascript\n{\n    orderedIds: []  // Array of item IDs in their new order\n}\n```\n\n**Usage:**\n```javascript\ndocument.addEventListener('drag:end', (event) => {\n    const { orderedIds } = event.detail;\n    // Handle reorder logic with the new order array\n    $wire.reorderItems(orderedIds);\n});\n```\n\n## Accessibility\n\nThis package is built with **WCAG 2.1 AA compliance** as a core principle:\n\n### Screen Reader Support\n- **ARIA Live Regions**: Automatic announcements for drag operations\n- **Semantic HTML**: Proper roles and labels for all interactive elements\n- **Clear Instructions**: Built-in guidance for keyboard users\n\n### Keyboard Navigation\n- **Space/Enter**: Grab and release items\n- **Arrow Keys**: Move items within containers\n- **Escape**: Cancel drag operations\n- **Tab**: Navigate between draggable items\n\n### Visual Indicators\n- **Focus Outlines**: Clear visual focus indicators\n- **Drag States**: Visual feedback during drag operations\n- **High Contrast**: Supports high contrast mode\n\n### Testing\nTested with:\n- **NVDA** (Windows)\n- **JAWS** (Windows) \n- **VoiceOver** (macOS/iOS)\n- **TalkBack** (Android)\n\n## Browser Support\n\n| Browser | Version | Support |\n|---------|---------|---------|\n| Chrome | 90+ | ✅ Full |\n| Firefox | 88+ | ✅ Full |\n| Safari | 14+ | ✅ Full |\n| Edge | 90+ | ✅ Full |\n| iOS Safari | 14+ | ✅ Full |\n| Chrome Android | 90+ | ✅ Full |\n\n### Required Features\n- **Drag and Drop API**: All modern browsers\n- **Alpine.js 3.x**: Required dependency\n- **ES6 Modules**: For optimal performance\n\n## Version 2.0.0 Migration Guide\n\n### Breaking Changes\n\nVersion 2.0.0 introduces significant architectural improvements while maintaining the same public API (`x-drag-context` and `x-drag-item` directives). However, there are important changes to be aware of:\n\n#### Event Structure Changes\n\n**Old (v1.0.0):**\n```javascript\ndocument.addEventListener('drag:end', (event) => {\n    const { group, oldIndex, newIndex, sourceElement, targetElement } = event.detail;\n    // Handle with index-based logic\n});\n```\n\n**New (v2.0.0):**\n```javascript\ndocument.addEventListener('drag:end', (event) => {\n    const { orderedIds } = event.detail;\n    // Handle with complete ordered array\n    $wire.reorderItems(orderedIds);\n});\n```\n\n#### Architectural Improvements\n\n- **Stateless Event Delegation**: The new architecture uses a global, stateless event delegation pattern that's immune to Livewire's DOM morphing\n- **Race Condition Fixes**: Resolved fundamental race conditions between Livewire's `message.processed` hook and Alpine.js's `initTree` function\n- **Automatic Rehydration**: New `forceRehydrateDraggableItems` function ensures all items remain draggable after Livewire updates\n- **Improved Stability**: Draggable items no longer become unresponsive after Livewire updates\n\n#### Migration Steps\n\n1. **Update Event Handlers**: Replace `oldIndex/newIndex` logic with `orderedIds` array handling\n2. **Backend Updates**: Modify your Livewire component methods to accept the complete ordered array:\n\n```php\n#[On('drag:end')]\npublic function reorderItems(array $orderedIds): void\n{\n    // Update your data structure with the new order\n    $this->items = collect($orderedIds)\n        ->map(fn($id) => $this->items->firstWhere('id', $id))\n        ->filter()\n        ->values()\n        ->toArray();\n}\n```\n\n3. **CSS Classes**: The `.is-grabbing` class behavior remains the same, no changes needed\n4. **No Directive Changes**: `x-drag-context` and `x-drag-item` usage remains identical\n\n### New Features in 2.0.0\n\n#### Automatic Rehydration Function\n\nThe new `forceRehydrateDraggableItems` function automatically ensures all draggable items maintain their functionality after Livewire updates. This function is called automatically by the package, but advanced users can access it if needed:\n\n```javascript\n// Available globally after package initialization\n// Called automatically after every Livewire update\nforceRehydrateDraggableItems(containerElement);\n```\n\n#### Enhanced Livewire Integration\n\nVersion 2.0.0 provides seamless integration with Livewire's DOM morphing through improved hooks:\n\n- **`morph.updating` Hook**: Temporarily ignores recently moved items during morphing\n- **`message.processed` Hook**: Automatically reinitializes contexts and rehydrates items\n- **Global State Management**: Maintains drag state across Livewire updates\n\n## Advanced Usage\n\n### Custom Event Handling\n\n```javascript\n// Multiple event listeners\ndocument.addEventListener('drag:end', (event) => {\n    const { orderedIds } = event.detail;\n    \n    // Handle reorder logic with the complete ordered array\n    $wire.reorderItems(orderedIds);\n});\n```\n\n### Cross-Container Drag and Drop\n\n```blade\n{{-- Source Container --}}\n<div x-drag-context=\"group='source'\" class=\"source-container\">\n    <div x-drag-item=\"{ id: 1, type: 'task' }\">Task 1</div>\n</div>\n\n{{-- Target Container --}}\n<div \n    x-drag-context=\"group='target'\"\n    @dragover.prevent\n    @drop=\"handleCrossContainerDrop($event)\"\n    class=\"target-container\"\n>\n    <!-- Drop zone -->\n</div>\n```\n\n### Custom Styling\n\n```css\n/* Custom drag states */\n.my-drag-item {\n    transition: all 0.2s ease;\n}\n\n.my-drag-item.is-grabbing {\n    background: #e3f2fd;\n    box-shadow: 0 8px 16px rgba(0,0,0,0.2);\n    transform: rotate(2deg) scale(1.05);\n}\n\n/* Custom focus styles */\n.my-drag-item:focus {\n    outline: 3px solid #2196f3;\n    outline-offset: 2px;\n}\n```\n\n## Laravel Integration\n\n### Service Provider Registration\n\n```php\n// config/app.php\n'providers' => [\n    // Other providers...\n    App\\Providers\\DragDropServiceProvider::class,\n],\n```\n\n### Middleware for Drag Operations\n\n```php\n<?php\n\nnamespace App\\Http\\Middleware;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\n\nclass ValidateDragOperation\n{\n    public function handle(Request $request, Closure $next)\n    {\n        if ($request->has(['oldIndex', 'newIndex'])) {\n            $request->validate([\n                'oldIndex' => 'required|integer|min:0',\n                'newIndex' => 'required|integer|min:0',\n            ]);\n        }\n\n        return $next($request);\n    }\n}\n```\n\n## Performance Optimization\n\n### Lazy Loading\n\n```blade\n<div \n    x-data=\"{ loaded: false }\"\n    x-intersect=\"loaded = true\"\n>\n    <template x-if=\"loaded\">\n        <div x-drag-context=\"handleDrop\">\n            <!-- Heavy drag content -->\n        </div>\n    </template>\n</div>\n```\n\n### Debounced Updates\n\n```javascript\n// Debounce frequent reorder operations\nconst debouncedReorder = debounce((oldIndex, newIndex) => {\n    $wire.reorderItems(oldIndex, newIndex);\n}, 300);\n```\n\n## Troubleshooting\n\n### Common Issues\n\n**Drag not working:**\n- Ensure Alpine.js is loaded before the drag-and-drop plugin\n- Check that `x-drag-context` wraps `x-drag-item` elements\n- Verify CSS doesn't prevent pointer events\n\n**Accessibility issues:**\n- Add `sr-only` class styles to your CSS\n- Ensure proper color contrast ratios\n- Test with actual screen readers\n\n**Mobile touch problems:**\n- Add `touch-action: none` to draggable elements\n- Test on actual devices, not just browser dev tools\n\n### Debug Mode\n\n```javascript\n// Enable debug logging\ndocument.addEventListener('drag:end', (event) => {\n    console.log('Drag operation:', event.detail);\n});\n```\n\n## Contributing\n\nWe welcome contributions! Please see our [Contributing Guidelines](docs/contributing.md) for details.\n\n### Development Setup\n\n```bash\n# Clone repository\ngit clone https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop.git\ncd livewire-drag-and-drop\n\n# Install dependencies\nnpm install\n\n# Start development server\nnpm run dev\n\n# Run tests\nnpm run test\n```\n\n### Code Standards\n\n- Follow [ArtisanPack UI Coding Standards](docs/coding-standards.md)\n- Use [WordPress Documentation Standards](https://developer.wordpress.org/coding-standards/inline-documentation-standards/)\n- Ensure WCAG 2.1 AA compliance\n- Add comprehensive JSDoc comments\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for version history and breaking changes.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Support\n\n- 📖 **Documentation**: [docs/](docs/)\n- 🐛 **Bug Reports**: [GitLab Issues](https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop/issues)\n- 💬 **Community**: [ArtisanPack UI Discussions](https://gitlab.com/jacob-martella-web-design/artisanpack-ui/livewire-drag-and-drop/-/wikis/home)\n- 📧 **Email**: support@artisanpack-ui.com\n\n## Credits\n\nCreated by [Jacob Martella](https://gitlab.com/jacob-martella-web-design) for the ArtisanPack UI ecosystem.\n\nBuilt with:\n- [Alpine.js](https://alpinejs.dev/) - Reactive JavaScript framework\n- [Livewire](https://laravel-livewire.com/) - Laravel full-stack framework\n- [Vite](https://vitejs.dev/) - Build tool and dev server\n\n---\n\n**ArtisanPack UI** - Crafting accessible, beautiful interfaces for the modern web.\n","readmeFilename":"README.md"}