K
KoreUI

Sortable

UI

Lista reordenable por arrastre. Wrapper fino sobre wire:sort —o x-sort en modo cliente—, la directiva que Livewire 4 trae de fábrica con SortableJS embebido: no añade ninguna dependencia de JavaScript propia.

El modo server necesita un componente Livewire real. Cada drop dispara wire:sort, que llama a tu método handler con el id movido y la posición destino; tu handler reordena el array y el nuevo estado vuelve al DOM. Sin componente Livewire alrededor, usa mode="client" para reordenar solo en el navegador, sin round-trip.

En vivo

Arrastra cualquier tarea para reordenarla. El drop llama al handler en el servidor y el array de abajo refleja el orden que Livewire guarda de verdad, no un efecto visual del navegador.

Diseñar wireframes
Revisión de código
Escribir tests
Deploy a staging
orden: Diseñar wireframes → Revisión de código → Escribir tests → Deploy a staging
<x-kore::sortable handler="reorderSort">
    @foreach($sortItems as $task)
        <x-kore::sortable.item :id="$task['id']" wire:key="task-{{ $task['id'] }}">
            {{ $task['name'] }}
        </x-kore::sortable.item>
    @endforeach
</x-kore::sortable>
<x-kore::sortable handler="reorderSort">
    @foreach($sortItems as $task)
        <x-kore::sortable.item :id="$task['id']" wire:key="task-{{ $task['id'] }}">
            {{ $task['name'] }}
        </x-kore::sortable.item>
    @endforeach
</x-kore::sortable>

El handler

El contenedor solo pide un método con esta forma: recibe el id de la fila movida y el índice destino, y decide cómo queda el array.

app/Livewire/Docs/DemoSortable.php
public array $sortItems = [
    ['id' => 1, 'name' => 'Diseñar wireframes'],
    ['id' => 2, 'name' => 'Revisión de código'],
    ['id' => 3, 'name' => 'Escribir tests'],
    ['id' => 4, 'name' => 'Deploy a staging'],
];

public function reorderSort($item, $position): void
{
    $items = collect($this->sortItems);
    $moved = $items->firstWhere('id', (int) $item);

    if (! $moved) {
        return;
    }

    $rest = $items->reject(fn ($i) => $i['id'] == (int) $item)->values();

    $this->sortItems = $rest->slice(0, $position)->push($moved)->concat($rest->slice($position))->values()->all();
}
public array $sortItems = [
    ['id' => 1, 'name' => 'Diseñar wireframes'],
    ['id' => 2, 'name' => 'Revisión de código'],
    ['id' => 3, 'name' => 'Escribir tests'],
    ['id' => 4, 'name' => 'Deploy a staging'],
];

public function reorderSort($item, $position): void
{
    $items = collect($this->sortItems);
    $moved = $items->firstWhere('id', (int) $item);

    if (! $moved) {
        return;
    }

    $rest = $items->reject(fn ($i) => $i['id'] == (int) $item)->values();

    $this->sortItems = $rest->slice(0, $position)->push($moved)->concat($rest->slice($position))->values()->all();
}

El id estable es obligatorio en modo server. Usa el id real del registro, nunca un valor generado en el render: ese mismo valor alimenta a la vez wire:sort:item —lo que el drop le manda al handler— y el wire:key que el morphing de Livewire necesita para no confundir una fila con otra al reconciliar el DOM.

Modo cliente

Sin handler ni componente Livewire alrededor: el modo cliente usa x-sort de Alpine y reordena solo en el DOM. Útil para listas que no necesitan persistirse, o como paso intermedio antes de guardar con un botón aparte.

Uno
Dos
Tres
<x-kore::sortable mode="client">
    <x-kore::sortable.item id="1">Uno</x-kore::sortable.item>
    <x-kore::sortable.item id="2">Dos</x-kore::sortable.item>
    <x-kore::sortable.item id="3">Tres</x-kore::sortable.item>
</x-kore::sortable>
<x-kore::sortable mode="client">
    <x-kore::sortable.item id="1">Uno</x-kore::sortable.item>
    <x-kore::sortable.item id="2">Dos</x-kore::sortable.item>
    <x-kore::sortable.item id="3">Tres</x-kore::sortable.item>
</x-kore::sortable>

Con tirador

Con handle activado, solo se arrastra desde el icono de la izquierda: el resto de la fila deja de ser zona de arrastre, así que puede llevar botones, enlaces o cualquier control clicable sin que el drag los tape. Funciona igual en modo server: añade handle a un sortable con handler.

Uno
Dos
Tres
<x-kore::sortable mode="client" :handle="true">
    <x-kore::sortable.item id="1">Uno</x-kore::sortable.item>
    <x-kore::sortable.item id="2">Dos</x-kore::sortable.item>
    <x-kore::sortable.item id="3">Tres</x-kore::sortable.item>
</x-kore::sortable>
<x-kore::sortable mode="client" :handle="true">
    <x-kore::sortable.item id="1">Uno</x-kore::sortable.item>
    <x-kore::sortable.item id="2">Dos</x-kore::sortable.item>
    <x-kore::sortable.item id="3">Tres</x-kore::sortable.item>
</x-kore::sortable>

Entre listas (grupos)

Dos contenedores con el mismo group permiten arrastrar ítems de uno a otro. Es exactamente lo que arma kore::kanban por columnas y kore::transfer entre disponibles/seleccionados, por debajo.

Por hacer

Por hacer A
Por hacer B

Hecho

Hecho C

En modo server, cada lista lleva su propio handler y comparten group: <x-kore::sortable handler="moveTodo" group="tasks"> junto a <x-kore::sortable handler="moveDone" group="tasks">. El handler que se dispara ya te dice a qué lista fue a parar el ítem —es la base sobre la que están construidos kore::kanban y kore::transfer—.

<x-kore::sortable mode="client" group="demo-grupo">
    <x-kore::sortable.item id="1">Por hacer A</x-kore::sortable.item>
    <x-kore::sortable.item id="2">Por hacer B</x-kore::sortable.item>
</x-kore::sortable>

<x-kore::sortable mode="client" group="demo-grupo">
    <x-kore::sortable.item id="3">Hecho C</x-kore::sortable.item>
</x-kore::sortable>
<x-kore::sortable mode="client" group="demo-grupo">
    <x-kore::sortable.item id="1">Por hacer A</x-kore::sortable.item>
    <x-kore::sortable.item id="2">Por hacer B</x-kore::sortable.item>
</x-kore::sortable>

<x-kore::sortable mode="client" group="demo-grupo">
    <x-kore::sortable.item id="3">Hecho C</x-kore::sortable.item>
</x-kore::sortable>

Props del contenedor

Prop Tipo Default Descripción
handler string|null null Método Livewire que recibe (item, position) en el drop
mode string server server (wire:sort, round-trip) o client (x-sort, solo DOM)
group string|null null Nombre de grupo para arrastrar entre listas (Kanban/Transfer)
handle bool false Muestra un tirador por fila: solo se arrastra desde ahí
tag string div Etiqueta HTML del contenedor: div, ul, etc.
animation int 150 Duración de la animación de SortableJS, en ms

Props de item

Prop Tipo Default Descripción
id string requerido en modo server Clave estable, alimenta wire:sort:item y wire:key
Cargando