Sortable
Headless Sortable; covers React Aria useDraggableCollection, ARIA APG drag reordering.
| Package | @vizejs/ui/sortable |
| Maturity | stable |
| Own the source | vize lib pull sortable |
| Requires | drag-and-drop |
| Aliases | sortable list, reorder, sortable grid, sortable tree |
| Covers | React Aria useDraggableCollection, ARIA APG drag reordering |
API
createSortable
Create an SSR-safe sortable coordinator for lists, grids, and trees.
function createSortable(options: SortableOptions = {}): SortableController
useSortable
Create a sortable coordinator disposed with the current Vue effect scope.
function useSortable(options: SortableOptions = {}): SortableController
readOrientation
Resolve the closed orientation union without accepting mistyped JavaScript.
function readOrientation(value: SortableOptions["orientation"]): SortableOrientation
readDirection
Resolve the closed direction union without accepting mistyped JavaScript.
function readDirection(value: SortableOptions["direction"]): SortableDirection
readColumns
Resolve a positive integer column count for grid arrow-key geometry.
function readColumns(value: SortableOptions["columns"]): number
edgesFor
Drop edges one item target resolves for an orientation.
function edgesFor(orientation: SortableOrientation, nesting: boolean): readonly DropEdge[]
edgeForPosition
Map a logical position back onto the physical edge for an orientation.
function edgeForPosition( position: SortablePosition, orientation: SortableOrientation, direction: SortableDirection, ): DropEdge
createSortableEvent
Build a frozen lifecycle snapshot for sortable callbacks.
function createSortableEvent<Type extends SortableEventType>( type: Type, pointerType: DragPointerType, key: string, fromIndex: number, toIndex: number, overKey: string | null, position: SortablePosition | null, originalEvent: Event | null, ): SortableEvent<Type>
positionForEdge
Map a physical drop edge onto the logical position for an orientation.
function positionForEdge( edge: DropEdge, orientation: SortableOrientation, direction: SortableDirection, ): SortablePosition
computeToIndex
Compute the insertion index for moving fromIndex relative to overIndex. "inside" moves report the receiving item's index unchanged.
function computeToIndex( fromIndex: number, overIndex: number, position: SortablePosition, ): number
keyboardDelta
Signed index step for one arrow key, or null when the key is not owned.
function keyboardDelta( key: string, orientation: SortableOrientation, direction: SortableDirection, columns: number, ): number | null
dispatchSortableEvent
Dispatch one lifecycle snapshot to the matching consumer callback.
function dispatchSortableEvent(options: SortableOptions, event: SortableEvent): void
projectDragContext
Project a drag-and-drop context onto sortable indexes and positions.
function projectDragContext( keys: readonly string[], orientation: SortableOrientation, direction: SortableDirection, dragContext: DragContextSlice, ): SortableProjection
sortableContextFor
Build the sortable announcement context for one drag announcement phase.
function sortableContextFor( phase: "cancel" | "drop" | "grab" | "move", dragContext: AnnouncementSlice, projection: SortableProjection, count: number, originIndex: number | null, ): SortableAnnouncementContext
defaultSortableAnnouncements
Built-in English announcement builders; consumers override to localize.
const defaultSortableAnnouncements: Required<SortableAnnouncements>
handleSortableKeydown
Handle one keydown from an item: grab, move, nest, drop, or cancel.
function handleSortableKeydown( host: SortableKeyboardHost, event: KeyboardEvent, key: string, ): void
cancelSortableKeyboard
Cancel an owned keyboard sort from focus loss or programmatic teardown.
function cancelSortableKeyboard(host: SortableKeyboardHost, event: Event | null): boolean
Behavior
Normative state × input → outcome table for @vizejs/ui/sortable. Every row is
exercised by src/families/interaction/sortable/sortable*.test.ts; compile-only assertions live in
src/families/interaction/sortable/sortable.types.test-d.ts.
| # | State | Input | Outcome | Proven by |
|---|---|---|---|---|
| S1 | idle | items register in any order | logical indexes follow document order, not registration order | ordering test |
| S2 | idle | pointer travels the start distance on an item | sortstart reports the item's current index and announces the grab |
pointer lifecycle test |
| S3 | sorting | pointer over another item's leading half | preview projects before and the indicator carries the edge line |
pointer preview test |
| S4 | sorting | pointer over another item's trailing half | preview projects after with the moved insertion index |
pointer preview test |
| S5 | sorting | unchanged projection on further movement | no duplicate preview is emitted | preview dedup test |
| S6 | sorting over an item | owning contact releases | sortcommit reports origin and final indexes exactly once |
pointer commit test |
| S7 | sorting | Escape, blur, hidden document, or cancel() | sortcancel reports the index to return to and announces the return |
cancellation tests |
| S8 | sorting released outside | owning contact releases over no item | the sort cancels back to the origin index | outside-release test |
| S9 | idle focus on item | Enter or Space without modifiers | keyboard sort grabs, reports position, and speaks usage instructions | keyboard grab test |
| S10 | keyboard sorting (vertical) | ArrowUp and ArrowDown, Home, and End | destination steps and clamps; each change previews and announces | keyboard move tests |
| S11 | keyboard sorting (grid) | ArrowUp and ArrowDown | destination steps by the resolved column count | grid keyboard test |
| S12 | keyboard sorting (horizontal, RTL) | ArrowLeft and ArrowRight | logical direction flips with the resolved writing direction | RTL keyboard test |
| S13 | keyboard sorting (nesting) | ArrowRight then ArrowLeft | preview nests inside the previous item, then returns to index moves | nesting keyboard test |
| S14 | sorting (nesting) | pointer inside an item's central band | preview and commit report "inside" with the receiving item's key |
nesting pointer test |
| S15 | keyboard sorting | Enter or Space | sortcommit reports origin and destination and announces the drop |
keyboard commit test |
| S16 | disabled item or controller | any grab attempt | no sort starts; an active sort cancels on reactive disablement | disablement tests |
| S17 | any sort | item registration disposal | the owning sort cancels first and both registrations release | disposal tests |
| S18 | any sort | controller disposal | delegated listeners and reactive state release without callbacks | disposal tests |
| S19 | concurrent SSR requests | identical component trees | byte-identical markup contains no handlers or DOM reads | SSR test |
| S20 | SSR followed by hydration | keyboard sort | host identity remains and reactive output updates without warning | hydration test |
| S21 | public TypeScript API | mutation or invalid closed union | compilation rejects misuse | type assertions |
Accessibility and layout obligations
Every item host must be keyboard focusable so pointer sorting has an
Enter-grab, arrow-move, Enter-drop equivalent, and every phase speaks through
the underlying drag-and-drop live region with injectable, localizable
builders. Grid and horizontal arrow keys resolve against the configured
writing direction so RTL layouts move logically. The primitive is headless: it
assigns no role or style, and consumers render placeholder and indicator
geometry from the reactive indicator state. Nested trees compose this family
with the drag-and-drop core's innermost-target ownership; "inside" previews
and commits carry the receiving item so consumers re-parent their own model.