react-native-reanimated-dnd 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +296 -655
- package/lib/components/Draggable.js +1 -1
- package/lib/components/Droppable.d.ts +0 -1
- package/lib/components/Droppable.js +1 -1
- package/lib/components/Sortable.js +1 -1
- package/lib/components/SortableGrid.d.ts +3 -0
- package/lib/components/SortableGrid.js +1 -0
- package/lib/components/SortableGridItem.d.ts +7 -0
- package/lib/components/SortableGridItem.js +1 -0
- package/lib/components/SortableItem.d.ts +2 -2
- package/lib/components/SortableItem.js +1 -1
- package/lib/components/sortableUtils.d.ts +21 -0
- package/lib/components/sortableUtils.js +1 -1
- package/lib/context/DropContext.js +1 -1
- package/lib/hooks/index.d.ts +2 -0
- package/lib/hooks/index.js +1 -1
- package/lib/hooks/safeMeasure.d.ts +3 -0
- package/lib/hooks/safeMeasure.js +1 -0
- package/lib/hooks/useDraggable.js +1 -1
- package/lib/hooks/useDroppable.js +1 -1
- package/lib/hooks/useGridSortable.d.ts +2 -0
- package/lib/hooks/useGridSortable.js +1 -0
- package/lib/hooks/useGridSortableList.d.ts +3 -0
- package/lib/hooks/useGridSortableList.js +1 -0
- package/lib/hooks/useHorizontalSortable.js +1 -1
- package/lib/hooks/useHorizontalSortableList.js +1 -1
- package/lib/hooks/useSortable.d.ts +13 -6
- package/lib/hooks/useSortable.js +1 -1
- package/lib/hooks/useSortableList.d.ts +15 -3
- package/lib/hooks/useSortableList.js +1 -1
- package/lib/index.d.ts +7 -0
- package/lib/index.js +1 -1
- package/lib/types/draggable.d.ts +3 -0
- package/lib/types/grid.d.ts +148 -0
- package/lib/types/grid.js +1 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -1
- package/lib/types/sortable.d.ts +76 -21
- package/lib/utils/gridCalculations.d.ts +21 -0
- package/lib/utils/gridCalculations.js +1 -0
- package/package.json +23 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
import React,{createContext,useContext}from"react";import Animated from"react-native-reanimated";import{GestureDetector}from"react-native-gesture-handler";import{useDraggable}from"../hooks/useDraggable";const DraggableContext=createContext(null);const Handle=({children,style})=>{const draggableContext=useContext(DraggableContext);if(!draggableContext){console.warn("Draggable.Handle must be used within a Draggable component");return React.createElement(React.Fragment,null,children)}return React.createElement(GestureDetector,{gesture:draggableContext.gesture},React.createElement(Animated.View,{style},children))};const DraggableComponent=({style:componentStyle,children,...useDraggableHookOptions})=>{const{animatedViewProps,gesture,state,hasHandle,animatedViewRef}=useDraggable(
|
|
1
|
+
import React,{createContext,useContext,useEffect}from"react";import Animated from"react-native-reanimated";import{GestureDetector}from"react-native-gesture-handler";import{useDraggable}from"../hooks/useDraggable";const DraggableContext=createContext(null);const Handle=({children,style})=>{const draggableContext=useContext(DraggableContext);useEffect((()=>{draggableContext===null||draggableContext===void 0?void 0:draggableContext.registerHandle(true);return()=>{draggableContext===null||draggableContext===void 0?void 0:draggableContext.registerHandle(false)}}),[draggableContext]);if(!draggableContext){console.warn("Draggable.Handle must be used within a Draggable component");return React.createElement(React.Fragment,null,children)}return React.createElement(GestureDetector,{gesture:draggableContext.gesture},React.createElement(Animated.View,{style},children))};const DraggableComponent=({style:componentStyle,children,...useDraggableHookOptions})=>{const{animatedViewProps,gesture,state,hasHandle,animatedViewRef,registerHandle}=useDraggable(useDraggableHookOptions);const contextValue={gesture,state,registerHandle};const content=React.createElement(Animated.View,{ref:animatedViewRef,...animatedViewProps,style:[componentStyle,animatedViewProps.style],collapsable:false},React.createElement(DraggableContext.Provider,{value:contextValue},children));if(hasHandle){return content}else{return React.createElement(GestureDetector,{gesture},content)}};export const Draggable=Object.assign(DraggableComponent,{Handle});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { DroppableProps } from "../types/droppable";
|
|
3
|
-
export declare const _getUniqueDroppableId: () => number;
|
|
4
3
|
export declare const Droppable: <TData = unknown>({ onDrop, dropDisabled, onActiveChange, dropAlignment, dropOffset, activeStyle, droppableId, capacity, style, children, }: DroppableProps<TData>) => React.ReactElement;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import React from"react";import Animated from"react-native-reanimated";import{useDroppable}from"../hooks/useDroppable";
|
|
1
|
+
import React from"react";import Animated from"react-native-reanimated";import{useDroppable}from"../hooks/useDroppable";export const Droppable=({onDrop,dropDisabled,onActiveChange,dropAlignment,dropOffset,activeStyle,droppableId,capacity,style,children})=>{const{viewProps,animatedViewRef}=useDroppable({onDrop,dropDisabled,onActiveChange,dropAlignment,dropOffset,activeStyle,droppableId,capacity});return React.createElement(Animated.View,{ref:animatedViewRef,...viewProps,style:[style,viewProps.style],collapsable:false},children)};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import React,{memo,useCallback}from"react";import{StyleSheet}from"react-native";import Animated from"react-native-reanimated";import{GestureHandlerRootView,FlatList,ScrollView}from"react-native-gesture-handler";import{DropProvider}from"../context/DropContext";import{SortableDirection}from"../types/sortable";import{useSortableList}from"../hooks/useSortableList";import{useHorizontalSortableList}from"../hooks/useHorizontalSortableList";import{dataHash}from"./sortableUtils";const AnimatedFlatList=Animated.createAnimatedComponent(FlatList);const AnimatedScrollView=Animated.createAnimatedComponent(ScrollView);function SortableComponent({data,renderItem,direction=SortableDirection.Vertical,itemHeight,itemWidth,gap=0,paddingHorizontal=0,style,contentContainerStyle,itemKeyExtractor=item=>item.id,useFlatList=true}){if(direction===SortableDirection.Vertical&&!itemHeight){throw new Error("itemHeight is required when direction is vertical")}if(direction===SortableDirection.Horizontal&&!itemWidth){throw new Error("itemWidth is required when direction is horizontal")}if(direction===SortableDirection.Horizontal){
|
|
1
|
+
import React,{memo,useCallback,useMemo}from"react";import{StyleSheet}from"react-native";import Animated from"react-native-reanimated";import{GestureHandlerRootView,FlatList,ScrollView}from"react-native-gesture-handler";import{DropProvider}from"../context/DropContext";import{SortableDirection}from"../types/sortable";import{useSortableList}from"../hooks/useSortableList";import{useHorizontalSortableList}from"../hooks/useHorizontalSortableList";import{dataHash}from"./sortableUtils";const AnimatedFlatList=Animated.createAnimatedComponent(FlatList);const AnimatedScrollView=Animated.createAnimatedComponent(ScrollView);function SortableComponent({data,renderItem,direction=SortableDirection.Vertical,itemHeight,itemWidth,gap=0,paddingHorizontal=0,enableDynamicHeights=false,estimatedItemHeight=60,onHeightsMeasured,style,contentContainerStyle,itemKeyExtractor=item=>item.id,useFlatList=true}){const isDynamicHeightMode=useMemo((()=>{if(direction===SortableDirection.Horizontal){return false}if(enableDynamicHeights){return true}if(typeof itemHeight==="number"){return false}if(itemHeight===undefined){return false}return true}),[enableDynamicHeights,itemHeight,direction]);if(direction===SortableDirection.Vertical&&!isDynamicHeightMode&&!itemHeight){throw new Error("itemHeight is required when direction is vertical and not using dynamic heights")}if(direction===SortableDirection.Horizontal&&!itemWidth){throw new Error("itemWidth is required when direction is horizontal")}if(direction===SortableDirection.Horizontal){return React.createElement(HorizontalSortableContent,{data,renderItem,direction,itemWidth,gap,paddingHorizontal,style,contentContainerStyle,itemKeyExtractor,useFlatList})}return React.createElement(VerticalSortableContent,{data,renderItem,direction,itemHeight,enableDynamicHeights,estimatedItemHeight,onHeightsMeasured,style,contentContainerStyle,itemKeyExtractor,useFlatList})}function VerticalSortableContent({data,renderItem,direction,itemHeight,enableDynamicHeights,estimatedItemHeight,onHeightsMeasured,style,contentContainerStyle,itemKeyExtractor,useFlatList}){const{scrollViewRef,dropProviderRef,handleScroll,handleScrollEnd,contentHeight,getItemProps}=useSortableList({data,itemHeight,enableDynamicHeights,estimatedItemHeight,onHeightsMeasured,itemKeyExtractor});const memoizedVerticalRenderItem=useCallback((({item,index})=>{const itemProps=getItemProps(item,index);const sortableItemProps={item,index,direction:SortableDirection.Vertical,...itemProps};return renderItem(sortableItemProps)}),[getItemProps,renderItem]);return React.createElement(GestureHandlerRootView,{style:styles.flex},React.createElement(DropProvider,{ref:dropProviderRef},useFlatList?React.createElement(AnimatedFlatList,{ref:scrollViewRef,data,keyExtractor:itemKeyExtractor,renderItem:memoizedVerticalRenderItem,onScroll:handleScroll,scrollEventThrottle:16,style:[styles.scrollView,style],contentContainerStyle:[{height:contentHeight},contentContainerStyle],onScrollEndDrag:handleScrollEnd,onMomentumScrollEnd:handleScrollEnd,simultaneousHandlers:dropProviderRef,showsVerticalScrollIndicator:false}):React.createElement(AnimatedScrollView,{ref:scrollViewRef,onScroll:handleScroll,scrollEventThrottle:16,style:[styles.scrollView,style],contentContainerStyle:[{height:contentHeight},contentContainerStyle],onScrollEndDrag:handleScrollEnd,onMomentumScrollEnd:handleScrollEnd,simultaneousHandlers:dropProviderRef},data.map(((item,index)=>{const itemProps=getItemProps(item,index);const sortableItemProps={item,index,direction:SortableDirection.Vertical,...itemProps};return renderItem(sortableItemProps)})))))}function HorizontalSortableContent({data,renderItem,direction,itemWidth,gap=0,paddingHorizontal=0,style,contentContainerStyle,itemKeyExtractor,useFlatList}){const{scrollViewRef,dropProviderRef,handleScroll,handleScrollEnd,contentWidth,getItemProps}=useHorizontalSortableList({data,itemWidth,gap,paddingHorizontal,itemKeyExtractor});const memoizedHorizontalRenderItem=useCallback((({item,index})=>{const itemProps=getItemProps(item,index);const sortableItemProps={item,index,direction:SortableDirection.Horizontal,autoScrollHorizontalDirection:itemProps.autoScrollDirection,...itemProps};return renderItem(sortableItemProps)}),[getItemProps,renderItem]);return React.createElement(GestureHandlerRootView,{style:styles.flex},React.createElement(DropProvider,{ref:dropProviderRef},useFlatList?React.createElement(AnimatedFlatList,{ref:scrollViewRef,data,keyExtractor:itemKeyExtractor,horizontal:true,renderItem:memoizedHorizontalRenderItem,onScroll:handleScroll,scrollEventThrottle:16,style:[styles.scrollView,style],contentContainerStyle:[{width:contentWidth},contentContainerStyle],onScrollEndDrag:handleScrollEnd,onMomentumScrollEnd:handleScrollEnd,simultaneousHandlers:dropProviderRef,showsHorizontalScrollIndicator:false}):React.createElement(AnimatedScrollView,{ref:scrollViewRef,onScroll:handleScroll,scrollEventThrottle:16,horizontal:true,style:[styles.scrollView,style],contentContainerStyle:[{width:contentWidth},contentContainerStyle],onScrollEndDrag:handleScrollEnd,onMomentumScrollEnd:handleScrollEnd,simultaneousHandlers:dropProviderRef,showsHorizontalScrollIndicator:false},data.map(((item,index)=>{const itemProps=getItemProps(item,index);const sortableItemProps={item,index,direction:SortableDirection.Horizontal,autoScrollHorizontalDirection:itemProps.autoScrollDirection,...itemProps};return renderItem(sortableItemProps)})))))}export const Sortable=memo((({data,renderItem,...props})=>{const dataHashKey=dataHash(data);return React.createElement(SortableComponent,{data,renderItem,...props,key:dataHashKey})}));const styles=StyleSheet.create({flex:{flex:1},scrollView:{flex:1,position:"relative",backgroundColor:"white"}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import React,{memo,useCallback}from"react";import{StyleSheet,View}from"react-native";import Animated from"react-native-reanimated";import{GestureHandlerRootView,ScrollView}from"react-native-gesture-handler";import{DropProvider}from"../context/DropContext";import{GridOrientation,GridStrategy}from"../types/grid";import{useGridSortableList}from"../hooks/useGridSortableList";const AnimatedScrollView=Animated.createAnimatedComponent(ScrollView);function SortableGridComponent({data,renderItem,dimensions,orientation=GridOrientation.Vertical,strategy=GridStrategy.Insert,style,contentContainerStyle,itemKeyExtractor=item=>item.id,scrollEnabled=true}){if(!dimensions.itemWidth||!dimensions.itemHeight){throw new Error("SortableGrid requires itemWidth and itemHeight in dimensions")}if(orientation===GridOrientation.Vertical&&!dimensions.columns){throw new Error("SortableGrid requires columns in dimensions when orientation is vertical")}if(orientation===GridOrientation.Horizontal&&!dimensions.rows){throw new Error("SortableGrid requires rows in dimensions when orientation is horizontal")}const{scrollViewRef,dropProviderRef,handleScroll,handleScrollEnd,contentWidth,contentHeight,getItemProps}=useGridSortableList({data,dimensions,orientation,strategy,itemKeyExtractor});const renderGridItems=useCallback((()=>data.map(((item,index)=>{const itemProps=getItemProps(item,index);const renderItemProps={item,index,...itemProps};return renderItem(renderItemProps)}))),[data,getItemProps,renderItem]);return React.createElement(GestureHandlerRootView,{style:styles.flex},React.createElement(DropProvider,{ref:dropProviderRef},React.createElement(AnimatedScrollView,{ref:scrollViewRef,onScroll:handleScroll,scrollEventThrottle:16,scrollEnabled,style:[styles.scrollView,style],contentContainerStyle,onScrollEndDrag:handleScrollEnd,onMomentumScrollEnd:handleScrollEnd,simultaneousHandlers:dropProviderRef,showsVerticalScrollIndicator:false,showsHorizontalScrollIndicator:false},React.createElement(View,{style:{width:contentWidth,height:contentHeight,position:"relative"}},renderGridItems()))))}export const SortableGrid=memo((({data,renderItem,...props})=>React.createElement(SortableGridComponent,{data,renderItem,...props})));const styles=StyleSheet.create({flex:{flex:1},scrollView:{flex:1,position:"relative"}});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { SortableGridItemProps } from "../types/grid";
|
|
3
|
+
import { SortableHandleProps } from "../types/sortable";
|
|
4
|
+
export declare function SortableGridItem<T>({ id, data, positions, scrollY, scrollX, autoScrollDirection, itemsCount, dimensions, orientation, strategy, containerWidth, containerHeight, activationDelay, children, style, animatedStyle: customAnimatedStyle, onMove, onDragStart, onDrop, onDragging, isBeingRemoved, }: SortableGridItemProps<T>): React.JSX.Element;
|
|
5
|
+
export declare namespace SortableGridItem {
|
|
6
|
+
var Handle: ({ children, style }: SortableHandleProps) => React.JSX.Element;
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import React,{createContext,useContext,useEffect}from"react";import Animated from"react-native-reanimated";import{GestureDetector}from"react-native-gesture-handler";import{useGridSortable}from"../hooks/useGridSortable";import{GridStrategy}from"../types/grid";const SortableGridContext=createContext(null);const SortableGridHandle=({children,style})=>{const gridContext=useContext(SortableGridContext);useEffect((()=>{gridContext===null||gridContext===void 0?void 0:gridContext.registerHandle(true);return()=>{gridContext===null||gridContext===void 0?void 0:gridContext.registerHandle(false)}}),[gridContext]);if(!gridContext){console.warn("SortableGridHandle must be used within a SortableGridItem component");return React.createElement(React.Fragment,null,children)}return React.createElement(GestureDetector,{gesture:gridContext.panGestureHandler},React.createElement(Animated.View,{style},children))};function renderSortableGridContent(animatedStyle,customAnimatedStyle,style,children,panGestureHandler,handlePanGestureHandler,registerHandle){return React.createElement(Animated.View,{style:[animatedStyle,customAnimatedStyle]},React.createElement(GestureDetector,{gesture:panGestureHandler},React.createElement(Animated.View,{style:{flex:1}},React.createElement(SortableGridContext.Provider,{value:{panGestureHandler:handlePanGestureHandler,registerHandle}},React.createElement(Animated.View,{style:[{flex:1},style]},children)))))}export function SortableGridItem({id,data,positions,scrollY,scrollX,autoScrollDirection,itemsCount,dimensions,orientation,strategy=GridStrategy.Insert,containerWidth,containerHeight,activationDelay,children,style,animatedStyle:customAnimatedStyle,onMove,onDragStart,onDrop,onDragging,isBeingRemoved}){const gridSortableOptions={id,positions,scrollY,scrollX,autoScrollDirection,itemsCount,dimensions,orientation,strategy,containerWidth,containerHeight,activationDelay,onMove,onDragStart,onDrop,onDragging,isBeingRemoved};const{animatedStyle,panGestureHandler,handlePanGestureHandler,registerHandle}=useGridSortable(gridSortableOptions);return renderSortableGridContent(animatedStyle,customAnimatedStyle,style,children,panGestureHandler,handlePanGestureHandler,registerHandle)}SortableGridItem.Handle=SortableGridHandle;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
3
|
-
export declare function SortableItem<T>({
|
|
2
|
+
import { SortableHandleProps, SortableItemProps } from "../types/sortable";
|
|
3
|
+
export declare function SortableItem<T>({ direction, ...props }: SortableItemProps<T>): React.JSX.Element;
|
|
4
4
|
export declare namespace SortableItem {
|
|
5
5
|
var Handle: ({ children, style }: SortableHandleProps) => React.JSX.Element;
|
|
6
6
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import React,{createContext,useContext}from"react";import Animated from"react-native-reanimated";import{
|
|
1
|
+
import React,{createContext,useCallback,useContext,useEffect}from"react";import Animated from"react-native-reanimated";import{GestureDetector}from"react-native-gesture-handler";import{useHorizontalSortable}from"../hooks/useHorizontalSortable";import{useSortable}from"../hooks/useSortable";import{SortableDirection}from"../types/sortable";const SortableContext=createContext(null);const SortableHandle=({children,style})=>{const sortableContext=useContext(SortableContext);useEffect((()=>{sortableContext===null||sortableContext===void 0?void 0:sortableContext.registerHandle(true);return()=>{sortableContext===null||sortableContext===void 0?void 0:sortableContext.registerHandle(false)}}),[sortableContext]);if(!sortableContext){console.warn("SortableHandle must be used within a SortableItem component");return React.createElement(React.Fragment,null,children)}return React.createElement(GestureDetector,{gesture:sortableContext.panGestureHandler},React.createElement(Animated.View,{style},children))};function renderSortableContent(animatedStyle,customAnimatedStyle,style,children,panGestureHandler,handlePanGestureHandler,registerHandle,onLayout){const content=React.createElement(Animated.View,{style:[animatedStyle,customAnimatedStyle],onLayout},React.createElement(SortableContext.Provider,{value:{panGestureHandler:handlePanGestureHandler,registerHandle}},React.createElement(Animated.View,{style},children)));return React.createElement(GestureDetector,{gesture:panGestureHandler},content)}function VerticalSortableItemInner({id,positions,lowerBound,autoScrollDirection,itemsCount,itemHeight,containerHeight,isDynamicHeight=false,estimatedItemHeight,itemHeights,scheduleHeightUpdate,children,style,animatedStyle:customAnimatedStyle,onMove,onDragStart,onDrop,onDragging}){const{animatedStyle,panGestureHandler,handlePanGestureHandler,registerHandle}=useSortable({id,positions,lowerBound,autoScrollDirection,itemsCount,itemHeight,containerHeight,estimatedItemHeight,isDynamicHeight,itemHeights,onMove,onDragStart,onDrop,onDragging});const handleLayout=useCallback((event=>{if(!isDynamicHeight||!scheduleHeightUpdate){return}const{height}=event.nativeEvent.layout;scheduleHeightUpdate(id,height)}),[id,isDynamicHeight,scheduleHeightUpdate]);return renderSortableContent(animatedStyle,customAnimatedStyle,style,children,panGestureHandler,handlePanGestureHandler,registerHandle,isDynamicHeight&&scheduleHeightUpdate?handleLayout:undefined)}function HorizontalSortableItemInner({id,positions,leftBound,autoScrollHorizontalDirection,itemsCount,itemWidth,gap=0,paddingHorizontal=0,containerWidth,children,style,animatedStyle:customAnimatedStyle,onMove,onDragStart,onDrop,onDraggingHorizontal}){const{animatedStyle,panGestureHandler,handlePanGestureHandler,registerHandle}=useHorizontalSortable({id,positions,leftBound,autoScrollDirection:autoScrollHorizontalDirection,itemsCount,itemWidth,gap,paddingHorizontal,containerWidth,onMove,onDragStart,onDrop,onDragging:onDraggingHorizontal});return renderSortableContent(animatedStyle,customAnimatedStyle,style,children,panGestureHandler,handlePanGestureHandler,registerHandle)}export function SortableItem({direction=SortableDirection.Vertical,...props}){if(direction===SortableDirection.Vertical&&!props.isDynamicHeight&&!props.itemHeight&&(!props.lowerBound||!props.autoScrollDirection)){throw new Error("itemHeight (or isDynamicHeight), lowerBound, and autoScrollDirection are required for vertical direction")}if(direction===SortableDirection.Horizontal&&(!props.itemWidth||!props.leftBound||!props.autoScrollHorizontalDirection)){throw new Error("itemWidth, leftBound, and autoScrollHorizontalDirection are required for horizontal direction")}if(direction===SortableDirection.Horizontal){return React.createElement(HorizontalSortableItemInner,{...props,direction,itemWidth:props.itemWidth,leftBound:props.leftBound,autoScrollHorizontalDirection:props.autoScrollHorizontalDirection})}return React.createElement(VerticalSortableItemInner,{...props,direction,lowerBound:props.lowerBound,autoScrollDirection:props.autoScrollDirection})}SortableItem.Handle=SortableHandle;
|
|
@@ -30,4 +30,25 @@ export declare function setHorizontalPosition(positionX: number, itemsCount: num
|
|
|
30
30
|
[id: string]: number;
|
|
31
31
|
}>, id: string, itemWidth: number, gap?: number, paddingHorizontal?: number): void;
|
|
32
32
|
export declare function setHorizontalAutoScroll(positionX: number, leftBound: number, rightBound: number, scrollThreshold: number, autoScrollDirection: SharedValue<HorizontalScrollDirection>): void;
|
|
33
|
+
export declare function resolveItemHeight<TData>(itemHeightProp: number | number[] | ((item: TData, index: number) => number) | undefined, item: TData, index: number, fallback: number): number;
|
|
34
|
+
export declare function recalculateCumulativeHeights(positions: {
|
|
35
|
+
[id: string]: number;
|
|
36
|
+
}, itemHeights: {
|
|
37
|
+
[id: string]: number;
|
|
38
|
+
}, estimatedHeight: number): {
|
|
39
|
+
cumulative: {
|
|
40
|
+
[id: string]: number;
|
|
41
|
+
};
|
|
42
|
+
total: number;
|
|
43
|
+
};
|
|
44
|
+
export declare function findPositionForY(positionY: number, positions: {
|
|
45
|
+
[id: string]: number;
|
|
46
|
+
}, itemHeights: {
|
|
47
|
+
[id: string]: number;
|
|
48
|
+
}, estimatedHeight: number, itemsCount: number): number;
|
|
49
|
+
export declare function getItemCumulativeY(id: string, positions: {
|
|
50
|
+
[id: string]: number;
|
|
51
|
+
}, itemHeights: {
|
|
52
|
+
[id: string]: number;
|
|
53
|
+
}, estimatedHeight: number): number;
|
|
33
54
|
export declare const dataHash: (data: any[]) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export var ScrollDirection;(function(ScrollDirection){ScrollDirection["None"]="none";ScrollDirection["Up"]="up";ScrollDirection["Down"]="down"})(ScrollDirection||(ScrollDirection={}));export var HorizontalScrollDirection;(function(HorizontalScrollDirection){HorizontalScrollDirection["None"]="none";HorizontalScrollDirection["Left"]="left";HorizontalScrollDirection["Right"]="right"})(HorizontalScrollDirection||(HorizontalScrollDirection={}));export function clamp(value,lowerBound,upperBound){"worklet";return Math.max(lowerBound,Math.min(value,upperBound))}export function objectMove(object,from,to){"worklet";const newObject=Object.assign({},object);for(const id in object){if(object[id]===from){newObject[id]=to}if(object[id]===to){newObject[id]=from}}return newObject}export function listToObject(list){const values=Object.values(list);const object={};for(let i=0;i<values.length;i++){object[values[i].id]=i}return object}export function setPosition(positionY,itemsCount,positions,id,itemHeight){"worklet";const newPosition=clamp(Math.floor(positionY/itemHeight),0,itemsCount-1);if(newPosition!==positions.value[id]){positions.value=objectMove(positions.value,positions.value[id],newPosition)}}export function setAutoScroll(positionY,lowerBound,upperBound,scrollThreshold,autoScroll){"worklet";if(positionY<=lowerBound+scrollThreshold){autoScroll.value=ScrollDirection.Up}else if(positionY>=upperBound-scrollThreshold){autoScroll.value=ScrollDirection.Down}else{autoScroll.value=ScrollDirection.None}}export function getItemXPosition(position,itemWidth,gap=0,paddingHorizontal=0){"worklet";return paddingHorizontal+position*(itemWidth+gap)}export function getContentWidth(itemsCount,itemWidth,gap=0,paddingHorizontal=0){"worklet";if(itemsCount===0){return paddingHorizontal*2}const totalItemsWidth=itemsCount*itemWidth;const totalGaps=Math.max(0,itemsCount-1)*gap;return totalItemsWidth+totalGaps+paddingHorizontal*2}export function setHorizontalPosition(positionX,itemsCount,positions,id,itemWidth,gap=0,paddingHorizontal=0){"worklet";const adjustedX=positionX-paddingHorizontal;const itemWithGapWidth=itemWidth+gap;const newPosition=clamp(Math.floor(adjustedX/itemWithGapWidth),0,itemsCount-1);if(newPosition!==positions.value[id]){positions.value=objectMove(positions.value,positions.value[id],newPosition)}}export function setHorizontalAutoScroll(positionX,leftBound,rightBound,scrollThreshold,autoScrollDirection){"worklet";const effectiveThreshold=Math.max(scrollThreshold,60);const leftEdge=leftBound+effectiveThreshold;const rightEdge=rightBound-effectiveThreshold;if(positionX<leftEdge){autoScrollDirection.value=HorizontalScrollDirection.Left}else if(positionX>rightEdge){autoScrollDirection.value=HorizontalScrollDirection.Right}else{autoScrollDirection.value=HorizontalScrollDirection.None}}export const dataHash=data=>{const str=data.reduce(((acc,item)=>acc+item.id),"");let hash=0;for(let i=0,len=str.length;i<len;i++){let chr=str.charCodeAt(i);hash=(hash<<5)-hash+chr;hash|=0}return hash.toString()};
|
|
1
|
+
export var ScrollDirection;(function(ScrollDirection){ScrollDirection["None"]="none";ScrollDirection["Up"]="up";ScrollDirection["Down"]="down"})(ScrollDirection||(ScrollDirection={}));export var HorizontalScrollDirection;(function(HorizontalScrollDirection){HorizontalScrollDirection["None"]="none";HorizontalScrollDirection["Left"]="left";HorizontalScrollDirection["Right"]="right"})(HorizontalScrollDirection||(HorizontalScrollDirection={}));export function clamp(value,lowerBound,upperBound){"worklet";return Math.max(lowerBound,Math.min(value,upperBound))}export function objectMove(object,from,to){"worklet";const newObject=Object.assign({},object);for(const id in object){if(object[id]===from){newObject[id]=to}if(object[id]===to){newObject[id]=from}}return newObject}export function listToObject(list){const values=Object.values(list);const object={};for(let i=0;i<values.length;i++){object[values[i].id]=i}return object}export function setPosition(positionY,itemsCount,positions,id,itemHeight){"worklet";const newPosition=clamp(Math.floor(positionY/itemHeight),0,itemsCount-1);if(newPosition!==positions.value[id]){positions.value=objectMove(positions.value,positions.value[id],newPosition)}}export function setAutoScroll(positionY,lowerBound,upperBound,scrollThreshold,autoScroll){"worklet";if(positionY<=lowerBound+scrollThreshold){autoScroll.value=ScrollDirection.Up}else if(positionY>=upperBound-scrollThreshold){autoScroll.value=ScrollDirection.Down}else{autoScroll.value=ScrollDirection.None}}export function getItemXPosition(position,itemWidth,gap=0,paddingHorizontal=0){"worklet";return paddingHorizontal+position*(itemWidth+gap)}export function getContentWidth(itemsCount,itemWidth,gap=0,paddingHorizontal=0){"worklet";if(itemsCount===0){return paddingHorizontal*2}const totalItemsWidth=itemsCount*itemWidth;const totalGaps=Math.max(0,itemsCount-1)*gap;return totalItemsWidth+totalGaps+paddingHorizontal*2}export function setHorizontalPosition(positionX,itemsCount,positions,id,itemWidth,gap=0,paddingHorizontal=0){"worklet";const adjustedX=positionX-paddingHorizontal;const itemWithGapWidth=itemWidth+gap;const newPosition=clamp(Math.floor(adjustedX/itemWithGapWidth),0,itemsCount-1);if(newPosition!==positions.value[id]){positions.value=objectMove(positions.value,positions.value[id],newPosition)}}export function setHorizontalAutoScroll(positionX,leftBound,rightBound,scrollThreshold,autoScrollDirection){"worklet";const effectiveThreshold=Math.max(scrollThreshold,60);const leftEdge=leftBound+effectiveThreshold;const rightEdge=rightBound-effectiveThreshold;if(positionX<leftEdge){autoScrollDirection.value=HorizontalScrollDirection.Left}else if(positionX>rightEdge){autoScrollDirection.value=HorizontalScrollDirection.Right}else{autoScrollDirection.value=HorizontalScrollDirection.None}}export function resolveItemHeight(itemHeightProp,item,index,fallback){var _a;if(typeof itemHeightProp==="number"){return itemHeightProp}if(Array.isArray(itemHeightProp)){return(_a=itemHeightProp[index])!==null&&_a!==void 0?_a:fallback}if(typeof itemHeightProp==="function"){return itemHeightProp(item,index)}return fallback}export function recalculateCumulativeHeights(positions,itemHeights,estimatedHeight){"worklet";var _a;let count=0;for(const _id in positions){count++}const idsByPosition=new Array(count);for(const id in positions){idsByPosition[positions[id]]=id}const cumulative={};let total=0;for(let i=0;i<count;i++){const itemId=idsByPosition[i];if(itemId!==undefined){cumulative[itemId]=total;total+=(_a=itemHeights[itemId])!==null&&_a!==void 0?_a:estimatedHeight}}return{cumulative,total}}export function findPositionForY(positionY,positions,itemHeights,estimatedHeight,itemsCount){"worklet";var _a;let count=0;for(const _id in positions){count++}const idsByPosition=new Array(count);for(const id in positions){idsByPosition[positions[id]]=id}let cumY=0;let result=0;for(let i=0;i<count;i++){const itemId=idsByPosition[i];if(itemId!==undefined){if(positionY>=cumY){result=i}cumY+=(_a=itemHeights[itemId])!==null&&_a!==void 0?_a:estimatedHeight}}return clamp(result,0,itemsCount-1)}export function getItemCumulativeY(id,positions,itemHeights,estimatedHeight){"worklet";var _a,_b;const targetPos=(_a=positions[id])!==null&&_a!==void 0?_a:0;let y=0;for(const itemId in positions){if(positions[itemId]<targetPos){y+=(_b=itemHeights[itemId])!==null&&_b!==void 0?_b:estimatedHeight}}return y}export const dataHash=data=>{const str=data.reduce(((acc,item)=>acc+item.id),"");let hash=0;for(let i=0,len=str.length;i<len;i++){let chr=str.charCodeAt(i);hash=(hash<<5)-hash+chr;hash|=0}return hash.toString()};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import React,{useRef,useState,useMemo,useCallback,forwardRef,useImperativeHandle
|
|
1
|
+
import React,{useRef,useState,useEffect,useMemo,useCallback,forwardRef,useImperativeHandle}from"react";import{SlotsContext}from"../types/context";export const DropProvider=forwardRef((({children,onLayoutUpdateComplete,onDroppedItemsUpdate,onDragging,onDragStart,onDragEnd},ref)=>{const slotsRef=useRef({});const[activeHoverSlotId,setActiveHoverSlotIdState]=useState(null);const[droppedItems,setDroppedItems]=useState({});const positionUpdateListenersRef=useRef({});const registerPositionUpdateListener=useCallback(((id,listener)=>{positionUpdateListenersRef.current[id]=listener}),[]);const unregisterPositionUpdateListener=useCallback((id=>{delete positionUpdateListenersRef.current[id]}),[]);const updateDroppedItems=useCallback((updater=>{setDroppedItems((currentItems=>updater(currentItems)))}),[]);useEffect((()=>{onDroppedItemsUpdate===null||onDroppedItemsUpdate===void 0?void 0:onDroppedItemsUpdate(droppedItems)}),[droppedItems,onDroppedItemsUpdate]);const registerDroppedItem=useCallback(((draggableId,droppableId,itemData)=>{updateDroppedItems((prev=>({...prev,[draggableId]:{droppableId,data:itemData}})))}),[updateDroppedItems]);const unregisterDroppedItem=useCallback((draggableId=>{updateDroppedItems((prev=>{const newItems={...prev};delete newItems[draggableId];return newItems}))}),[updateDroppedItems]);const getDroppedItems=useCallback((()=>droppedItems),[droppedItems]);const internalRequestPositionUpdate=useCallback((()=>{const listeners=positionUpdateListenersRef.current;Object.values(listeners).forEach((listener=>{listener()}));onLayoutUpdateComplete===null||onLayoutUpdateComplete===void 0?void 0:onLayoutUpdateComplete()}),[onLayoutUpdateComplete]);useImperativeHandle(ref,(()=>({requestPositionUpdate:internalRequestPositionUpdate,getDroppedItems})));const hasAvailableCapacity=useCallback((droppableId=>{const droppedCount=Object.values(droppedItems).filter((item=>item.droppableId===droppableId)).length;const droppableSlot=Object.values(slotsRef.current).find((slot=>slot.id===droppableId));if(!droppableSlot){return false}const capacity=droppableSlot.capacity!==undefined?droppableSlot.capacity:1;return droppedCount<capacity}),[droppedItems]);const handleDragStart=useCallback((data=>{if(onDragStart){onDragStart(data)}internalRequestPositionUpdate()}),[onDragStart,internalRequestPositionUpdate]);const contextValue=useMemo((()=>({register:(id,slot)=>{slotsRef.current[id]=slot},unregister:id=>{delete slotsRef.current[id]},isRegistered:id=>slotsRef.current[id]!==undefined,getSlots:()=>slotsRef.current,setActiveHoverSlot:id=>setActiveHoverSlotIdState(id),activeHoverSlotId,registerPositionUpdateListener,unregisterPositionUpdateListener,requestPositionUpdate:internalRequestPositionUpdate,registerDroppedItem,unregisterDroppedItem,getDroppedItems,hasAvailableCapacity,onDragging,onDragStart:handleDragStart,onDragEnd})),[activeHoverSlotId,registerPositionUpdateListener,unregisterPositionUpdateListener,internalRequestPositionUpdate,registerDroppedItem,unregisterDroppedItem,getDroppedItems,hasAvailableCapacity,onDragging,handleDragStart,onDragEnd]);return React.createElement(SlotsContext.Provider,{value:contextValue},children)}));DropProvider.displayName="DropProvider";
|
package/lib/hooks/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { useSortable } from "./useSortable";
|
|
2
2
|
export { useSortableList } from "./useSortableList";
|
|
3
|
+
export { useHorizontalSortable } from "./useHorizontalSortable";
|
|
4
|
+
export { useHorizontalSortableList } from "./useHorizontalSortableList";
|
|
3
5
|
export { useDraggable } from "./useDraggable";
|
|
4
6
|
export { useDroppable } from "./useDroppable";
|
package/lib/hooks/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{useSortable}from"./useSortable";export{useSortableList}from"./useSortableList";export{useDraggable}from"./useDraggable";export{useDroppable}from"./useDroppable";
|
|
1
|
+
export{useSortable}from"./useSortable";export{useSortableList}from"./useSortableList";export{useHorizontalSortable}from"./useHorizontalSortable";export{useHorizontalSortableList}from"./useHorizontalSortableList";export{useDraggable}from"./useDraggable";export{useDroppable}from"./useDroppable";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{measure}from"react-native-reanimated";export const safeMeasure=ref=>{"worklet";try{const measurement=measure(ref);if(!measurement){return null}if(measurement.width<=0||measurement.height<=0){return null}return measurement}catch(_a){return null}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import React,{useRef,useCallback,useContext,useEffect,useState}from"react";import{useSharedValue,useAnimatedStyle,withSpring,runOnJS,runOnUI,useAnimatedReaction,useAnimatedRef,measure}from"react-native-reanimated";import{Gesture}from"react-native-gesture-handler";import{SlotsContext}from"../types/context";import{DraggableState}from"../types/draggable";export const useDraggable=options=>{const{data,draggableId,dragDisabled=false,onDragStart,onDragEnd,onDragging,onStateChange,animationFunction,dragBoundsRef,dragAxis="both",collisionAlgorithm="intersect",children,handleComponent}=options;const animatedViewRef=useAnimatedRef();const[state,setState]=useState(DraggableState.IDLE);const[hasHandle,setHasHandle]=useState(false);useEffect((()=>{if(!children||!handleComponent){setHasHandle(false);return}const checkForHandle=child=>{if(React.isValidElement(child)){if(child.type===handleComponent){return true}if(child.props&&child.props.children){if(React.Children.toArray(child.props.children).some(checkForHandle)){return true}}}return false};setHasHandle(React.Children.toArray(children).some(checkForHandle))}),[children,handleComponent]);useEffect((()=>{onStateChange===null||onStateChange===void 0?void 0:onStateChange(state)}),[state,onStateChange]);const tx=useSharedValue(0);const ty=useSharedValue(0);const offsetX=useSharedValue(0);const offsetY=useSharedValue(0);const dragDisabledShared=useSharedValue(dragDisabled);const dragAxisShared=useSharedValue(dragAxis);const originX=useSharedValue(0);const originY=useSharedValue(0);const itemW=useSharedValue(0);const itemH=useSharedValue(0);const isOriginSet=useRef(false);const internalDraggableId=useRef(draggableId||`draggable-${Math.random().toString(36).substr(2,9)}`).current;const boundsX=useSharedValue(0);const boundsY=useSharedValue(0);const boundsWidth=useSharedValue(0);const boundsHeight=useSharedValue(0);const boundsAreSet=useSharedValue(false);const{getSlots,setActiveHoverSlot,activeHoverSlotId,registerPositionUpdateListener,unregisterPositionUpdateListener,registerDroppedItem,unregisterDroppedItem,hasAvailableCapacity,onDragging:contextOnDragging,onDragStart:contextOnDragStart,onDragEnd:contextOnDragEnd}=useContext(SlotsContext);useEffect((()=>{dragDisabledShared.value=dragDisabled}),[dragDisabled,dragDisabledShared]);useEffect((()=>{dragAxisShared.value=dragAxis}),[dragAxis,dragAxisShared]);const updateDraggablePosition=useCallback((()=>{runOnUI((()=>{"worklet";const measurement=measure(animatedViewRef);if(measurement===null){return}const currentTx=tx.value;const currentTy=ty.value;if(currentTx===0&¤tTy===0){const newOriginX=measurement.pageX-currentTx;const newOriginY=measurement.pageY-currentTy;originX.value=newOriginX;originY.value=newOriginY}itemW.value=measurement.width;itemH.value=measurement.height;if(!isOriginSet.current){isOriginSet.current=true}}))()}),[animatedViewRef,originX,originY,itemW,itemH,tx,ty]);const updateDraggablePositionWorklet=useCallback((()=>{"worklet";const measurement=measure(animatedViewRef);if(measurement===null){return}const currentTx=tx.value;const currentTy=ty.value;if(currentTx===0&¤tTy===0){const newOriginX=measurement.pageX-currentTx;const newOriginY=measurement.pageY-currentTy;originX.value=newOriginX;originY.value=newOriginY}itemW.value=measurement.width;itemH.value=measurement.height;if(!isOriginSet.current){isOriginSet.current=true}}),[animatedViewRef,originX,originY,itemW,itemH,tx,ty]);const updateBounds=useCallback((()=>{const currentBoundsView=dragBoundsRef===null||dragBoundsRef===void 0?void 0:dragBoundsRef.current;if(currentBoundsView){currentBoundsView.measure(((_x,_y,width,height,pageX,pageY)=>{if(typeof pageX==="number"&&typeof pageY==="number"&&width>0&&height>0){runOnUI((()=>{"worklet";boundsX.value=pageX;boundsY.value=pageY;boundsWidth.value=width;boundsHeight.value=height;if(!boundsAreSet.value){boundsAreSet.value=true}}))()}else{console.warn("useDraggable: dragBoundsRef measurement failed or returned invalid dimensions. Bounds may be stale or item unbounded.")}}))}else{runOnUI((()=>{"worklet";if(boundsAreSet.value){boundsAreSet.value=false}}))()}}),[dragBoundsRef,boundsX,boundsY,boundsWidth,boundsHeight,boundsAreSet]);useEffect((()=>{const handlePositionUpdate=()=>{updateDraggablePosition();updateBounds()};registerPositionUpdateListener(internalDraggableId,handlePositionUpdate);return()=>{unregisterPositionUpdateListener(internalDraggableId)}}),[internalDraggableId,registerPositionUpdateListener,unregisterPositionUpdateListener,updateDraggablePosition,updateBounds]);useEffect((()=>{updateBounds()}),[updateBounds]);const handleLayoutHandler=useCallback((event=>{updateDraggablePosition()}),[updateDraggablePosition]);const animateDragEndPosition=useCallback(((targetXValue,targetYValue)=>{"worklet";if(animationFunction){tx.value=animationFunction(targetXValue);ty.value=animationFunction(targetYValue)}else{tx.value=withSpring(targetXValue);ty.value=withSpring(targetYValue)}}),[animationFunction,tx,ty]);const performCollisionCheck=useCallback(((draggableX,draggableY,draggableW,draggableH,slot,algo)=>{if(algo==="intersect"){return draggableX<slot.x+slot.width&&draggableX+draggableW>slot.x&&draggableY<slot.y+slot.height&&draggableY+draggableH>slot.y}else if(algo==="contain"){return draggableX>=slot.x&&draggableX+draggableW<=slot.x+slot.width&&draggableY>=slot.y&&draggableY+draggableH<=slot.y+slot.height}else{const draggableCenterX=draggableX+draggableW/2;const draggableCenterY=draggableY+draggableH/2;return draggableCenterX>=slot.x&&draggableCenterX<=slot.x+slot.width&&draggableCenterY>=slot.y&&draggableCenterY<=slot.y+slot.height}}),[]);const processDropAndAnimate=useCallback(((currentTxVal,currentTyVal,draggableData,currentOriginX,currentOriginY,currentItemW,currentItemH)=>{const slots=getSlots();const currentDraggableX=currentOriginX+currentTxVal;const currentDraggableY=currentOriginY+currentTyVal;let hitSlotData=null;let hitSlotId=null;for(const key in slots){const slotId=parseInt(key,10);const s=slots[slotId];const isCollision=performCollisionCheck(currentDraggableX,currentDraggableY,currentItemW,currentItemH,s,collisionAlgorithm);if(isCollision){const hasCapacity=hasAvailableCapacity(s.id);if(hasCapacity){hitSlotData=s;hitSlotId=slotId;break}}}let finalTxValue;let finalTyValue;if(hitSlotData&&hitSlotId!==null){if(hitSlotData.onDrop){runOnJS(hitSlotData.onDrop)(draggableData)}runOnJS(registerDroppedItem)(internalDraggableId,hitSlotData.id,draggableData);runOnJS(setState)(DraggableState.DROPPED);const alignment=hitSlotData.dropAlignment||"center";const offset=hitSlotData.dropOffset||{x:0,y:0};let targetX=0;let targetY=0;switch(alignment){case"top-left":targetX=hitSlotData.x;targetY=hitSlotData.y;break;case"top-center":targetX=hitSlotData.x+hitSlotData.width/2-currentItemW/2;targetY=hitSlotData.y;break;case"top-right":targetX=hitSlotData.x+hitSlotData.width-currentItemW;targetY=hitSlotData.y;break;case"center-left":targetX=hitSlotData.x;targetY=hitSlotData.y+hitSlotData.height/2-currentItemH/2;break;case"center":targetX=hitSlotData.x+hitSlotData.width/2-currentItemW/2;targetY=hitSlotData.y+hitSlotData.height/2-currentItemH/2;break;case"center-right":targetX=hitSlotData.x+hitSlotData.width-currentItemW;targetY=hitSlotData.y+hitSlotData.height/2-currentItemH/2;break;case"bottom-left":targetX=hitSlotData.x;targetY=hitSlotData.y+hitSlotData.height-currentItemH;break;case"bottom-center":targetX=hitSlotData.x+hitSlotData.width/2-currentItemW/2;targetY=hitSlotData.y+hitSlotData.height-currentItemH;break;case"bottom-right":targetX=hitSlotData.x+hitSlotData.width-currentItemW;targetY=hitSlotData.y+hitSlotData.height-currentItemH;break;default:targetX=hitSlotData.x+hitSlotData.width/2-currentItemW/2;targetY=hitSlotData.y+hitSlotData.height/2-currentItemH/2}const draggableTargetX=targetX+offset.x;const draggableTargetY=targetY+offset.y;finalTxValue=draggableTargetX-currentOriginX;finalTyValue=draggableTargetY-currentOriginY}else{finalTxValue=0;finalTyValue=0;runOnJS(setState)(DraggableState.IDLE);runOnJS(unregisterDroppedItem)(internalDraggableId)}runOnUI(animateDragEndPosition)(finalTxValue,finalTyValue)}),[getSlots,animateDragEndPosition,collisionAlgorithm,performCollisionCheck,setState,internalDraggableId,registerDroppedItem,unregisterDroppedItem,hasAvailableCapacity]);const updateHoverState=useCallback(((currentTxVal,currentTyVal,currentOriginX,currentOriginY,currentItemW,currentItemH)=>{const slots=getSlots();const currentDraggableX=currentOriginX+currentTxVal;const currentDraggableY=currentOriginY+currentTyVal;let newHoveredSlotId=null;for(const key in slots){const slotId=parseInt(key,10);const s=slots[slotId];const isCollision=performCollisionCheck(currentDraggableX,currentDraggableY,currentItemW,currentItemH,s,collisionAlgorithm);if(isCollision){newHoveredSlotId=slotId;break}}if(activeHoverSlotId!==newHoveredSlotId){setActiveHoverSlot(newHoveredSlotId)}}),[getSlots,setActiveHoverSlot,activeHoverSlotId,collisionAlgorithm,performCollisionCheck]);const gesture=React.useMemo((()=>Gesture.Pan().onBegin((()=>{"worklet";updateDraggablePositionWorklet();if(dragDisabledShared.value){return}offsetX.value=tx.value;offsetY.value=ty.value;runOnJS(setState)(DraggableState.DRAGGING);if(onDragStart){runOnJS(onDragStart)(data)}if(contextOnDragStart){runOnJS(contextOnDragStart)(data)}})).onUpdate((event=>{"worklet";if(dragDisabledShared.value){return}let newTx=offsetX.value+event.translationX;let newTy=offsetY.value+event.translationY;if(boundsAreSet.value){const currentItemW=itemW.value;const currentItemH=itemH.value;const minTx=boundsX.value-originX.value;const maxTx=boundsX.value+boundsWidth.value-originX.value-currentItemW;const minTy=boundsY.value-originY.value;const maxTy=boundsY.value+boundsHeight.value-originY.value-currentItemH;newTx=Math.max(minTx,Math.min(newTx,maxTx));newTy=Math.max(minTy,Math.min(newTy,maxTy))}if(dragAxisShared.value==="x"){tx.value=newTx}else if(dragAxisShared.value==="y"){ty.value=newTy}else{tx.value=newTx;ty.value=newTy}if(onDragging){runOnJS(onDragging)({x:originX.value,y:originY.value,tx:tx.value,ty:ty.value,itemData:data})}if(contextOnDragging){runOnJS(contextOnDragging)({x:originX.value,y:originY.value,tx:tx.value,ty:ty.value,itemData:data})}runOnJS(updateHoverState)(tx.value,ty.value,originX.value,originY.value,itemW.value,itemH.value)})).onEnd((()=>{"worklet";if(dragDisabledShared.value){return}if(onDragEnd){runOnJS(onDragEnd)(data)}if(contextOnDragEnd){runOnJS(contextOnDragEnd)(data)}runOnJS(processDropAndAnimate)(tx.value,ty.value,data,originX.value,originY.value,itemW.value,itemH.value);runOnJS(setActiveHoverSlot)(null)}))),[dragDisabledShared,offsetX,offsetY,tx,ty,originX,originY,itemW,itemH,onDragStart,onDragEnd,data,processDropAndAnimate,updateHoverState,setActiveHoverSlot,animationFunction,onDragging,boundsAreSet,boundsX,boundsY,boundsWidth,boundsHeight,dragAxisShared,setState,updateDraggablePositionWorklet,contextOnDragging,contextOnDragStart,contextOnDragEnd]);const animatedStyleProp=useAnimatedStyle((()=>{"worklet";return{transform:[{translateX:tx.value},{translateY:ty.value}]}}),[tx,ty]);useAnimatedReaction((()=>({txValue:tx.value,tyValue:ty.value,isZero:tx.value===0&&ty.value===0})),((result,previous)=>{if(result.isZero&&previous&&!previous.isZero){runOnJS(setState)(DraggableState.IDLE);runOnJS(unregisterDroppedItem)(internalDraggableId)}}),[setState,unregisterDroppedItem,internalDraggableId]);useEffect((()=>()=>{unregisterDroppedItem(internalDraggableId)}),[internalDraggableId,unregisterDroppedItem]);return{animatedViewProps:{style:animatedStyleProp,onLayout:handleLayoutHandler},gesture,state,animatedViewRef,hasHandle}};
|
|
1
|
+
import React,{useCallback,useContext,useEffect,useRef,useState}from"react";import{useAnimatedReaction,useAnimatedRef,useSharedValue,useAnimatedStyle,withSpring}from"react-native-reanimated";import{Gesture}from"react-native-gesture-handler";import{scheduleOnRN,scheduleOnUI}from"react-native-worklets";import{SlotsContext}from"../types/context";import{DraggableState}from"../types/draggable";import{safeMeasure}from"./safeMeasure";export const useDraggable=options=>{const{data,draggableId,dragDisabled=false,preDragDelay=0,onDragStart,onDragEnd,onDragging,onStateChange,animationFunction,dragBoundsRef,dragAxis="both",collisionAlgorithm="intersect"}=options;const animatedViewRef=useAnimatedRef();const[state,setState]=useState(DraggableState.IDLE);const[hasHandle,setHasHandle]=useState(false);const registerHandle=useCallback((registered=>{setHasHandle(registered)}),[]);useEffect((()=>{onStateChange===null||onStateChange===void 0?void 0:onStateChange(state)}),[state,onStateChange]);const tx=useSharedValue(0);const ty=useSharedValue(0);const offsetX=useSharedValue(0);const offsetY=useSharedValue(0);const dragDisabledShared=useSharedValue(dragDisabled);const dragAxisShared=useSharedValue(dragAxis);const preDragDelayShared=useSharedValue(preDragDelay);const nodeReady=useSharedValue(false);const originX=useSharedValue(0);const originY=useSharedValue(0);const itemW=useSharedValue(0);const itemH=useSharedValue(0);const isOriginSet=useRef(false);const internalDraggableId=useRef(draggableId||`draggable-${Math.random().toString(36).substr(2,9)}`).current;const boundsX=useSharedValue(0);const boundsY=useSharedValue(0);const boundsWidth=useSharedValue(0);const boundsHeight=useSharedValue(0);const boundsAreSet=useSharedValue(false);const{getSlots,setActiveHoverSlot,activeHoverSlotId,registerPositionUpdateListener,unregisterPositionUpdateListener,registerDroppedItem,unregisterDroppedItem,hasAvailableCapacity,onDragging:contextOnDragging,onDragStart:contextOnDragStart,onDragEnd:contextOnDragEnd}=useContext(SlotsContext);useEffect((()=>{preDragDelayShared.value=preDragDelay}),[preDragDelay,preDragDelayShared]);useEffect((()=>{dragDisabledShared.value=dragDisabled}),[dragDisabled,dragDisabledShared]);useEffect((()=>{dragAxisShared.value=dragAxis}),[dragAxis,dragAxisShared]);const updateDraggablePosition=useCallback((()=>{scheduleOnUI((()=>{"worklet";if(!nodeReady.value){return}const measurement=safeMeasure(animatedViewRef);if(measurement===null){return}const currentTx=tx.value;const currentTy=ty.value;if(currentTx===0&¤tTy===0){const newOriginX=measurement.pageX-currentTx;const newOriginY=measurement.pageY-currentTy;originX.value=newOriginX;originY.value=newOriginY}itemW.value=measurement.width;itemH.value=measurement.height;if(!isOriginSet.current){isOriginSet.current=true}}))}),[animatedViewRef,originX,originY,itemW,itemH,tx,ty]);const updateDraggablePositionWorklet=useCallback((()=>{"worklet";if(!nodeReady.value){return}const measurement=safeMeasure(animatedViewRef);if(measurement===null){return}const currentTx=tx.value;const currentTy=ty.value;if(currentTx===0&¤tTy===0){const newOriginX=measurement.pageX-currentTx;const newOriginY=measurement.pageY-currentTy;originX.value=newOriginX;originY.value=newOriginY}itemW.value=measurement.width;itemH.value=measurement.height;if(!isOriginSet.current){isOriginSet.current=true}}),[animatedViewRef,originX,originY,itemW,itemH,tx,ty]);const updateBounds=useCallback((()=>{const currentBoundsView=dragBoundsRef===null||dragBoundsRef===void 0?void 0:dragBoundsRef.current;if(currentBoundsView){currentBoundsView.measure(((_x,_y,width,height,pageX,pageY)=>{if(typeof pageX==="number"&&typeof pageY==="number"&&width>0&&height>0){scheduleOnUI((()=>{"worklet";boundsX.value=pageX;boundsY.value=pageY;boundsWidth.value=width;boundsHeight.value=height;if(!boundsAreSet.value){boundsAreSet.value=true}}))}else{console.warn("useDraggable: dragBoundsRef measurement failed or returned invalid dimensions. Bounds may be stale or item unbounded.")}}))}else{scheduleOnUI((()=>{"worklet";if(boundsAreSet.value){boundsAreSet.value=false}}))}}),[dragBoundsRef,boundsX,boundsY,boundsWidth,boundsHeight,boundsAreSet]);useEffect((()=>{const handlePositionUpdate=()=>{updateDraggablePosition();updateBounds()};registerPositionUpdateListener(internalDraggableId,handlePositionUpdate);return()=>{unregisterPositionUpdateListener(internalDraggableId)}}),[internalDraggableId,registerPositionUpdateListener,unregisterPositionUpdateListener,updateDraggablePosition,updateBounds]);useEffect((()=>{updateBounds()}),[updateBounds]);const handleLayoutHandler=useCallback((_event=>{scheduleOnUI((()=>{"worklet";nodeReady.value=true}));updateDraggablePosition()}),[nodeReady,updateDraggablePosition]);const animateDragEndPosition=useCallback(((targetXValue,targetYValue)=>{"worklet";if(animationFunction){tx.value=animationFunction(targetXValue);ty.value=animationFunction(targetYValue)}else{tx.value=withSpring(targetXValue);ty.value=withSpring(targetYValue)}}),[animationFunction,tx,ty]);const performCollisionCheck=useCallback(((draggableX,draggableY,draggableW,draggableH,slot,algo)=>{if(algo==="intersect"){return draggableX<slot.x+slot.width&&draggableX+draggableW>slot.x&&draggableY<slot.y+slot.height&&draggableY+draggableH>slot.y}else if(algo==="contain"){return draggableX>=slot.x&&draggableX+draggableW<=slot.x+slot.width&&draggableY>=slot.y&&draggableY+draggableH<=slot.y+slot.height}else{const draggableCenterX=draggableX+draggableW/2;const draggableCenterY=draggableY+draggableH/2;return draggableCenterX>=slot.x&&draggableCenterX<=slot.x+slot.width&&draggableCenterY>=slot.y&&draggableCenterY<=slot.y+slot.height}}),[]);const processDropAndAnimate=useCallback(((currentTxVal,currentTyVal,draggableData,currentOriginX,currentOriginY,currentItemW,currentItemH)=>{const slots=getSlots();const currentDraggableX=currentOriginX+currentTxVal;const currentDraggableY=currentOriginY+currentTyVal;let hitSlotData=null;let hitSlotId=null;for(const key in slots){const slotId=parseInt(key,10);const s=slots[slotId];const isCollision=performCollisionCheck(currentDraggableX,currentDraggableY,currentItemW,currentItemH,s,collisionAlgorithm);if(isCollision){const hasCapacity=hasAvailableCapacity(s.id);if(hasCapacity){hitSlotData=s;hitSlotId=slotId;break}}}let finalTxValue;let finalTyValue;if(hitSlotData&&hitSlotId!==null){if(hitSlotData.onDrop){hitSlotData.onDrop(draggableData)}scheduleOnRN(registerDroppedItem,internalDraggableId,hitSlotData.id,draggableData);scheduleOnRN(setState,DraggableState.DROPPED);const alignment=hitSlotData.dropAlignment||"center";const offset=hitSlotData.dropOffset||{x:0,y:0};let targetX=0;let targetY=0;switch(alignment){case"top-left":targetX=hitSlotData.x;targetY=hitSlotData.y;break;case"top-center":targetX=hitSlotData.x+hitSlotData.width/2-currentItemW/2;targetY=hitSlotData.y;break;case"top-right":targetX=hitSlotData.x+hitSlotData.width-currentItemW;targetY=hitSlotData.y;break;case"center-left":targetX=hitSlotData.x;targetY=hitSlotData.y+hitSlotData.height/2-currentItemH/2;break;case"center":targetX=hitSlotData.x+hitSlotData.width/2-currentItemW/2;targetY=hitSlotData.y+hitSlotData.height/2-currentItemH/2;break;case"center-right":targetX=hitSlotData.x+hitSlotData.width-currentItemW;targetY=hitSlotData.y+hitSlotData.height/2-currentItemH/2;break;case"bottom-left":targetX=hitSlotData.x;targetY=hitSlotData.y+hitSlotData.height-currentItemH;break;case"bottom-center":targetX=hitSlotData.x+hitSlotData.width/2-currentItemW/2;targetY=hitSlotData.y+hitSlotData.height-currentItemH;break;case"bottom-right":targetX=hitSlotData.x+hitSlotData.width-currentItemW;targetY=hitSlotData.y+hitSlotData.height-currentItemH;break;default:targetX=hitSlotData.x+hitSlotData.width/2-currentItemW/2;targetY=hitSlotData.y+hitSlotData.height/2-currentItemH/2}const draggableTargetX=targetX+offset.x;const draggableTargetY=targetY+offset.y;finalTxValue=draggableTargetX-currentOriginX;finalTyValue=draggableTargetY-currentOriginY}else{finalTxValue=0;finalTyValue=0;scheduleOnRN(setState,DraggableState.IDLE);scheduleOnRN(unregisterDroppedItem,internalDraggableId)}scheduleOnUI(animateDragEndPosition,finalTxValue,finalTyValue)}),[getSlots,animateDragEndPosition,collisionAlgorithm,performCollisionCheck,setState,internalDraggableId,registerDroppedItem,unregisterDroppedItem,hasAvailableCapacity]);const updateHoverState=useCallback(((currentTxVal,currentTyVal,currentOriginX,currentOriginY,currentItemW,currentItemH)=>{const slots=getSlots();const currentDraggableX=currentOriginX+currentTxVal;const currentDraggableY=currentOriginY+currentTyVal;let newHoveredSlotId=null;for(const key in slots){const slotId=parseInt(key,10);const s=slots[slotId];const isCollision=performCollisionCheck(currentDraggableX,currentDraggableY,currentItemW,currentItemH,s,collisionAlgorithm);if(isCollision){newHoveredSlotId=slotId;break}}if(activeHoverSlotId!==newHoveredSlotId){setActiveHoverSlot(newHoveredSlotId)}}),[getSlots,setActiveHoverSlot,activeHoverSlotId,collisionAlgorithm,performCollisionCheck]);const gesture=React.useMemo((()=>Gesture.Pan().activateAfterLongPress(preDragDelay).onStart((()=>{"worklet";if(!nodeReady.value){return}updateDraggablePositionWorklet();if(dragDisabledShared.value){return}offsetX.value=tx.value;offsetY.value=ty.value;scheduleOnRN(setState,DraggableState.DRAGGING);if(onDragStart){scheduleOnRN(onDragStart,data)}if(contextOnDragStart){scheduleOnRN(contextOnDragStart,data)}})).onUpdate((event=>{"worklet";if(dragDisabledShared.value){return}let newTx=offsetX.value+event.translationX;let newTy=offsetY.value+event.translationY;if(boundsAreSet.value){const currentItemW=itemW.value;const currentItemH=itemH.value;const minTx=boundsX.value-originX.value;const maxTx=boundsX.value+boundsWidth.value-originX.value-currentItemW;const minTy=boundsY.value-originY.value;const maxTy=boundsY.value+boundsHeight.value-originY.value-currentItemH;newTx=Math.max(minTx,Math.min(newTx,maxTx));newTy=Math.max(minTy,Math.min(newTy,maxTy))}if(dragAxisShared.value==="x"){tx.value=newTx}else if(dragAxisShared.value==="y"){ty.value=newTy}else{tx.value=newTx;ty.value=newTy}if(onDragging){scheduleOnRN(onDragging,{x:originX.value,y:originY.value,tx:tx.value,ty:ty.value,itemData:data})}if(contextOnDragging){scheduleOnRN(contextOnDragging,{x:originX.value,y:originY.value,tx:tx.value,ty:ty.value,itemData:data})}scheduleOnRN(updateHoverState,tx.value,ty.value,originX.value,originY.value,itemW.value,itemH.value)})).onEnd((()=>{"worklet";if(dragDisabledShared.value){return}if(onDragEnd){scheduleOnRN(onDragEnd,data)}if(contextOnDragEnd){scheduleOnRN(contextOnDragEnd,data)}scheduleOnRN(processDropAndAnimate,tx.value,ty.value,data,originX.value,originY.value,itemW.value,itemH.value);scheduleOnRN(setActiveHoverSlot,null)}))),[dragDisabledShared,offsetX,offsetY,tx,ty,originX,originY,itemW,itemH,onDragStart,onDragEnd,data,processDropAndAnimate,updateHoverState,setActiveHoverSlot,animationFunction,onDragging,boundsAreSet,boundsX,boundsY,boundsWidth,boundsHeight,dragAxisShared,setState,updateDraggablePositionWorklet,contextOnDragging,contextOnDragStart,contextOnDragEnd,nodeReady,preDragDelay]);const animatedStyleProp=useAnimatedStyle((()=>{"worklet";return{transform:[{translateX:tx.value},{translateY:ty.value}]}}),[tx,ty]);useAnimatedReaction((()=>({txValue:tx.value,tyValue:ty.value,isZero:tx.value===0&&ty.value===0})),((result,previous)=>{if(result.isZero&&previous&&!previous.isZero){scheduleOnRN(setState,DraggableState.IDLE);scheduleOnRN(unregisterDroppedItem,internalDraggableId)}}),[setState,unregisterDroppedItem,internalDraggableId]);useEffect((()=>()=>{unregisterDroppedItem(internalDraggableId)}),[internalDraggableId,unregisterDroppedItem]);return{animatedViewProps:{style:animatedStyleProp,onLayout:handleLayoutHandler},gesture,state,animatedViewRef,hasHandle,registerHandle}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{useCallback,useContext,useEffect,useMemo,useRef}from"react";import{StyleSheet}from"react-native";import{useAnimatedRef,useSharedValue}from"react-native-reanimated";import{scheduleOnRN,scheduleOnUI}from"react-native-worklets";import{SlotsContext}from"../types/context";import{safeMeasure}from"./safeMeasure";let _nextDroppableId=1;const _getUniqueDroppableId=()=>_nextDroppableId++;export const useDroppable=options=>{const{onDrop,dropDisabled,onActiveChange,dropAlignment,dropOffset,activeStyle,droppableId,capacity}=options;const nodeReady=useSharedValue(false);const animatedViewRef=useAnimatedRef();const id=useRef(_getUniqueDroppableId()).current;const stringId=useRef(droppableId||`droppable-${id}`).current;const instanceId=useRef(`droppable-${id}-${Math.random().toString(36).substr(2,9)}`).current;const{register,unregister,isRegistered,activeHoverSlotId:contextActiveHoverSlotId,registerPositionUpdateListener,unregisterPositionUpdateListener}=useContext(SlotsContext);const isActive=contextActiveHoverSlotId===id;const{processedActiveStyle,activeTransforms}=useMemo((()=>{if(!isActive||!activeStyle){return{processedActiveStyle:null,activeTransforms:[]}}const flattenedStyle=StyleSheet.flatten(activeStyle);let processedStyle={...flattenedStyle};let transforms=[];if(flattenedStyle.transform){if(Array.isArray(flattenedStyle.transform)){transforms=[...flattenedStyle.transform]}delete processedStyle.transform}return{processedActiveStyle:processedStyle,activeTransforms:transforms}}),[isActive,activeStyle]);const combinedActiveStyle=useMemo((()=>{if(!isActive||!activeStyle){return undefined}if(activeTransforms.length===0){return processedActiveStyle}return{...processedActiveStyle,transform:activeTransforms}}),[isActive,activeStyle,processedActiveStyle,activeTransforms]);useEffect((()=>{onActiveChange===null||onActiveChange===void 0?void 0:onActiveChange(isActive)}),[isActive,onActiveChange]);const registerWithMeasurement=useCallback(((pageX,pageY,width,height)=>{register(id,{id:droppableId||`droppable-${id}`,x:pageX,y:pageY,width,height,onDrop,dropAlignment:dropAlignment||"center",dropOffset:dropOffset||{x:0,y:0},capacity})}),[id,droppableId,onDrop,register,dropAlignment,dropOffset,capacity]);const updateDroppablePosition=useCallback((()=>{scheduleOnUI((()=>{"worklet";if(!nodeReady.value){return}const measurement=safeMeasure(animatedViewRef);if(measurement===null){return}if(measurement.width>0&&measurement.height>0){scheduleOnRN(registerWithMeasurement,measurement.pageX,measurement.pageY,measurement.width,measurement.height)}}))}),[animatedViewRef,registerWithMeasurement]);const handleLayoutHandler=useCallback((_event=>{scheduleOnUI((()=>{"worklet";nodeReady.value=true}));updateDroppablePosition()}),[nodeReady,updateDroppablePosition]);useEffect((()=>{registerPositionUpdateListener(instanceId,updateDroppablePosition);return()=>{unregisterPositionUpdateListener(instanceId)}}),[instanceId,registerPositionUpdateListener,unregisterPositionUpdateListener,updateDroppablePosition]);useEffect((()=>{if(dropDisabled){unregister(id)}else{updateDroppablePosition()}}),[dropDisabled,id,unregister,updateDroppablePosition]);useEffect((()=>()=>{unregister(id)}),[id,unregister]);return{viewProps:{onLayout:handleLayoutHandler,style:combinedActiveStyle},isActive,activeStyle,animatedViewRef}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useCallback,useState,useRef}from"react";import{useAnimatedReaction,useAnimatedStyle,useSharedValue,withSpring,withTiming}from"react-native-reanimated";import{Gesture}from"react-native-gesture-handler";import{scheduleOnRN}from"react-native-worklets";import{GridScrollDirection,GridOrientation,GridStrategy}from"../types/grid";import{setGridPosition,setGridAutoScroll,calculateGridContentDimensions}from"../utils/gridCalculations";export function useGridSortable(options){const{id,positions,scrollY,scrollX,autoScrollDirection,itemsCount,dimensions,orientation,strategy=GridStrategy.Insert,containerWidth=500,containerHeight=500,activationDelay,onMove,onDragStart,onDrop,onDragging,isBeingRemoved=false}=options;const[isMoving,setIsMoving]=useState(false);const[hasHandle,setHasHandle]=useState(false);const registerHandle=useCallback((registered=>{setHasHandle(registered)}),[]);const movingSV=useSharedValue(false);const currentOverItemId=useSharedValue(null);const onDraggingLastCallTimestamp=useSharedValue(0);const THROTTLE_INTERVAL=50;const initialPositionRef=useRef(null);if(initialPositionRef.current===null){const posArr=positions.get();const pos=posArr===null||posArr===void 0?void 0:posArr[id];initialPositionRef.current=pos?{x:pos.x,y:pos.y}:{x:0,y:0}}const initialPosition=initialPositionRef.current;const positionX=useSharedValue(initialPosition.x);const positionY=useSharedValue(initialPosition.y);const topValue=useSharedValue(initialPosition.y);const leftValue=useSharedValue(initialPosition.x);const targetScrollY=useSharedValue(0);const targetScrollX=useSharedValue(0);const initialItemContentX=useSharedValue(0);const initialItemContentY=useSharedValue(0);const initialFingerAbsoluteX=useSharedValue(0);const initialFingerAbsoluteY=useSharedValue(0);const initialScrollY=useSharedValue(0);const initialScrollX=useSharedValue(0);const calculatedContainerHeight=useRef(containerHeight).current;const calculatedContainerWidth=useRef(containerWidth).current;useAnimatedReaction((()=>({x:positionX.value,y:positionY.value})),((current,previous)=>{var _a;if(!movingSV.value){return}if(previous!==null&¤t.x===previous.x&¤t.y===previous.y){return}const{itemWidth,itemHeight,columnGap=0,rowGap=0,columns=3}=dimensions;const clampedColumn=Math.min(Math.max(0,Math.round(current.x/(itemWidth+columnGap))),(orientation===GridOrientation.Vertical?columns:Infinity)-1);const clampedRow=Math.floor(current.y/(itemHeight+rowGap));let targetIndex;if(orientation===GridOrientation.Vertical){targetIndex=clampedRow*columns+clampedColumn}else{const rows=(_a=dimensions.rows)!==null&&_a!==void 0?_a:3;targetIndex=clampedColumn*rows+clampedRow}targetIndex=Math.max(0,Math.min(targetIndex,itemsCount-1));let newOverItemId=null;const positionsValue=positions.value;for(const itemId in positionsValue){if(positionsValue[itemId].index===targetIndex&&itemId!==id){newOverItemId=itemId;break}}if(currentOverItemId.value!==newOverItemId){currentOverItemId.value=newOverItemId}if(onDragging){const now=Date.now();if(now-onDraggingLastCallTimestamp.value>THROTTLE_INTERVAL){scheduleOnRN(onDragging,id,newOverItemId,Math.round(current.x),Math.round(current.y));onDraggingLastCallTimestamp.value=now}}topValue.value=current.y;leftValue.value=current.x;setGridPosition(current.x,current.y,scrollX.value,scrollY.value,itemsCount,positions,id,dimensions,orientation,strategy);setGridAutoScroll(current.x,current.y,scrollX.value,scrollY.value,calculatedContainerWidth,calculatedContainerHeight,dimensions.itemHeight,autoScrollDirection)}),[movingSV,dimensions,itemsCount,positions,id,orientation,strategy,onDragging,scrollX,scrollY,autoScrollDirection,currentOverItemId,topValue,leftValue,onDraggingLastCallTimestamp]);useAnimatedReaction((()=>positions.value[id]),((currentPosition,previousPosition)=>{if(currentPosition!==null&¤tPosition!==undefined&&previousPosition!==null&&previousPosition!==undefined){if(currentPosition.x!==previousPosition.x||currentPosition.y!==previousPosition.y){if(!movingSV.value){topValue.value=withSpring(currentPosition.y);leftValue.value=withSpring(currentPosition.x);if(onMove){scheduleOnRN(onMove,id,previousPosition.index,currentPosition.index)}}}}}),[movingSV]);useAnimatedReaction((()=>autoScrollDirection.value),((scrollDirection,previousValue)=>{if(scrollDirection!==null&&previousValue!==null&&scrollDirection!==previousValue){const{width:contentWidth,height:contentHeight}=calculateGridContentDimensions(itemsCount,dimensions,orientation);const maxScrollY=Math.max(0,contentHeight-calculatedContainerHeight);const maxScrollX=Math.max(0,contentWidth-calculatedContainerWidth);const hasUp=scrollDirection===GridScrollDirection.Up||scrollDirection===GridScrollDirection.UpLeft||scrollDirection===GridScrollDirection.UpRight;const hasDown=scrollDirection===GridScrollDirection.Down||scrollDirection===GridScrollDirection.DownLeft||scrollDirection===GridScrollDirection.DownRight;const hasLeft=scrollDirection===GridScrollDirection.Left||scrollDirection===GridScrollDirection.UpLeft||scrollDirection===GridScrollDirection.DownLeft;const hasRight=scrollDirection===GridScrollDirection.Right||scrollDirection===GridScrollDirection.UpRight||scrollDirection===GridScrollDirection.DownRight;if(hasUp){targetScrollY.value=scrollY.value;targetScrollY.value=withTiming(0,{duration:1500})}else if(hasDown){targetScrollY.value=scrollY.value;targetScrollY.value=withTiming(maxScrollY,{duration:1500})}else{targetScrollY.value=scrollY.value}if(hasLeft){targetScrollX.value=scrollX.value;targetScrollX.value=withTiming(0,{duration:1500})}else if(hasRight){targetScrollX.value=scrollX.value;targetScrollX.value=withTiming(maxScrollX,{duration:1500})}else{targetScrollX.value=scrollX.value}if(scrollDirection===GridScrollDirection.None){targetScrollY.value=scrollY.value;targetScrollX.value=scrollX.value}}}));useAnimatedReaction((()=>({y:targetScrollY.value,x:targetScrollX.value})),((target,previous)=>{if(previous===null){return}if(movingSV.value){if(target.y!==previous.y){scrollY.value=target.y}if(target.x!==previous.x){scrollX.value=target.x}}}),[movingSV]);const createPanGesture=()=>Gesture.Pan().activateAfterLongPress(activationDelay!==null&&activationDelay!==void 0?activationDelay:200).shouldCancelWhenOutside(false).onStart((event=>{"worklet";const currentPos=positions.value[id];if(!currentPos){return}initialItemContentX.value=currentPos.x;initialItemContentY.value=currentPos.y;initialFingerAbsoluteX.value=event.absoluteX;initialFingerAbsoluteY.value=event.absoluteY;initialScrollY.value=scrollY.value;initialScrollX.value=scrollX.value;positionX.value=initialItemContentX.value;positionY.value=initialItemContentY.value;movingSV.value=true;scheduleOnRN(setIsMoving,true);if(onDragStart){scheduleOnRN(onDragStart,id,currentPos.index)}})).onUpdate((event=>{"worklet";const fingerDxScreen=event.absoluteX-initialFingerAbsoluteX.value;const fingerDyScreen=event.absoluteY-initialFingerAbsoluteY.value;const scrollDeltaY=scrollY.value-initialScrollY.value;const scrollDeltaX=scrollX.value-initialScrollX.value;positionX.value=initialItemContentX.value+fingerDxScreen+scrollDeltaX;positionY.value=initialItemContentY.value+fingerDyScreen+scrollDeltaY})).onFinalize((()=>{"worklet";const finishPos=positions.value[id];if(finishPos){topValue.value=withTiming(finishPos.y);leftValue.value=withTiming(finishPos.x)}movingSV.value=false;scheduleOnRN(setIsMoving,false);autoScrollDirection.value=GridScrollDirection.None;if(onDrop&&finishPos){const positionsCopy={...positions.value};scheduleOnRN(onDrop,id,finishPos.index,positionsCopy)}currentOverItemId.value=null}));const panGestureHandler=createPanGesture().enabled(!hasHandle);const handlePanGestureHandler=createPanGesture();const animatedStyle=useAnimatedStyle((()=>{"worklet";if(isBeingRemoved){return{position:"absolute",top:topValue.value,left:leftValue.value,width:dimensions.itemWidth,height:dimensions.itemHeight,zIndex:0,opacity:withTiming(0,{duration:250}),transform:[{scale:withTiming(.5,{duration:250})}]}}return{position:"absolute",top:topValue.value,left:leftValue.value,width:dimensions.itemWidth,height:dimensions.itemHeight,zIndex:movingSV.value?1e3:0,shadowColor:"black",shadowOpacity:withSpring(movingSV.value?.2:0),shadowRadius:10,transform:[{scale:withSpring(movingSV.value?1.05:1)}]}}),[movingSV,isBeingRemoved,dimensions]);return{animatedStyle,panGestureHandler,handlePanGestureHandler,isMoving,hasHandle,registerHandle}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { UseGridSortableListOptions, UseGridSortableListReturn } from "../types/grid";
|
|
2
|
+
import { SortableData } from "../types/sortable";
|
|
3
|
+
export declare function useGridSortableList<TData extends SortableData>(options: UseGridSortableListOptions<TData>): UseGridSortableListReturn<TData>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useRef,useCallback,useEffect}from"react";import{scrollTo,useAnimatedReaction,useAnimatedRef,useAnimatedScrollHandler,useSharedValue}from"react-native-reanimated";import{GridScrollDirection,GridOrientation,GridStrategy}from"../types/grid";import{listToGridObject,calculateGridContentDimensions}from"../utils/gridCalculations";export function useGridSortableList(options){const{data,dimensions,orientation=GridOrientation.Vertical,strategy=GridStrategy.Insert,itemKeyExtractor=item=>item.id}=options;if(__DEV__){data.forEach(((item,index)=>{const id=itemKeyExtractor(item,index);if(typeof id!=="string"||!id){console.error(`[react-native-reanimated-dnd] Grid item at index ${index} has invalid id: ${id}. `+`Each item must have a unique string id property.`)}}))}const positions=useSharedValue(listToGridObject(data,dimensions,orientation));const scrollY=useSharedValue(0);const scrollX=useSharedValue(0);const autoScrollDirection=useSharedValue(GridScrollDirection.None);const scrollViewRef=useAnimatedRef();const dropProviderRef=useRef(null);useEffect((()=>{positions.value=listToGridObject(data,dimensions,orientation)}),[data.length,data.map((d=>itemKeyExtractor(d,0))).join(","),dimensions.columns,dimensions.rows,dimensions.itemWidth,dimensions.itemHeight,dimensions.rowGap,dimensions.columnGap,orientation]);useAnimatedReaction((()=>scrollY.value),(scrolling=>{scrollTo(scrollViewRef,scrollX.value,scrolling,false)}));useAnimatedReaction((()=>scrollX.value),(scrolling=>{scrollTo(scrollViewRef,scrolling,scrollY.value,false)}));const handleScroll=useAnimatedScrollHandler((event=>{scrollY.value=event.contentOffset.y;scrollX.value=event.contentOffset.x}));const scrollTimeoutRef=useRef(null);const handleScrollEnd=useCallback((()=>{if(scrollTimeoutRef.current){clearTimeout(scrollTimeoutRef.current)}scrollTimeoutRef.current=setTimeout((()=>{var _a;(_a=dropProviderRef.current)===null||_a===void 0?void 0:_a.requestPositionUpdate()}),50)}),[]);const{width:contentWidth,height:contentHeight}=calculateGridContentDimensions(data.length,dimensions,orientation);const getItemProps=useCallback(((item,index)=>{const id=itemKeyExtractor(item,index);return{id,positions,scrollY,scrollX,autoScrollDirection,itemsCount:data.length,dimensions,orientation,strategy}}),[data.length,dimensions,orientation,strategy,itemKeyExtractor,positions,scrollY,scrollX,autoScrollDirection]);return{positions,scrollY,scrollX,autoScrollDirection,scrollViewRef,dropProviderRef,handleScroll,handleScrollEnd,contentWidth,contentHeight,getItemProps}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{useCallback,useMemo,useRef,useState}from"react";import{useAnimatedReaction,useAnimatedStyle,useDerivedValue,useSharedValue,withSpring,withTiming}from"react-native-reanimated";import{Gesture}from"react-native-gesture-handler";import{scheduleOnRN}from"react-native-worklets";import{getContentWidth,getItemXPosition,setHorizontalAutoScroll,setHorizontalPosition}from"../components/sortableUtils";import{HorizontalScrollDirection}from"../types/sortable";export function useHorizontalSortable(options){const{id,positions,leftBound,autoScrollDirection,itemsCount,itemWidth,gap=0,paddingHorizontal=0,containerWidth=500,onMove,onDragStart,onDrop,onDragging}=options;const[isMoving,setIsMoving]=useState(false);const[hasHandle,setHasHandle]=useState(false);const registerHandle=useCallback((registered=>{setHasHandle(registered)}),[]);const movingSV=useSharedValue(false);const currentOverItemId=useSharedValue(null);const onDraggingLastCallTimestamp=useSharedValue(0);const THROTTLE_INTERVAL=50;const initialLeftVal=useMemo((()=>{const posArr=positions.get();const pos=posArr===null||posArr===void 0?void 0:posArr[id];return getItemXPosition(pos,itemWidth,gap,paddingHorizontal)}),[]);const initialLeftBoundVal=useMemo((()=>leftBound.get()),[]);const positionX=useSharedValue(initialLeftVal);const left=useSharedValue(initialLeftVal);const targetLeftBound=useSharedValue(initialLeftBoundVal);const initialItemContentX=useSharedValue(0);const initialFingerAbsoluteX=useSharedValue(0);const initialLeftBound=useSharedValue(0);const calculatedContainerWidth=useRef(containerWidth).current;const rightBound=useDerivedValue((()=>leftBound.value+calculatedContainerWidth));useAnimatedReaction((()=>positionX.value),((currentX,previousX)=>{if(currentX===null||!movingSV.value){return}if(previousX!==null&¤tX===previousX){return}const adjustedX=currentX-paddingHorizontal;const itemWithGapWidth=itemWidth+gap;const clampedPosition=Math.min(Math.max(0,Math.ceil(adjustedX/itemWithGapWidth)),itemsCount-1);let newOverItemId=null;for(const[itemIdIter,itemPosIter]of Object.entries(positions.value)){if(itemPosIter===clampedPosition&&itemIdIter!==id){newOverItemId=itemIdIter;break}}if(currentOverItemId.value!==newOverItemId){currentOverItemId.value=newOverItemId}if(onDragging){const now=Date.now();if(now-onDraggingLastCallTimestamp.value>THROTTLE_INTERVAL){scheduleOnRN(onDragging,id,newOverItemId,Math.round(currentX));onDraggingLastCallTimestamp.value=now}}left.value=currentX;setHorizontalPosition(currentX,itemsCount,positions,id,itemWidth,gap,paddingHorizontal);setHorizontalAutoScroll(currentX,leftBound.value,rightBound.value,itemWidth,autoScrollDirection)}),[movingSV,itemWidth,gap,paddingHorizontal,itemsCount,positions,id,onDragging,leftBound,rightBound,autoScrollDirection,currentOverItemId,left,onDraggingLastCallTimestamp]);useAnimatedReaction((()=>positions.value[id]),((currentPosition,previousPosition)=>{if(currentPosition!==null&&previousPosition!==null&¤tPosition!==previousPosition){if(!movingSV.value){const newLeft=getItemXPosition(currentPosition,itemWidth,gap,paddingHorizontal);left.value=withSpring(newLeft);if(onMove){scheduleOnRN(onMove,id,previousPosition,currentPosition)}}}}),[movingSV,itemWidth,gap,paddingHorizontal]);useAnimatedReaction((()=>autoScrollDirection.value),((scrollDirection,previousValue)=>{if(scrollDirection!==null&&previousValue!==null&&scrollDirection!==previousValue){switch(scrollDirection){case HorizontalScrollDirection.Left:{targetLeftBound.value=leftBound.value;targetLeftBound.value=withTiming(0,{duration:1500});break}case HorizontalScrollDirection.Right:{const contentWidth=getContentWidth(itemsCount,itemWidth,gap,paddingHorizontal);const maxScroll=Math.max(0,contentWidth-calculatedContainerWidth);targetLeftBound.value=leftBound.value;targetLeftBound.value=withTiming(maxScroll,{duration:1500});break}case HorizontalScrollDirection.None:{targetLeftBound.value=leftBound.value;break}}}}));useAnimatedReaction((()=>targetLeftBound.value),((targetLeftBoundValue,previousValue)=>{if(targetLeftBoundValue!==null&&previousValue!==null&&targetLeftBoundValue!==previousValue){if(movingSV.value){leftBound.value=targetLeftBoundValue}}}),[movingSV]);const createPanGesture=()=>Gesture.Pan().activateAfterLongPress(200).shouldCancelWhenOutside(false).onStart((event=>{"worklet";initialItemContentX.value=getItemXPosition(positions.value[id],itemWidth,gap,paddingHorizontal);initialFingerAbsoluteX.value=event.absoluteX;initialLeftBound.value=leftBound.value;positionX.value=initialItemContentX.value;movingSV.value=true;scheduleOnRN(setIsMoving,true);if(onDragStart){scheduleOnRN(onDragStart,id,positions.value[id])}})).onUpdate((event=>{"worklet";const fingerDxScreen=event.absoluteX-initialFingerAbsoluteX.value;const scrollDeltaSinceStart=leftBound.value-initialLeftBound.value;positionX.value=initialItemContentX.value+fingerDxScreen+scrollDeltaSinceStart})).onFinalize((()=>{"worklet";const finishPosition=getItemXPosition(positions.value[id],itemWidth,gap,paddingHorizontal);left.value=withTiming(finishPosition);movingSV.value=false;scheduleOnRN(setIsMoving,false);if(onDrop){const positionsCopy={...positions.value};scheduleOnRN(onDrop,id,positions.value[id],positionsCopy)}currentOverItemId.value=null}));const panGestureHandler=createPanGesture().enabled(!hasHandle);const handlePanGestureHandler=createPanGesture();const animatedStyle=useAnimatedStyle((()=>{"worklet";return{position:"absolute",top:0,bottom:0,left:left.value,width:itemWidth,zIndex:movingSV.value?1:0,backgroundColor:"transparent",shadowColor:"black",shadowOpacity:withSpring(movingSV.value?.2:0),shadowRadius:10}}),[movingSV,itemWidth]);return{animatedStyle,panGestureHandler,handlePanGestureHandler,isMoving,hasHandle,registerHandle}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useRef,useCallback}from"react";import{scrollTo,useAnimatedReaction,useAnimatedRef,useAnimatedScrollHandler,useSharedValue}from"react-native-reanimated";import{listToObject,getContentWidth}from"../components/sortableUtils";import{HorizontalScrollDirection}from"../types/sortable";export function useHorizontalSortableList(options){const{data,itemWidth,gap=0,paddingHorizontal=0,itemKeyExtractor=item=>item.id}=options;const positions=useSharedValue(listToObject(data));const scrollX=useSharedValue(0);const autoScroll=useSharedValue(HorizontalScrollDirection.None);const scrollViewRef=useAnimatedRef();const dropProviderRef=useRef(null);useAnimatedReaction((()=>scrollX.value),(scrolling=>{scrollTo(scrollViewRef,scrolling,0,false)}));const handleScroll=useAnimatedScrollHandler((event=>{scrollX.value=event.contentOffset.x}));const handleScrollEnd=useCallback((()=>{let localScrollTimeout=null;if(localScrollTimeout){clearTimeout(localScrollTimeout)}localScrollTimeout=setTimeout((()=>{var _a;(_a=dropProviderRef.current)===null||_a===void 0?void 0:_a.requestPositionUpdate()}),50)}),[]);const contentWidth=getContentWidth(data.length,itemWidth,gap,paddingHorizontal);const getItemProps=useCallback(((item,index)=>{const id=itemKeyExtractor(item,index);return{id,positions,leftBound:scrollX,autoScrollDirection:autoScroll,itemsCount:data.length,itemWidth,gap,paddingHorizontal}}),[data.length,itemWidth,gap,paddingHorizontal,itemKeyExtractor,positions,scrollX,autoScroll]);return{positions,scrollX,autoScroll,scrollViewRef,dropProviderRef,handleScroll,handleScrollEnd,contentWidth,getItemProps}}
|
|
1
|
+
import{useRef,useCallback}from"react";import{scrollTo,useAnimatedReaction,useAnimatedRef,useAnimatedScrollHandler,useSharedValue}from"react-native-reanimated";import{listToObject,getContentWidth}from"../components/sortableUtils";import{HorizontalScrollDirection}from"../types/sortable";export function useHorizontalSortableList(options){const{data,itemWidth,gap=0,paddingHorizontal=0,itemKeyExtractor=item=>item.id}=options;if(__DEV__){data.forEach(((item,index)=>{const id=itemKeyExtractor(item,index);if(typeof id!=="string"||!id){console.error(`[react-native-reanimated-dnd] Item at index ${index} has invalid id: ${id}. `+`Each item must have a unique string id property.`)}}))}const positions=useSharedValue(listToObject(data));const scrollX=useSharedValue(0);const autoScroll=useSharedValue(HorizontalScrollDirection.None);const scrollViewRef=useAnimatedRef();const dropProviderRef=useRef(null);useAnimatedReaction((()=>scrollX.value),(scrolling=>{scrollTo(scrollViewRef,scrolling,0,false)}));const handleScroll=useAnimatedScrollHandler((event=>{scrollX.value=event.contentOffset.x}));const handleScrollEnd=useCallback((()=>{let localScrollTimeout=null;if(localScrollTimeout){clearTimeout(localScrollTimeout)}localScrollTimeout=setTimeout((()=>{var _a;(_a=dropProviderRef.current)===null||_a===void 0?void 0:_a.requestPositionUpdate()}),50)}),[]);const contentWidth=getContentWidth(data.length,itemWidth,gap,paddingHorizontal);const getItemProps=useCallback(((item,index)=>{const id=itemKeyExtractor(item,index);return{id,positions,leftBound:scrollX,autoScrollDirection:autoScroll,itemsCount:data.length,itemWidth,gap,paddingHorizontal}}),[data.length,itemWidth,gap,paddingHorizontal,itemKeyExtractor,positions,scrollX,autoScroll]);return{positions,scrollX,autoScroll,scrollViewRef,dropProviderRef,handleScroll,handleScrollEnd,contentWidth,getItemProps}}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StyleProp, ViewStyle } from "react-native";
|
|
2
|
-
import React from "react";
|
|
3
2
|
import { SharedValue } from "react-native-reanimated";
|
|
3
|
+
import { GestureType } from "react-native-gesture-handler";
|
|
4
4
|
export declare enum ScrollDirection {
|
|
5
5
|
None = "none",
|
|
6
6
|
Up = "up",
|
|
@@ -29,19 +29,26 @@ export interface UseSortableOptions<T> {
|
|
|
29
29
|
lowerBound: SharedValue<number>;
|
|
30
30
|
autoScrollDirection: SharedValue<ScrollDirection>;
|
|
31
31
|
itemsCount: number;
|
|
32
|
-
itemHeight
|
|
32
|
+
itemHeight?: number;
|
|
33
33
|
containerHeight?: number;
|
|
34
|
+
estimatedItemHeight?: number;
|
|
35
|
+
isDynamicHeight?: boolean;
|
|
36
|
+
itemHeights?: SharedValue<{
|
|
37
|
+
[id: string]: number;
|
|
38
|
+
}>;
|
|
34
39
|
onMove?: (id: string, from: number, to: number) => void;
|
|
35
40
|
onDragStart?: (id: string, position: number) => void;
|
|
36
|
-
onDrop?: (id: string, position: number
|
|
41
|
+
onDrop?: (id: string, position: number, allPositions?: {
|
|
42
|
+
[id: string]: number;
|
|
43
|
+
}) => void;
|
|
37
44
|
onDragging?: (id: string, overItemId: string | null, yPosition: number) => void;
|
|
38
|
-
children?: React.ReactNode;
|
|
39
|
-
handleComponent?: React.ComponentType<any>;
|
|
40
45
|
}
|
|
41
46
|
export interface UseSortableReturn {
|
|
42
47
|
animatedStyle: StyleProp<ViewStyle>;
|
|
43
|
-
panGestureHandler:
|
|
48
|
+
panGestureHandler: GestureType;
|
|
49
|
+
handlePanGestureHandler: GestureType;
|
|
44
50
|
isMoving: boolean;
|
|
45
51
|
hasHandle: boolean;
|
|
52
|
+
registerHandle: (registered: boolean) => void;
|
|
46
53
|
}
|
|
47
54
|
export declare function useSortable<T>(options: UseSortableOptions<T>): UseSortableReturn;
|
package/lib/hooks/useSortable.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{useCallback,useMemo,useRef,useState}from"react";import{useAnimatedReaction,useAnimatedStyle,useDerivedValue,useSharedValue,withSpring,withTiming}from"react-native-reanimated";import{Gesture}from"react-native-gesture-handler";import{scheduleOnRN}from"react-native-worklets";import{findPositionForY,getItemCumulativeY}from"../components/sortableUtils";export var ScrollDirection;(function(ScrollDirection){ScrollDirection["None"]="none";ScrollDirection["Up"]="up";ScrollDirection["Down"]="down"})(ScrollDirection||(ScrollDirection={}));export function clamp(value,lowerBound,upperBound){"worklet";return Math.max(lowerBound,Math.min(value,upperBound))}export function objectMove(object,from,to){"worklet";const newObject=Object.assign({},object);const movedUp=to<from;for(const id in object){if(object[id]===from){newObject[id]=to;continue}const currentPosition=object[id];if(movedUp&¤tPosition>=to&¤tPosition<from){newObject[id]++}else if(currentPosition<=to&¤tPosition>from){newObject[id]--}}return newObject}export function listToObject(list){const values=Object.values(list);const object={};for(let i=0;i<values.length;i++){object[values[i].id]=i}return object}export function setPosition(positionY,itemsCount,positions,id,itemHeight){"worklet";const newPosition=clamp(Math.floor(positionY/itemHeight),0,itemsCount-1);if(newPosition!==positions.value[id]){positions.value=objectMove(positions.value,positions.value[id],newPosition)}}export function setAutoScroll(positionY,lowerBound,upperBound,scrollThreshold,autoScroll){"worklet";if(positionY<=lowerBound+scrollThreshold){autoScroll.value=ScrollDirection.Up}else if(positionY>=upperBound-scrollThreshold){autoScroll.value=ScrollDirection.Down}else{autoScroll.value=ScrollDirection.None}}export function useSortable(options){const{id,positions,lowerBound,autoScrollDirection,itemsCount,itemHeight,containerHeight=500,estimatedItemHeight=60,isDynamicHeight=false,itemHeights,onMove,onDragStart,onDrop,onDragging}=options;const effectiveItemHeight=itemHeight||estimatedItemHeight;const[isMoving,setIsMoving]=useState(false);const[hasHandle,setHasHandle]=useState(false);const registerHandle=useCallback((registered=>{setHasHandle(registered)}),[]);const movingSV=useSharedValue(false);const currentOverItemId=useSharedValue(null);const onDraggingLastCallTimestamp=useSharedValue(0);const THROTTLE_INTERVAL=50;const initialTopVal=useMemo((()=>{var _a;const posArr=positions.get();const pos=(_a=posArr===null||posArr===void 0?void 0:posArr[id])!==null&&_a!==void 0?_a:0;return pos*effectiveItemHeight}),[]);const initialLowerBoundVal=useMemo((()=>lowerBound.get()),[]);const positionY=useSharedValue(initialTopVal);const top=useSharedValue(initialTopVal);const targetLowerBound=useSharedValue(initialLowerBoundVal);const initialItemContentY=useSharedValue(0);const initialFingerAbsoluteY=useSharedValue(0);const initialLowerBound=useSharedValue(0);const calculatedContainerHeight=useRef(containerHeight).current;const upperBound=useDerivedValue((()=>lowerBound.value+calculatedContainerHeight));useAnimatedReaction((()=>{var _a;if(isDynamicHeight&&itemHeights){return getItemCumulativeY(id,positions.value,itemHeights.value,estimatedItemHeight)}return((_a=positions.value[id])!==null&&_a!==void 0?_a:0)*effectiveItemHeight}),((newTop,oldTop)=>{if(oldTop!==null&&newTop!==oldTop&&!movingSV.value){top.value=withSpring(newTop)}}),[isDynamicHeight,itemHeights,positions,id,effectiveItemHeight,estimatedItemHeight,movingSV]);useAnimatedReaction((()=>positions.value[id]),((currentPosition,previousPosition)=>{if(currentPosition!==null&&previousPosition!==null&¤tPosition!==previousPosition&&!movingSV.value&&onMove){scheduleOnRN(onMove,id,previousPosition,currentPosition)}}),[movingSV,onMove]);useAnimatedReaction((()=>positionY.value),((currentY,previousY)=>{if(currentY===null||!movingSV.value){return}if(previousY!==null&¤tY===previousY){return}let clampedPosition;if(isDynamicHeight&&itemHeights){clampedPosition=findPositionForY(currentY,positions.value,itemHeights.value,estimatedItemHeight,itemsCount)}else{clampedPosition=Math.min(Math.max(0,Math.ceil(currentY/effectiveItemHeight)),itemsCount-1)}let newOverItemId=null;const positionsObj=positions.value;for(const itemIdIter in positionsObj){if(positionsObj[itemIdIter]===clampedPosition&&itemIdIter!==id){newOverItemId=itemIdIter;break}}if(currentOverItemId.value!==newOverItemId){currentOverItemId.value=newOverItemId}if(onDragging){const now=Date.now();if(now-onDraggingLastCallTimestamp.value>THROTTLE_INTERVAL){scheduleOnRN(onDragging,id,newOverItemId,Math.round(currentY));onDraggingLastCallTimestamp.value=now}}top.value=currentY;if(isDynamicHeight){if(clampedPosition!==positions.value[id]){positions.value=objectMove(positions.value,positions.value[id],clampedPosition)}}else{setPosition(currentY,itemsCount,positions,id,effectiveItemHeight)}setAutoScroll(currentY,lowerBound.value,upperBound.value,effectiveItemHeight,autoScrollDirection)}),[movingSV,effectiveItemHeight,isDynamicHeight,estimatedItemHeight,itemsCount,positions,id,onDragging,lowerBound,upperBound,autoScrollDirection,currentOverItemId,top,onDraggingLastCallTimestamp,itemHeights]);useAnimatedReaction((()=>autoScrollDirection.value),((scrollDirection,previousValue)=>{var _a;if(scrollDirection!==null&&previousValue!==null&&scrollDirection!==previousValue){switch(scrollDirection){case ScrollDirection.Up:{targetLowerBound.value=lowerBound.value;targetLowerBound.value=withTiming(0,{duration:1500});break}case ScrollDirection.Down:{let contentHeight;if(isDynamicHeight&&itemHeights){contentHeight=0;const heights=itemHeights.value;const posObj=positions.value;for(const itemId in posObj){contentHeight+=(_a=heights[itemId])!==null&&_a!==void 0?_a:estimatedItemHeight}}else{contentHeight=itemsCount*effectiveItemHeight}const maxScroll=contentHeight-calculatedContainerHeight;targetLowerBound.value=lowerBound.value;targetLowerBound.value=withTiming(maxScroll,{duration:1500});break}case ScrollDirection.None:{targetLowerBound.value=lowerBound.value;break}}}}),[isDynamicHeight,itemHeights,effectiveItemHeight,estimatedItemHeight,itemsCount,calculatedContainerHeight]);useAnimatedReaction((()=>targetLowerBound.value),((targetLowerBoundValue,previousValue)=>{if(targetLowerBoundValue!==null&&previousValue!==null&&targetLowerBoundValue!==previousValue){if(movingSV.value){lowerBound.value=targetLowerBoundValue}}}),[movingSV]);const createPanGesture=()=>Gesture.Pan().activateAfterLongPress(200).shouldCancelWhenOutside(false).onStart((event=>{"worklet";if(isDynamicHeight&&itemHeights){initialItemContentY.value=getItemCumulativeY(id,positions.value,itemHeights.value,estimatedItemHeight)}else{initialItemContentY.value=positions.value[id]*effectiveItemHeight}initialFingerAbsoluteY.value=event.absoluteY;initialLowerBound.value=lowerBound.value;positionY.value=initialItemContentY.value;movingSV.value=true;scheduleOnRN(setIsMoving,true);if(onDragStart){scheduleOnRN(onDragStart,id,positions.value[id])}})).onUpdate((event=>{"worklet";const fingerDyScreen=event.absoluteY-initialFingerAbsoluteY.value;const scrollDeltaSinceStart=lowerBound.value-initialLowerBound.value;positionY.value=initialItemContentY.value+fingerDyScreen+scrollDeltaSinceStart})).onFinalize((()=>{"worklet";let finishPosition;if(isDynamicHeight&&itemHeights){finishPosition=getItemCumulativeY(id,positions.value,itemHeights.value,estimatedItemHeight)}else{finishPosition=positions.value[id]*effectiveItemHeight}top.value=withTiming(finishPosition);movingSV.value=false;scheduleOnRN(setIsMoving,false);if(onDrop){const positionsCopy={...positions.value};scheduleOnRN(onDrop,id,positions.value[id],positionsCopy)}currentOverItemId.value=null}));const panGestureHandler=createPanGesture().enabled(!hasHandle);const handlePanGestureHandler=createPanGesture();const animatedStyle=useAnimatedStyle((()=>{"worklet";return{position:"absolute",left:0,right:0,top:top.value,zIndex:movingSV.value?1:0,shadowColor:"black",shadowOpacity:withSpring(movingSV.value?.2:0),shadowRadius:10}}),[movingSV]);return{animatedStyle,panGestureHandler,handlePanGestureHandler,isMoving,hasHandle,registerHandle}}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { DropProviderRef } from "../types/context";
|
|
2
2
|
export interface UseSortableListOptions<TData> {
|
|
3
3
|
data: TData[];
|
|
4
|
-
itemHeight: number;
|
|
4
|
+
itemHeight?: number | number[] | ((item: TData, index: number) => number);
|
|
5
|
+
enableDynamicHeights?: boolean;
|
|
6
|
+
estimatedItemHeight?: number;
|
|
7
|
+
onHeightsMeasured?: (heights: {
|
|
8
|
+
[id: string]: number;
|
|
9
|
+
}) => void;
|
|
5
10
|
itemKeyExtractor?: (item: TData, index: number) => string;
|
|
6
11
|
}
|
|
7
12
|
export interface UseSortableListReturn<TData> {
|
|
@@ -9,17 +14,24 @@ export interface UseSortableListReturn<TData> {
|
|
|
9
14
|
scrollY: any;
|
|
10
15
|
autoScroll: any;
|
|
11
16
|
scrollViewRef: any;
|
|
12
|
-
dropProviderRef: React.RefObject<DropProviderRef>;
|
|
17
|
+
dropProviderRef: React.RefObject<DropProviderRef | null>;
|
|
13
18
|
handleScroll: any;
|
|
14
19
|
handleScrollEnd: () => void;
|
|
15
20
|
contentHeight: number;
|
|
21
|
+
isDynamicHeight: boolean;
|
|
22
|
+
itemHeights: any;
|
|
23
|
+
scheduleHeightUpdate?: (id: string, height: number) => void;
|
|
16
24
|
getItemProps: (item: TData, index: number) => {
|
|
17
25
|
id: string;
|
|
18
26
|
positions: any;
|
|
19
27
|
lowerBound: any;
|
|
20
28
|
autoScrollDirection: any;
|
|
21
29
|
itemsCount: number;
|
|
22
|
-
itemHeight
|
|
30
|
+
itemHeight?: number;
|
|
31
|
+
isDynamicHeight: boolean;
|
|
32
|
+
estimatedItemHeight: number;
|
|
33
|
+
itemHeights?: any;
|
|
34
|
+
scheduleHeightUpdate?: (id: string, height: number) => void;
|
|
23
35
|
};
|
|
24
36
|
}
|
|
25
37
|
export declare function useSortableList<TData extends {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useRef,useCallback}from"react";import{scrollTo,useAnimatedReaction,useAnimatedRef,useAnimatedScrollHandler,useSharedValue}from"react-native-reanimated";import{listToObject}from"../components/sortableUtils";import{ScrollDirection}from"../types/sortable";export function useSortableList(options){const{data,itemHeight,itemKeyExtractor=item=>item.id}=options;const positions=useSharedValue(listToObject(data));const scrollY=useSharedValue(0);const autoScroll=useSharedValue(ScrollDirection.None);const scrollViewRef=useAnimatedRef();const dropProviderRef=useRef(null);useAnimatedReaction((()=>scrollY.value),(scrolling=>{scrollTo(scrollViewRef,0,scrolling,false)}));const handleScroll=useAnimatedScrollHandler((event=>{scrollY.value=event.contentOffset.y}));const handleScrollEnd=useCallback((()=>{let localScrollTimeout=null;if(localScrollTimeout){clearTimeout(localScrollTimeout)}localScrollTimeout=setTimeout((()=>{var _a;(_a=dropProviderRef.current)===null||_a===void 0?void 0:_a.requestPositionUpdate()}),50)}),[]);const contentHeight
|
|
1
|
+
import{useRef,useCallback,useMemo,useState,useEffect}from"react";import{scrollTo,useAnimatedReaction,useAnimatedRef,useAnimatedScrollHandler,useSharedValue}from"react-native-reanimated";import{listToObject,resolveItemHeight}from"../components/sortableUtils";import{ScrollDirection}from"../types/sortable";export function useSortableList(options){const{data,itemHeight,enableDynamicHeights=false,estimatedItemHeight=60,onHeightsMeasured,itemKeyExtractor=item=>item.id}=options;const isDynamicHeight=useMemo((()=>{if(enableDynamicHeights){return true}if(typeof itemHeight==="number"){return false}if(itemHeight===undefined){return false}return true}),[enableDynamicHeights,itemHeight]);const needsMeasurement=enableDynamicHeights;if(__DEV__){data.forEach(((item,index)=>{const id=itemKeyExtractor(item,index);if(typeof id!=="string"||!id){console.error(`[react-native-reanimated-dnd] Item at index ${index} has invalid id: ${id}. `+`Each item must have a unique string id property.`)}}));if(!isDynamicHeight&&itemHeight===undefined){console.error("[react-native-reanimated-dnd] itemHeight is required when not using dynamic heights. "+"Either provide itemHeight or set enableDynamicHeights to true.")}}const positions=useSharedValue(listToObject(data));const scrollY=useSharedValue(0);const autoScroll=useSharedValue(ScrollDirection.None);const scrollViewRef=useAnimatedRef();const dropProviderRef=useRef(null);const initialHeights=useMemo((()=>{if(!isDynamicHeight){return{}}const heights={};data.forEach(((item,index)=>{const id=itemKeyExtractor(item,index);heights[id]=resolveItemHeight(itemHeight,item,index,estimatedItemHeight)}));return heights}),[]);const itemHeightsSV=useSharedValue(initialHeights);const fixedContentHeight=typeof itemHeight==="number"?data.length*itemHeight:null;const[dynamicContentHeight,setDynamicContentHeight]=useState((()=>{if(!isDynamicHeight&&fixedContentHeight!==null){return fixedContentHeight}let total=0;data.forEach(((item,index)=>{total+=resolveItemHeight(itemHeight,item,index,estimatedItemHeight)}));return total}));useEffect((()=>{if(!isDynamicHeight){return}const newHeights={};data.forEach(((item,index)=>{const id=itemKeyExtractor(item,index);if(!needsMeasurement){newHeights[id]=resolveItemHeight(itemHeight,item,index,estimatedItemHeight)}else{const existing=itemHeightsSV.value[id];newHeights[id]=existing!==null&&existing!==void 0?existing:estimatedItemHeight}}));itemHeightsSV.value=newHeights;let total=0;data.forEach(((item,index)=>{var _a;const id=itemKeyExtractor(item,index);total+=(_a=newHeights[id])!==null&&_a!==void 0?_a:estimatedItemHeight}));setDynamicContentHeight(total)}),[data,isDynamicHeight,needsMeasurement,itemHeight,estimatedItemHeight,itemKeyExtractor]);const scheduleHeightUpdate=useCallback(((id,height)=>{const rounded=Math.round(height);const prev=itemHeightsSV.value[id];if(prev!==undefined&&Math.abs(prev-rounded)<1){return}const newHeights={...itemHeightsSV.value,[id]:rounded};itemHeightsSV.value=newHeights;let total=0;data.forEach(((item,index)=>{var _a;const itemId=itemKeyExtractor(item,index);total+=(_a=newHeights[itemId])!==null&&_a!==void 0?_a:estimatedItemHeight}));setDynamicContentHeight(total);if(onHeightsMeasured){onHeightsMeasured(newHeights)}}),[data,itemKeyExtractor,estimatedItemHeight,onHeightsMeasured]);useAnimatedReaction((()=>scrollY.value),(scrolling=>{scrollTo(scrollViewRef,0,scrolling,false)}));const handleScroll=useAnimatedScrollHandler((event=>{scrollY.value=event.contentOffset.y}));const handleScrollEnd=useCallback((()=>{let localScrollTimeout=null;if(localScrollTimeout){clearTimeout(localScrollTimeout)}localScrollTimeout=setTimeout((()=>{var _a;(_a=dropProviderRef.current)===null||_a===void 0?void 0:_a.requestPositionUpdate()}),50)}),[]);const contentHeight=!isDynamicHeight&&fixedContentHeight!==null?fixedContentHeight:dynamicContentHeight;const getItemProps=useCallback(((item,index)=>{const id=itemKeyExtractor(item,index);return{id,positions,lowerBound:scrollY,autoScrollDirection:autoScroll,itemsCount:data.length,itemHeight:typeof itemHeight==="number"?itemHeight:undefined,isDynamicHeight,estimatedItemHeight,itemHeights:isDynamicHeight?itemHeightsSV:undefined,scheduleHeightUpdate:needsMeasurement?scheduleHeightUpdate:undefined}}),[data.length,itemHeight,isDynamicHeight,estimatedItemHeight,needsMeasurement,itemKeyExtractor,positions,scrollY,autoScroll,itemHeightsSV,scheduleHeightUpdate]);return{positions,scrollY,autoScroll,scrollViewRef,dropProviderRef,handleScroll,handleScrollEnd,contentHeight,isDynamicHeight,itemHeights:itemHeightsSV,scheduleHeightUpdate:needsMeasurement?scheduleHeightUpdate:undefined,getItemProps}}
|
package/lib/index.d.ts
CHANGED
|
@@ -2,10 +2,17 @@ export { Draggable } from "./components/Draggable";
|
|
|
2
2
|
export { Droppable } from "./components/Droppable";
|
|
3
3
|
export { Sortable } from "./components/Sortable";
|
|
4
4
|
export { SortableItem } from "./components/SortableItem";
|
|
5
|
+
export { SortableGrid } from "./components/SortableGrid";
|
|
6
|
+
export { SortableGridItem } from "./components/SortableGridItem";
|
|
5
7
|
export { DropProvider } from "./context/DropContext";
|
|
6
8
|
export * from "./types";
|
|
7
9
|
export { listToObject, setAutoScroll, setPosition, clamp, objectMove, ScrollDirection, } from "./components/sortableUtils";
|
|
10
|
+
export { calculateGridPosition, calculateIndexFromRowColumn, listToGridObject, getGridCellFromCoordinates, reorderGridInsert, reorderGridSwap, setGridPosition, calculateGridContentDimensions, setGridAutoScroll, findItemIdAtIndex, } from "./utils/gridCalculations";
|
|
8
11
|
export { useDraggable } from "./hooks/useDraggable";
|
|
9
12
|
export { useDroppable } from "./hooks/useDroppable";
|
|
10
13
|
export { useSortable } from "./hooks/useSortable";
|
|
11
14
|
export { useSortableList } from "./hooks/useSortableList";
|
|
15
|
+
export { useHorizontalSortable } from "./hooks/useHorizontalSortable";
|
|
16
|
+
export { useHorizontalSortableList } from "./hooks/useHorizontalSortableList";
|
|
17
|
+
export { useGridSortable } from "./hooks/useGridSortable";
|
|
18
|
+
export { useGridSortableList } from "./hooks/useGridSortableList";
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{Draggable}from"./components/Draggable";export{Droppable}from"./components/Droppable";export{Sortable}from"./components/Sortable";export{SortableItem}from"./components/SortableItem";export{DropProvider}from"./context/DropContext";export*from"./types";export{listToObject,setAutoScroll,setPosition,clamp,objectMove,ScrollDirection}from"./components/sortableUtils";export{useDraggable}from"./hooks/useDraggable";export{useDroppable}from"./hooks/useDroppable";export{useSortable}from"./hooks/useSortable";export{useSortableList}from"./hooks/useSortableList";
|
|
1
|
+
export{Draggable}from"./components/Draggable";export{Droppable}from"./components/Droppable";export{Sortable}from"./components/Sortable";export{SortableItem}from"./components/SortableItem";export{SortableGrid}from"./components/SortableGrid";export{SortableGridItem}from"./components/SortableGridItem";export{DropProvider}from"./context/DropContext";export*from"./types";export{listToObject,setAutoScroll,setPosition,clamp,objectMove,ScrollDirection}from"./components/sortableUtils";export{calculateGridPosition,calculateIndexFromRowColumn,listToGridObject,getGridCellFromCoordinates,reorderGridInsert,reorderGridSwap,setGridPosition,calculateGridContentDimensions,setGridAutoScroll,findItemIdAtIndex}from"./utils/gridCalculations";export{useDraggable}from"./hooks/useDraggable";export{useDroppable}from"./hooks/useDroppable";export{useSortable}from"./hooks/useSortable";export{useSortableList}from"./hooks/useSortableList";export{useHorizontalSortable}from"./hooks/useHorizontalSortable";export{useHorizontalSortableList}from"./hooks/useHorizontalSortableList";export{useGridSortable}from"./hooks/useGridSortable";export{useGridSortableList}from"./hooks/useGridSortableList";
|
package/lib/types/draggable.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface UseDraggableOptions<TData = unknown> {
|
|
|
13
13
|
data: TData;
|
|
14
14
|
draggableId?: string;
|
|
15
15
|
dragDisabled?: boolean;
|
|
16
|
+
preDragDelay?: number;
|
|
16
17
|
onDragStart?: (data: TData) => void;
|
|
17
18
|
onDragEnd?: (data: TData) => void;
|
|
18
19
|
onDragging?: (payload: {
|
|
@@ -37,10 +38,12 @@ export interface UseDraggableReturn {
|
|
|
37
38
|
state: DraggableState;
|
|
38
39
|
animatedViewRef: ReturnType<typeof useAnimatedRef<Animated.View>>;
|
|
39
40
|
hasHandle: boolean;
|
|
41
|
+
registerHandle: (registered: boolean) => void;
|
|
40
42
|
}
|
|
41
43
|
export interface DraggableContextValue {
|
|
42
44
|
gesture: any;
|
|
43
45
|
state: DraggableState;
|
|
46
|
+
registerHandle: (registered: boolean) => void;
|
|
44
47
|
}
|
|
45
48
|
export interface DraggableProps<TData = unknown> extends UseDraggableOptions<TData> {
|
|
46
49
|
style?: StyleProp<ViewStyle>;
|