From 5ea588bf75a2603425f54cfb932df663eef5d83a Mon Sep 17 00:00:00 2001 From: Omar Mahili Date: Mon, 27 Jan 2025 21:01:54 +0100 Subject: [PATCH] feat: add onIndexChange event --- README.md | 3 +- example/src/IndexChange.tsx | 77 +++++++++++++++++++ example/src/screens.ts | 6 ++ .../ReorderableListCore.tsx | 2 + .../useReorderableListCore.ts | 6 ++ src/index.ts | 2 + src/types/props.ts | 13 +++- 7 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 example/src/IndexChange.tsx diff --git a/README.md b/README.md index 8eb31d8..89fbc74 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,11 @@ This component uses a [FlatList](https://reactnative.dev/docs/flatlist) and it e | cellAnimations | `ReorderableListCellAnimations` | No | N/A | Allows passing an object with values and/or shared values that can animate a cell, for example by using the `onDragStart` and `onDragEnd` events. Supports view style properties. Override opacity and/or transform to disable the default animation, e.g. `{opacity: 1, transform: []}`. Check the [examples](https://github.com/omahili/react-native-reorderable-list/tree/master/example) for more details. | | shouldUpdateActiveItem | boolean | No | `false` | Whether the active item should be updated. Enables usage of `useIsActive` hook. | | panEnabled | `boolean` | No | `true` | Wether the pan gestures necessary for dragging are enabled. | -| panActivateAfterLongPress | `number` | No | N/A | Duration in milliseconds a the long press on the list before pan gestures, necessary for dragging, are allowed to activate. | +| panActivateAfterLongPress | `number` | No | N/A | Duration in milliseconds of the long press on the list before the pan gesture for dragging is allowed to activate. | | onReorder | `(event: { from: number, to: number }) => void` | Yes | N/A | Event fired after an item is released and the list is reordered. | | onDragStart | `(event: { index: number }) => void` | No | N/A | Event fired when an item is dragged. Needs to be a `worklet`. See [Reanimated docs](https://docs.swmansion.com/react-native-reanimated) for further info. | | onDragEnd | `(event: { from: number, to: number }) => void` | No | N/A | Event fired when the dragged item is released. Needs to be a `worklet`. See [Reanimated docs](https://docs.swmansion.com/react-native-reanimated) for further info. | +| onIndexChange | `(event: { index: number }) => void` | No | N/A | Event fired when the index of the dragged item changes. Needs to be a `worklet`. See [Reanimated docs](https://docs.swmansion.com/react-native-reanimated) for further info. | | onScroll | `ReturnType` | No | N/A | An animated scroll handler created with useAnimatedScrollHandler. See [Reanimated docs](https://docs.swmansion.com/react-native-reanimated) for further info. | The following props from FlatList are not supported: diff --git a/example/src/IndexChange.tsx b/example/src/IndexChange.tsx new file mode 100644 index 0000000..90cb21b --- /dev/null +++ b/example/src/IndexChange.tsx @@ -0,0 +1,77 @@ +import React, {useCallback, useState} from 'react'; +import {ListRenderItemInfo, StyleSheet, View} from 'react-native'; + +import {runOnJS} from 'react-native-reanimated'; +import ReorderableList, { + ReorderableListReorderEvent, + reorderItems, +} from 'react-native-reorderable-list'; +import {ReorderableListIndexChangeEvent} from 'src/types'; + +import { + ItemSeparator, + ListItem, + SeedDataItem, + TitleHighlight, + useSeedData, +} from './common'; + +export const IndexChangeScreen = () => { + const seedData = useSeedData(); + const [data, setData] = useState(seedData); + const [currentIndex, setCurrentIndex] = useState(); + + const handleReorder = ({from, to}: ReorderableListReorderEvent) => { + setData(value => reorderItems(value, from, to)); + }; + + const renderItem = ({item}: ListRenderItemInfo) => ( + + ); + + const handleDragEnd = useCallback(() => { + 'worklet'; + + runOnJS(setCurrentIndex)(undefined); + }, []); + + const handleIndexChange = useCallback( + (e: ReorderableListIndexChangeEvent) => { + 'worklet'; + + runOnJS(setCurrentIndex)(e.index); + }, + [], + ); + + return ( + + item.id} + ItemSeparatorComponent={ItemSeparator} + onDragEnd={handleDragEnd} + onDragStart={handleIndexChange} // to register index at start + onIndexChange={handleIndexChange} + /> + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + footer: { + height: 80, + paddingBottom: 24, + }, +}); diff --git a/example/src/screens.ts b/example/src/screens.ts index 2b1b2e5..9d54687 100644 --- a/example/src/screens.ts +++ b/example/src/screens.ts @@ -6,6 +6,7 @@ import {CustomAnimationsScreen} from './CustomAnimations'; import {DynamicHeightsScreen} from './DynamicHeights'; import {FloatingHeaderScreen} from './FloatingHeader'; import {HeaderFooterScreen} from './HeaderFooter'; +import {IndexChangeScreen} from './IndexChange'; import {MultipleListsScreen} from './MultipleLists'; import {NestedListsScreen} from './NestedLists'; import {NestedScrollableListsScreen} from './NestedScrollableLists'; @@ -69,6 +70,11 @@ const screens = [ name: 'Floating Header', component: FloatingHeaderScreen, }, + { + id: '11', + name: 'Index Change', + component: IndexChangeScreen, + }, ] as const; const names = screens.map(x => x.name); diff --git a/src/components/ReorderableListCore/ReorderableListCore.tsx b/src/components/ReorderableListCore/ReorderableListCore.tsx index 58330e8..1d6cc4a 100644 --- a/src/components/ReorderableListCore/ReorderableListCore.tsx +++ b/src/components/ReorderableListCore/ReorderableListCore.tsx @@ -54,6 +54,7 @@ const ReorderableListCore = ( onScroll, onDragStart, onDragEnd, + onIndexChange, scrollViewContainerRef, scrollViewHeightY, scrollViewScrollOffsetY, @@ -93,6 +94,7 @@ const ReorderableListCore = ( onReorder, onDragStart, onDragEnd, + onIndexChange, scrollViewContainerRef, scrollViewHeightY, scrollViewScrollOffsetY, diff --git a/src/components/ReorderableListCore/useReorderableListCore.ts b/src/components/ReorderableListCore/useReorderableListCore.ts index 409dca2..32d8f01 100644 --- a/src/components/ReorderableListCore/useReorderableListCore.ts +++ b/src/components/ReorderableListCore/useReorderableListCore.ts @@ -31,6 +31,7 @@ import { ReorderableListCellAnimations, ReorderableListDragEndEvent, ReorderableListDragStartEvent, + ReorderableListIndexChangeEvent, ReorderableListState, } from '../../types'; import type {ReorderableListReorderEvent} from '../../types'; @@ -51,6 +52,7 @@ interface UseReorderableListCoreArgs { onReorder: (event: ReorderableListReorderEvent) => void; onDragStart?: (event: ReorderableListDragStartEvent) => void; onDragEnd?: (event: ReorderableListDragEndEvent) => void; + onIndexChange?: (event: ReorderableListIndexChangeEvent) => void; onLayout?: (event: LayoutChangeEvent) => void; scrollViewContainerRef: React.RefObject | undefined; scrollViewHeightY: SharedValue | undefined; @@ -77,6 +79,7 @@ export const useReorderableListCore = ({ onDragStart, onDragEnd, onLayout, + onIndexChange, scrollViewContainerRef, scrollViewHeightY, scrollViewScrollOffsetY, @@ -351,6 +354,8 @@ export const useReorderableListCore = ({ previousDirection.value = newDirection; previousIndex.value = currentIndex.value; currentIndex.value = newIndex; + + onIndexChange?.({index: newIndex}); } }, [ @@ -361,6 +366,7 @@ export const useReorderableListCore = ({ itemOffset, itemHeight, getIndexFromY, + onIndexChange, ], ); diff --git a/src/index.ts b/src/index.ts index 38f582b..80e6214 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ import type { ReorderableListCellAnimations, ReorderableListDragEndEvent, ReorderableListDragStartEvent, + ReorderableListIndexChangeEvent, ReorderableListProps, ReorderableListReorderEvent, ScrollViewContainerProps, @@ -29,6 +30,7 @@ export { ReorderableListCellAnimations, ReorderableListDragStartEvent, ReorderableListDragEndEvent, + ReorderableListIndexChangeEvent, ScrollViewContainer, ScrollViewContainerProps, NestedReorderableList, diff --git a/src/types/props.ts b/src/types/props.ts index 6939cd8..4c1bdc8 100644 --- a/src/types/props.ts +++ b/src/types/props.ts @@ -39,6 +39,13 @@ export interface ReorderableListDragStartEvent { index: number; } +export interface ReorderableListIndexChangeEvent { + /** + * Index of the dragged item. + */ + index: number; +} + export interface ReorderableListDragEndEvent { /** * Index of the dragged item. @@ -107,7 +114,7 @@ export interface ReorderableListProps */ panEnabled?: boolean; /** - * Duration in milliseconds of a long press on the list before pan gestures, necessary for dragging, are allowed to activate. + * Duration in milliseconds of the long press on the list before the pan gesture for dragging is allowed to activate. */ panActivateAfterLongPress?: number; /** @@ -126,6 +133,10 @@ export interface ReorderableListProps * Event fired when the dragged item is released. Needs to be a `worklet`. See [Reanimated docs](https://docs.swmansion.com/react-native-reanimated) for further info. */ onDragEnd?: (event: ReorderableListDragEndEvent) => void; + /** + * Event fired when the index of the dragged item changes. Needs to be a `worklet`. See [Reanimated docs](https://docs.swmansion.com/react-native-reanimated) for further info. + */ + onIndexChange?: (event: ReorderableListIndexChangeEvent) => void; } export type Transforms = PerspectiveTransform &