mgcrea / react-native-dnd

Modern and easy-to-use drag&drop library for react-native.
https://mgcrea.github.io/react-native-dnd/
MIT License
147 stars 15 forks source link

How to make draggable components stay inside droppable components after being released over them? #15

Open cleidsondru opened 10 months ago

cleidsondru commented 10 months ago

Hi @mgcrea,

I have been trying to use the demo code of your lib but every time I drop the draggable component over the droppable it moves back to it first position.

https://github.com/mgcrea/react-native-dnd/assets/59833097/8a938229-3c8e-43fc-9405-2bd4b7fdb7ad

In this example, the big squares are droppable components and the green squares are draggable components.

// Custom Dropable
import { useDroppable, UseDraggableOptions } from "@mgcrea/react-native-dnd";
import { type FunctionComponent, type PropsWithChildren } from "react";
import { type ViewProps } from "react-native";
import Animated, {
  AnimateProps,
  useAnimatedStyle,
} from "react-native-reanimated";

export type MyDroppableProps = AnimateProps<ViewProps> & UseDraggableOptions;

export const MyDroppable: FunctionComponent<
  PropsWithChildren<MyDroppableProps>
> = ({ children, id, disabled, data, style, ...otherProps }) => {
  const { setNodeRef, setNodeLayout, activeId } = useDroppable({
    id,
    disabled,
    data,
  });

  const animatedStyle = useAnimatedStyle(() => {
    const isActive = activeId.value === id;
    const style = {
      opacity: isActive ? 0.9 : 1,
    };
    return style;
  }, [id]);

  return (
    <Animated.View
      ref={setNodeRef}
      onLayout={setNodeLayout}
      style={[style, animatedStyle]}
      {...otherProps}
    >
      {children}
    </Animated.View>
  );
};
//Custom Dragble component
import {
  useDraggable,
  type UseDraggableOptions,
} from "@mgcrea/react-native-dnd";
import { type FunctionComponent, type PropsWithChildren } from "react";
import { type ViewProps } from "react-native";
import Animated, {
  AnimateProps,
  useAnimatedStyle,
  withSpring,
} from "react-native-reanimated";

export type MyDraggableProps = AnimateProps<ViewProps> & UseDraggableOptions;

export const MyDraggable: FunctionComponent<
  PropsWithChildren<MyDraggableProps>
> = ({
  children,
  id,
  data,
  disabled,
  activationDelay,
  activationTolerance,
  style,
  ...otherProps
}) => {
  const { setNodeRef, setNodeLayout, activeId, actingId, offset } =
    useDraggable({
      id,
      data,
      disabled,
      activationDelay,
      activationTolerance,
    });

  const animatedStyle = useAnimatedStyle(() => {
    const isActive = activeId.value === id;
    const isResting = actingId.value !== id;
    const style = {
      opacity: isActive ? 0.9 : 1,
      zIndex: isActive ? 999 : 1,
      transform: [
        {
          translateX: isActive ? offset.x.value : withSpring(offset.x.value),
        },
        {
          translateY: isActive ? offset.y.value : withSpring(offset.y.value),
        },
      ],
    };
    return style;
  }, [id]);

  return (
    <Animated.View
      ref={setNodeRef}
      onLayout={setNodeLayout}
      style={[style, animatedStyle]}
      {...otherProps}
    >
      {children}
    </Animated.View>
  );
};
// Board screen
import { DndProvider, DndProviderProps } from "@mgcrea/react-native-dnd";
import React from "react";
import { SafeAreaView, StyleSheet, Text } from "react-native";
import { GestureHandlerRootView, State } from "react-native-gesture-handler";

import { MyDraggable } from "../components/MyDraggable";
import { MyDroppable } from "../components/MyDroppsble";
import { DarkColors } from "../util/colors";

const BoardScreen = () => {
  const handleDragEnd: DndProviderProps["onDragEnd"] = ({ active, over }) => {
    "worklet";

    if (over) {
      console.log("onDragEnd", over.id, active.data.value);
    }
  };

  const handleBegin: DndProviderProps["onBegin"] = () => {
    "worklet";
    console.log("onBegin");
  };

  const handleFinalize: DndProviderProps["onFinalize"] = ({ state }) => {
    "worklet";
    console.log("onFinalize");
    if (state !== State.FAILED) {
      console.log("onFinalize");
    }
  };

  return (
    <SafeAreaView style={styles.mainContainer}>
      <GestureHandlerRootView>
        <DndProvider
          onBegin={handleBegin}
          onFinalize={handleFinalize}
          onDragEnd={handleDragEnd}
        >
          <MyDroppable id="drop1" style={styles.dropBox}>
            <MyDraggable id="drag1" style={styles.box}>
              <Text>Drag1</Text>
            </MyDraggable>
          </MyDroppable>
          <MyDroppable id="drop2" style={styles.dropBox}>
            <MyDraggable id="drag2" style={styles.box}>
              <Text>Drag2</Text>
            </MyDraggable>
            <MyDraggable id="drag3" style={styles.box}>
              <Text>Drag3</Text>
            </MyDraggable>
          </MyDroppable>
        </DndProvider>
      </GestureHandlerRootView>
    </SafeAreaView>
  );
};

export default BoardScreen;

const styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
    backgroundColor: DarkColors.white,
  },
  box: {
    margin: 5,
    padding: 24,
    height: 128,
    width: 128,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "darkseagreen",
  },
  dropBox: {
    margin: 24,
    padding: 24,
    height: "50%",
    width: "auto",
    justifyContent: "center",
    alignItems: "center",
    borderWidth: 1,
    flexShrink: 1,
  },
});

Above is the code I am using. Am I missing something?

rssj commented 10 months ago

I'm facing the same problem

mgcrea commented 9 months ago

There is no built-in way to do that, what you can do: