Common

useDragReorder

Composable per il riordino col trascinamento, col puntatore (drag & drop HTML5) e da tastiera, con annunci per gli screen reader. Usato da OrderList e dalla colonna rowReorder del DataTable.

Composable comune — usato da OrderList (draggable) e dalla colonna rowReorder del DataTable. È esportato anche dal pacchetto, insieme a moveItem, per i componenti custom.

Firma TypeScript

export function useDragReorder(options: UseDragReorderOptions): {
  dragIndex: Ref<number | null>       // voce trascinata col puntatore
  dropIndex: Ref<number | null>       // voce sotto la linea di rilascio
  dropPosition: Ref<'before' | 'after' | null>
  grabbedIndex: Ref<number | null>    // voce afferrata da tastiera
  targetIndex: Ref<number | null>     // posizione finale scelta da tastiera
  announcement: Ref<string>           // testo per una live region
  onDragStart: (index: number, event: DragEvent, dragImage?: Element | null) => void
  onDragOver: (index: number, event: DragEvent) => void
  onDrop: (index: number, event: DragEvent) => void
  onDragEnd: () => void
  handleKeydown: (index: number, event: KeyboardEvent) => boolean
  cancelGrab: () => void
}

export interface UseDragReorderOptions {
  count: () => number
  label: (index: number) => string
  onReorder: (from: number, to: number, originalEvent: Event) => void
  disabled?: () => boolean
  grabKeys?: readonly string[]        // default [' ', 'Enter']
}

/** Copia della lista con la voce `from` spostata all'indice `to`. */
export function moveItem<T>(list: readonly T[], from: number, to: number): T[]

Parametri

ParametroTipoDescrizione
count() => numberNumero di voci della lista
label(index) => stringTesto della voce, usato negli annunci
onReorder(from, to, originalEvent) => voidCommit dello spostamento: indice di partenza e indice finale, sempre diversi. La lista non viene toccata: decide il chiamante
disabled() => boolean (opzionale)Disattiva puntatore e tastiera
grabKeysreadonly string[] (opzionale)Tasti che afferrano la voce. OrderList passa [' '] per lasciare Invio alla selezione. A voce afferrata rilasciano sempre Spazio e Invio

Comportamento

  • Puntatore: dropIndex/dropPosition indicano dove cadrà la voce (metà superiore della voce = prima, inferiore = dopo). Il rilascio sulla voce stessa non riordina.
  • Tastiera: il tasto di presa afferra la voce; frecce, Home e Fine scelgono la posizione (fermandosi ai bordi); Spazio o Invio rilasciano; Escape annulla e chiama event.stopPropagation(), così un Dialog contenitore resta aperto; Tab annulla e lascia passare il focus. handleKeydown restituisce true se ha consumato il tasto.
  • Annunci (i18n list.dragGrabbed, list.dragMoved, list.dragDropped, list.dragCancelled, con {item}, {position}, {total}) in announcement, da rendere in una live region. Le istruzioni sono in list.dragInstructions.

Esempio

import { moveItem, useDragReorder } from '@pzeta/vue-components'

const reorder = useDragReorder({
  count: () => items.value.length,
  label: (index) => items.value[index]?.nome ?? '',
  onReorder: (from, to) => {
    items.value = moveItem(items.value, from, to)
  },
})
<ul role="listbox" tabindex="0" aria-describedby="istruzioni"
    @keydown="reorder.handleKeydown(focused, $event)" @blur="reorder.cancelGrab()">
  <li v-for="(item, i) in items" :key="item.id" draggable="true"
      @dragstart="reorder.onDragStart(i, $event)" @dragover="reorder.onDragOver(i, $event)"
      @drop="reorder.onDrop(i, $event)" @dragend="reorder.onDragEnd()">
    {{ item.nome }}
  </li>
</ul>
<span id="istruzioni" hidden>{{ t('list.dragInstructions') }}</span>
<div class="sr-only" aria-live="assertive">{{ reorder.announcement.value }}</div>