diff --git a/packages/sortable/src/hooks/defaults.ts b/packages/sortable/src/hooks/defaults.ts index 81448aeb..c0cf1e0a 100644 --- a/packages/sortable/src/hooks/defaults.ts +++ b/packages/sortable/src/hooks/defaults.ts @@ -1,6 +1,19 @@ import {CSS} from '@dnd-kit/utilities'; -import type {AnimateLayoutChanges, SortableTransition} from './types'; +import {arrayMove} from '../utilities'; + +import type { + AnimateLayoutChanges, + NewIndexGetter, + SortableTransition, +} from './types'; + +export const defaultNewIndexGetter: NewIndexGetter = ({ + id, + items, + activeIndex, + overIndex, +}) => arrayMove(items, activeIndex, overIndex).indexOf(id); export const defaultAnimateLayoutChanges: AnimateLayoutChanges = ({ containerId, diff --git a/packages/sortable/src/hooks/index.ts b/packages/sortable/src/hooks/index.ts index 65e72a26..a00168c9 100644 --- a/packages/sortable/src/hooks/index.ts +++ b/packages/sortable/src/hooks/index.ts @@ -1,5 +1,5 @@ export {useSortable} from './useSortable'; export type {Arguments as UseSortableArguments} from './useSortable'; -export {defaultAnimateLayoutChanges} from './defaults'; -export type {AnimateLayoutChanges} from './types'; +export {defaultAnimateLayoutChanges, defaultNewIndexGetter} from './defaults'; +export type {AnimateLayoutChanges, NewIndexGetter} from './types'; diff --git a/packages/sortable/src/hooks/types.ts b/packages/sortable/src/hooks/types.ts index c7cbfc5f..26c67d24 100644 --- a/packages/sortable/src/hooks/types.ts +++ b/packages/sortable/src/hooks/types.ts @@ -17,3 +17,12 @@ export type AnimateLayoutChanges = (args: { transition: SortableTransition | null; wasDragging: boolean; }) => boolean; + +export interface NewIndexGetterArguments { + id: UniqueIdentifier; + items: UniqueIdentifier[]; + activeIndex: number; + overIndex: number; +} + +export type NewIndexGetter = (args: NewIndexGetterArguments) => number; diff --git a/packages/sortable/src/hooks/useSortable.ts b/packages/sortable/src/hooks/useSortable.ts index 2a77c904..ccbfa2c0 100644 --- a/packages/sortable/src/hooks/useSortable.ts +++ b/packages/sortable/src/hooks/useSortable.ts @@ -4,19 +4,25 @@ import {CSS, useCombinedRefs} from '@dnd-kit/utilities'; import {Context} from '../components'; import type {SortingStrategy} from '../types'; -import {arrayMove, isValidIndex} from '../utilities'; +import {isValidIndex} from '../utilities'; import { defaultAnimateLayoutChanges, defaultAttributes, + defaultNewIndexGetter, defaultTransition, disabledTransition, transitionProperty, } from './defaults'; -import type {AnimateLayoutChanges, SortableTransition} from './types'; +import type { + AnimateLayoutChanges, + NewIndexGetter, + SortableTransition, +} from './types'; import {useDerivedTransform} from './utilities'; export interface Arguments extends UseDraggableArguments { animateLayoutChanges?: AnimateLayoutChanges; + getNewIndex?: NewIndexGetter; strategy?: SortingStrategy; transition?: SortableTransition | null; } @@ -26,6 +32,7 @@ export function useSortable({ attributes: userDefinedAttributes, disabled, data: customData, + getNewIndex = defaultNewIndexGetter, id, strategy: localStrategy, transition = defaultTransition, @@ -93,7 +100,7 @@ export function useSortable({ : null; const newIndex = isValidIndex(activeIndex) && isValidIndex(overIndex) - ? arrayMove(items, activeIndex, overIndex).indexOf(id) + ? getNewIndex({id, items, activeIndex, overIndex}) : index; const prevItems = useRef(items); const itemsHaveChanged = items !== prevItems.current; diff --git a/packages/sortable/src/index.ts b/packages/sortable/src/index.ts index c2a8675b..3f113225 100644 --- a/packages/sortable/src/index.ts +++ b/packages/sortable/src/index.ts @@ -1,7 +1,15 @@ export {SortableContext} from './components'; export type {SortableContextProps} from './components'; -export {useSortable, defaultAnimateLayoutChanges} from './hooks'; -export type {UseSortableArguments, AnimateLayoutChanges} from './hooks'; +export { + useSortable, + defaultAnimateLayoutChanges, + defaultNewIndexGetter, +} from './hooks'; +export type { + UseSortableArguments, + AnimateLayoutChanges, + NewIndexGetter, +} from './hooks'; export { horizontalListSortingStrategy, rectSortingStrategy, @@ -9,5 +17,5 @@ export { verticalListSortingStrategy, } from './strategies'; export {sortableKeyboardCoordinates} from './sensors'; -export {arrayMove} from './utilities'; +export {arrayMove, arraySwap} from './utilities'; export type {SortingStrategy} from './types'; diff --git a/packages/sortable/src/utilities/arraySwap.ts b/packages/sortable/src/utilities/arraySwap.ts new file mode 100644 index 00000000..e2984ced --- /dev/null +++ b/packages/sortable/src/utilities/arraySwap.ts @@ -0,0 +1,11 @@ +/** + * Swap an array item to a different position. Returns a new array with the item swapped to the new position. + */ +export function arraySwap(array: T[], from: number, to: number): T[] { + const newArray = array.slice(); + + newArray[from] = array[to]; + newArray[to] = array[from]; + + return newArray; +} diff --git a/packages/sortable/src/utilities/index.ts b/packages/sortable/src/utilities/index.ts index 0c98528a..0f72bfa9 100644 --- a/packages/sortable/src/utilities/index.ts +++ b/packages/sortable/src/utilities/index.ts @@ -1,3 +1,4 @@ export {arrayMove} from './arrayMove'; +export {arraySwap} from './arraySwap'; export {getSortedRects} from './getSortedRects'; export {isValidIndex} from './isValidIndex'; diff --git a/stories/2 - Presets/Sortable/3-Grid.story.tsx b/stories/2 - Presets/Sortable/3-Grid.story.tsx index 4edc62ff..c253bd91 100644 --- a/stories/2 - Presets/Sortable/3-Grid.story.tsx +++ b/stories/2 - Presets/Sortable/3-Grid.story.tsx @@ -1,9 +1,11 @@ import React from 'react'; import {MeasuringStrategy} from '@dnd-kit/core'; import { + arraySwap, AnimateLayoutChanges, defaultAnimateLayoutChanges, rectSortingStrategy, + rectSwappingStrategy, } from '@dnd-kit/sortable'; import {Sortable, Props as SortableProps} from './Sortable'; @@ -104,6 +106,17 @@ export const ScrollContainer = () => ( ); +export const Swappable = () => ( + + arraySwap(items, activeIndex, overIndex).indexOf(id) + } + /> +); + export const PressDelay = () => ( ({}), + getNewIndex, handle = false, itemCount = 16, items: initialItems, @@ -94,6 +98,7 @@ export function Sortable({ modifiers, removable, renderItem, + reorderItems = arrayMove, strategy = rectSortingStrategy, useDragOverlay = true, wrapperStyle = () => ({}), @@ -186,7 +191,7 @@ export function Sortable({ if (over) { const overIndex = getIndex(over.id); if (activeIndex !== overIndex) { - setItems((items) => arrayMove(items, activeIndex, overIndex)); + setItems((items) => reorderItems(items, activeIndex, overIndex)); } } }} @@ -210,6 +215,7 @@ export function Sortable({ onRemove={handleRemove} animateLayoutChanges={animateLayoutChanges} useDragOverlay={useDragOverlay} + getNewIndex={getNewIndex} /> ))} @@ -253,6 +259,7 @@ export function Sortable({ interface SortableItemProps { animateLayoutChanges?: AnimateLayoutChanges; disabled?: boolean; + getNewIndex?: NewIndexGetter; id: string; index: number; handle: boolean; @@ -274,9 +281,10 @@ interface SortableItemProps { export function SortableItem({ disabled, animateLayoutChanges, + getNewIndex, + handle, id, index, - handle, onRemove, style, renderItem, @@ -293,9 +301,10 @@ export function SortableItem({ transform, transition, } = useSortable({ - animateLayoutChanges, id, + animateLayoutChanges, disabled, + getNewIndex, }); return (