diff --git a/example/app/src/examples/SortableGrid/PlaygroundExample.tsx b/example/app/src/examples/SortableGrid/PlaygroundExample.tsx index c4fb99d7..29a4b8dd 100644 --- a/example/app/src/examples/SortableGrid/PlaygroundExample.tsx +++ b/example/app/src/examples/SortableGrid/PlaygroundExample.tsx @@ -1,4 +1,4 @@ -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import type { SortableGridRenderItem } from 'react-native-sortables'; import Sortable from 'react-native-sortables'; @@ -9,6 +9,8 @@ import { colors, radius, sizes, spacing, text } from '@/theme'; const DATA = Array.from({ length: 12 }, (_, index) => `Item ${index + 1}`); export default function PlaygroundExample() { + const [data, setData] = useState(DATA); + const renderItem = useCallback>( ({ item }) => ( @@ -23,9 +25,23 @@ export default function PlaygroundExample() { { + console.log( + 'item', + params.key, + 'moved from', + params.fromIndex, + 'to', + params.toIndex, + `(${params.data[params.toIndex]})` + ); + setData(params.data.filter(item => item !== params.key)); + }} /> ); diff --git a/example/app/src/examples/SortableGrid/features/DropIndicatorExample.tsx b/example/app/src/examples/SortableGrid/features/DropIndicatorExample.tsx index 6d866f6e..f70be5a8 100644 --- a/example/app/src/examples/SortableGrid/features/DropIndicatorExample.tsx +++ b/example/app/src/examples/SortableGrid/features/DropIndicatorExample.tsx @@ -35,6 +35,7 @@ export default function DropIndicatorExample() { columns={COLUMNS} data={DATA} renderItem={renderItem} + reorderOnDrag={false} rowGap={spacing.xs} showDropIndicator /> diff --git a/packages/react-native-sortables/src/components/SortableFlex.tsx b/packages/react-native-sortables/src/components/SortableFlex.tsx index 95dc4317..4736fa57 100644 --- a/packages/react-native-sortables/src/components/SortableFlex.tsx +++ b/packages/react-native-sortables/src/components/SortableFlex.tsx @@ -101,6 +101,7 @@ const SortableFlexInner = memo(function SortableFlexInner({ paddingRight, paddingTop, paddingVertical, + reorderOnDrag, rowGap, showDropIndicator, strategy, @@ -159,6 +160,7 @@ const SortableFlexInner = memo(function SortableFlexInner({ itemEntering={itemEntering} itemExiting={itemExiting} overflow={overflow} + reorderOnDrag={reorderOnDrag} showDropIndicator={showDropIndicator} strategy={strategy} styleProps={styleProps} @@ -176,6 +178,7 @@ type SortableFlexComponentProps = Pick< | 'itemEntering' | 'itemExiting' | 'overflow' + | 'reorderOnDrag' | 'showDropIndicator' | 'strategy' > & { @@ -183,6 +186,7 @@ type SortableFlexComponentProps = Pick< }; function SortableFlexComponent({ + reorderOnDrag, strategy, styleProps, ...rest @@ -200,7 +204,7 @@ function SortableFlexComponent({ width } = styleProps; - useOrderUpdater(strategy, FLEX_STRATEGIES); + useOrderUpdater(strategy, FLEX_STRATEGIES, reorderOnDrag); const baseContainerStyle: ViewStyle = { ...styleProps, diff --git a/packages/react-native-sortables/src/components/SortableGrid.tsx b/packages/react-native-sortables/src/components/SortableGrid.tsx index b8a494cb..6023df31 100644 --- a/packages/react-native-sortables/src/components/SortableGrid.tsx +++ b/packages/react-native-sortables/src/components/SortableGrid.tsx @@ -109,6 +109,7 @@ const SortableGridInner = typedMemo(function SortableGridInner({ itemEntering, itemExiting, overflow, + reorderOnDrag, rowGap: _rowGap, rowHeight, showDropIndicator, @@ -156,6 +157,7 @@ const SortableGridInner = typedMemo(function SortableGridInner({ itemEntering={itemEntering} itemExiting={itemExiting} overflow={overflow} + reorderOnDrag={reorderOnDrag} rowGap={rowGap} rowHeight={rowHeight} showDropIndicator={showDropIndicator} @@ -176,6 +178,7 @@ type SortableGridComponentProps = Pick< | 'itemEntering' | 'itemExiting' | 'overflow' + | 'reorderOnDrag' | 'rowHeight' | 'showDropIndicator' | 'strategy' @@ -188,6 +191,7 @@ function SortableGridComponent({ columnGap, groups, isVertical, + reorderOnDrag, rowGap, rowHeight, strategy, @@ -199,7 +203,7 @@ function SortableGridComponent({ const isFirstRenderRef = useRef(true); - useOrderUpdater(strategy, GRID_STRATEGIES); + useOrderUpdater(strategy, GRID_STRATEGIES, reorderOnDrag); useLayoutEffect(() => { if (isFirstRenderRef.current) { diff --git a/packages/react-native-sortables/src/constants/props.ts b/packages/react-native-sortables/src/constants/props.ts index a4d9abe1..7c57af4f 100644 --- a/packages/react-native-sortables/src/constants/props.ts +++ b/packages/react-native-sortables/src/constants/props.ts @@ -57,6 +57,7 @@ export const DEFAULT_SHARED_PROPS = { onOrderChange: undefined, overDrag: 'both', overflow: 'visible', + reorderOnDrag: true, reorderTriggerOrigin: 'center', scrollableRef: undefined, showDropIndicator: false, diff --git a/packages/react-native-sortables/src/providers/grid/GridLayoutProvider/updates/insert.ts b/packages/react-native-sortables/src/providers/grid/GridLayoutProvider/updates/insert.ts index 0e472fc7..1669ad4f 100644 --- a/packages/react-native-sortables/src/providers/grid/GridLayoutProvider/updates/insert.ts +++ b/packages/react-native-sortables/src/providers/grid/GridLayoutProvider/updates/insert.ts @@ -35,7 +35,6 @@ function useInactiveIndexToKey() { }), ({ excludedKey, fixedKeys, idxToKey }) => { if (excludedKey === null) { - result.value = EMPTY_ARRAY; return; } diff --git a/packages/react-native-sortables/src/providers/grid/GridLayoutProvider/updates/swap.ts b/packages/react-native-sortables/src/providers/grid/GridLayoutProvider/updates/swap.ts index 958c4421..a7ea873b 100644 --- a/packages/react-native-sortables/src/providers/grid/GridLayoutProvider/updates/swap.ts +++ b/packages/react-native-sortables/src/providers/grid/GridLayoutProvider/updates/swap.ts @@ -37,7 +37,6 @@ function useInactiveIndexToKey() { ({ excludedKey, idxToKey, keyToIdx }) => { const excludedIndex = excludedKey ? keyToIdx[excludedKey] : undefined; if (excludedIndex === undefined) { - result.value = EMPTY_ARRAY; return; } diff --git a/packages/react-native-sortables/src/providers/shared/hooks/useOrderUpdater.ts b/packages/react-native-sortables/src/providers/shared/hooks/useOrderUpdater.ts index 57a11eb1..440cd2e4 100644 --- a/packages/react-native-sortables/src/providers/shared/hooks/useOrderUpdater.ts +++ b/packages/react-native-sortables/src/providers/shared/hooks/useOrderUpdater.ts @@ -1,14 +1,23 @@ +import { useCallback } from 'react'; import { useAnimatedReaction } from 'react-native-reanimated'; import { useDebugContext } from '../../../debug'; -import type { PredefinedStrategies, SortStrategyFactory } from '../../../types'; +import type { + OrderUpdaterCallbackProps, + PredefinedStrategies, + SortStrategyFactory +} from '../../../types'; import { error } from '../../../utils'; import { useCommonValuesContext } from '../CommonValuesProvider'; import { useDragContext } from '../DragProvider'; export default function useOrderUpdater< P extends PredefinedStrategies = PredefinedStrategies ->(strategy: keyof P | SortStrategyFactory, predefinedStrategies: P) { +>( + strategy: keyof P | SortStrategyFactory, + predefinedStrategies: P, + reorderOnDrag: boolean +) { const useStrategy = typeof strategy === 'string' ? predefinedStrategies[strategy] : strategy; @@ -25,27 +34,18 @@ export default function useOrderUpdater< const updater = useStrategy(); - useAnimatedReaction( - () => ({ - activeKey: activeItemKey.value, - dimensions: activeItemDimensions.value, - position: triggerOriginPosition.value - }), - ({ activeKey, dimensions, position }) => { - if (!activeKey || !dimensions || !position) { - if (debugCross) debugCross.set({ position: null }); - return; - } - + const handleOrderUpdate = useCallback( + ({ + activeKey, + dimensions, + position + }: Omit) => { + 'worklet'; const activeIndex = keyToIndex.value[activeKey]; if (activeIndex === undefined) { return; } - if (debugCross) { - debugCross.set({ color: '#00007e', position }); - } - const newOrder = updater({ activeIndex, activeKey, @@ -61,6 +61,38 @@ export default function useOrderUpdater< newOrder ); } + }, + [handleOrderChange, keyToIndex, updater] + ); + + useAnimatedReaction( + () => ({ + activeKey: activeItemKey.value, + dimensions: activeItemDimensions.value, + position: triggerOriginPosition.value + }), + ({ activeKey, dimensions, position }, prevProps) => { + debugCross?.set({ color: '#00007e', position }); + + if (reorderOnDrag) { + if (activeKey !== null && dimensions && position) { + handleOrderUpdate({ activeKey, dimensions, position }); + } else { + debugCross?.set({ position: null }); + } + } else if ( + activeKey === null && + prevProps?.dimensions && + prevProps?.position && + prevProps?.activeKey !== null + ) { + debugCross?.set({ position: null }); + handleOrderUpdate({ + activeKey: prevProps.activeKey, + dimensions: prevProps.dimensions, + position: prevProps.position + }); + } } ); } diff --git a/packages/react-native-sortables/src/types/props/shared.ts b/packages/react-native-sortables/src/types/props/shared.ts index 1c4b1de7..65f4679d 100644 --- a/packages/react-native-sortables/src/types/props/shared.ts +++ b/packages/react-native-sortables/src/types/props/shared.ts @@ -327,6 +327,10 @@ export type SharedProps = Simplify< * @default 'visible' */ overflow: Overflow; + /** Whether to reorder items during drag gesture or after the active item is dropped + * @default true + */ + reorderOnDrag: boolean; /** Enables debug mode to show additional visual helpers and console logs. * @note This only works in development builds and has no effect in production. * @default false diff --git a/packages/react-native-sortables/src/types/providers/shared.ts b/packages/react-native-sortables/src/types/providers/shared.ts index 9a0db7d6..1dbbe7b0 100644 --- a/packages/react-native-sortables/src/types/providers/shared.ts +++ b/packages/react-native-sortables/src/types/providers/shared.ts @@ -218,7 +218,7 @@ export type DebugContextType = { // ORDER UPDATER -type OrderUpdaterCallbackProps = { +export type OrderUpdaterCallbackProps = { activeKey: string; activeIndex: number; dimensions: Dimensions;