Skip to content

Commit

Permalink
chore(project): major refactoring, remove existing grid feature
Browse files Browse the repository at this point in the history
  • Loading branch information
mgcrea committed Dec 19, 2023
1 parent d9440e6 commit 14159a8
Show file tree
Hide file tree
Showing 20 changed files with 156 additions and 713 deletions.
8 changes: 4 additions & 4 deletions src/DndContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ export type DndContextValue = {
draggableOptions: SharedValue<DraggableOptions>;
droppableOptions: SharedValue<DroppableOptions>;
draggableOffsets: SharedValue<Offsets>;
draggableRestingOffsets: SharedValue<Offsets>;
draggableStates: SharedValue<DraggableStates>;
draggablePendingId: SharedValue<UniqueIdentifier | null>;
draggableActiveId: SharedValue<UniqueIdentifier | null>;
droppableActiveId: SharedValue<UniqueIdentifier | null>;
panGestureState: SharedValue<GestureEventPayload["state"]>;
draggableActiveOffset: SharedPoint;
draggableActingOffset: SharedPoint;
draggableRestingOffset: SharedPoint;
draggableActiveLayout: SharedValue<LayoutRectangle | null>;
draggableInitialOffset: SharedPoint;
draggableContentOffset: SharedPoint;
panGestureState: SharedValue<GestureEventPayload["state"]>;
};

// @ts-expect-error ignore detached state
Expand Down
99 changes: 62 additions & 37 deletions src/DndProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
includesPoint,
overlapsRectangle,
Point,
Rectangle,
} from "./utils";

export type DndProviderProps = {
Expand Down Expand Up @@ -64,7 +65,7 @@ export type DndProviderProps = {

export type DndProviderHandle = Pick<
DndContextValue,
"draggableLayouts" | "draggableOffsets" | "draggableActiveId" | "draggableRestingOffset"
"draggableLayouts" | "draggableOffsets" | "draggableRestingOffsets" | "draggableActiveId"
>;

export const DndProvider = forwardRef<DndProviderHandle, PropsWithChildren<DndProviderProps>>(
Expand All @@ -91,13 +92,13 @@ export const DndProvider = forwardRef<DndProviderHandle, PropsWithChildren<DndPr
const draggableOptions = useSharedValue<DraggableOptions>({});
const droppableOptions = useSharedValue<DroppableOptions>({});
const draggableOffsets = useSharedValue<Offsets>({});
const draggableRestingOffsets = useSharedValue<Offsets>({});
const draggableStates = useSharedValue<DraggableStates>({});
const draggablePendingId = useSharedValue<UniqueIdentifier | null>(null);
const draggableActiveId = useSharedValue<UniqueIdentifier | null>(null);
const droppableActiveId = useSharedValue<UniqueIdentifier | null>(null);
const draggableActiveOffset = useSharedPoint(0, 0);
const draggableActingOffset = useSharedPoint(0, 0);
const draggableRestingOffset = useSharedPoint(0, 0);
const draggableActiveLayout = useSharedValue<Rectangle | null>(null);
const draggableInitialOffset = useSharedPoint(0, 0);
const draggableContentOffset = useSharedPoint(0, 0);
const panGestureState = useSharedValue<GestureEventPayload["state"]>(0);

Expand Down Expand Up @@ -126,14 +127,14 @@ export const DndProvider = forwardRef<DndProviderHandle, PropsWithChildren<DndPr
draggableOptions,
droppableOptions,
draggableOffsets,
draggableRestingOffsets,
draggableStates,
draggablePendingId,
draggableActiveId,
droppableActiveId,
panGestureState,
draggableActiveOffset,
draggableActingOffset,
draggableRestingOffset,
draggableInitialOffset,
draggableActiveLayout,
draggableContentOffset,
});

Expand All @@ -143,8 +144,8 @@ export const DndProvider = forwardRef<DndProviderHandle, PropsWithChildren<DndPr
return {
draggableLayouts,
draggableOffsets,
draggableRestingOffsets,
draggableActiveId,
draggableRestingOffset,
};
},
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -220,49 +221,58 @@ export const DndProvider = forwardRef<DndProviderHandle, PropsWithChildren<DndPr
panGestureState.value = state;
const { value: layouts } = draggableLayouts;
const { value: offsets } = draggableOffsets;
const { value: restingOffsets } = draggableRestingOffsets;
const { value: options } = draggableOptions;
const { value: states } = draggableStates;
// for (const [id, offset] of Object.entries(offsets)) {
// console.log({ [id]: [offset.x.value, offset.y.value] });
// }
// Find the active layout key under {x, y}
const activeId = findActiveLayoutId({ x, y });
// Update shared state
draggableActingOffset.x.value = x;
draggableActingOffset.y.value = y;
// Check if an item was actually selected
if (activeId !== null) {
// Update activeId directly or with an optional delay
const { activationDelay } = options[activeId];
if (activationDelay > 0) {
draggablePendingId.value = activeId;
draggableStates.value[activeId].value = "pending";
runOnJS(setActiveId)(activeId, activationDelay);
} else {
draggableActiveId.value = activeId;
draggableStates.value[activeId].value = "dragging";
}
// Record any ongoing current offset as our initial offset for the gesture
const activeLayout = layouts[activeId].value;
const activeOffset = offsets[activeId];
const restingOffset = restingOffsets[activeId];
const { value: activeState } = states[activeId];
draggableActiveOffset.x.value = activeOffset.x.value;
draggableActiveOffset.y.value = activeOffset.y.value;
draggableInitialOffset.x.value = activeOffset.x.value;
draggableInitialOffset.y.value = activeOffset.y.value;
// Cancel the ongoing animation if we just reactivated an acting/dragging item
if (["dragging", "acting"].includes(activeState)) {
cancelAnimation(activeOffset.x);
cancelAnimation(activeOffset.y);
// If not we should reset the resting offset to the current offset value
// But only if the item is not currently still animating
} else if (activeState === "resting") {
draggableRestingOffset.x.value = activeOffset.x.value;
draggableRestingOffset.y.value = activeOffset.y.value;
} else {
// active or pending
// Record current offset as our natural resting offset for the gesture
restingOffset.x.value = activeOffset.x.value;
restingOffset.y.value = activeOffset.y.value;
}
// Update activeId directly or with an optional delay
const { activationDelay } = options[activeId];
if (activationDelay > 0) {
draggablePendingId.value = activeId;
draggableStates.value[activeId].value = "pending";
runOnJS(setActiveId)(activeId, activationDelay);
// @TODO activeLayout
} else {
draggableActiveId.value = activeId;
draggableActiveLayout.value = applyOffset(activeLayout, {
x: activeOffset.x.value,
y: activeOffset.y.value,
});
draggableStates.value[activeId].value = "dragging";
}
if (onBegin) {
const activeLayout = layouts[activeId].value;
onBegin(event, { activeId, activeLayout });
}
}
})
.onUpdate((event) => {
// console.log(draggableStates.value);
const { state, translationX, translationY, x, y } = event;
const { state, translationX, translationY } = event;
debug && console.log("update", { state, translationX, translationY });
// Track current state for cancellation purposes
panGestureState.value = state;
Expand All @@ -286,21 +296,19 @@ export const DndProvider = forwardRef<DndProviderHandle, PropsWithChildren<DndPr
// Ignore item-free interactions
return;
}
draggableActingOffset.x.value = x;
draggableActingOffset.y.value = y;
// Update our active offset to pan the active item
const activeOffset = offsets[activeId];
activeOffset.x.value = translationX + draggableActiveOffset.x.value;
activeOffset.y.value = translationY + draggableActiveOffset.y.value;
activeOffset.x.value = draggableInitialOffset.x.value + translationX;
activeOffset.y.value = draggableInitialOffset.y.value + translationY;
// Check potential droppable candidates
const activeLayout = layouts[activeId].value;
const updatedLayout = applyOffset(activeLayout, {
draggableActiveLayout.value = applyOffset(activeLayout, {
x: activeOffset.x.value,
y: activeOffset.y.value,
});
droppableActiveId.value = findDroppableLayoutId(updatedLayout);
droppableActiveId.value = findDroppableLayoutId(draggableActiveLayout.value);
if (onUpdate) {
onUpdate(event, { activeId, activeLayout: updatedLayout });
onUpdate(event, { activeId, activeLayout: draggableActiveLayout.value });
}
})
.onFinalize((event) => {
Expand All @@ -312,6 +320,7 @@ export const DndProvider = forwardRef<DndProviderHandle, PropsWithChildren<DndPr
const { value: pendingId } = draggablePendingId;
const { value: layouts } = draggableLayouts;
const { value: offsets } = draggableOffsets;
const { value: restingOffsets } = draggableRestingOffsets;
const { value: states } = draggableStates;
// Ignore item-free interactions
if (activeId === null) {
Expand Down Expand Up @@ -345,16 +354,32 @@ export const DndProvider = forwardRef<DndProviderHandle, PropsWithChildren<DndPr
droppableActiveId.value = null;
// Move back to initial position
const activeOffset = offsets[activeId];
const restingOffset = restingOffsets[activeId];
states[activeId].value = "acting";
const [targetX, targetY] = [restingOffset.x.value, restingOffset.y.value];
animatePointWithSpring(
activeOffset,
[draggableRestingOffset.x.value, draggableRestingOffset.y.value],
[targetX, targetY],
[
{ ...springConfig, velocity: velocityX },
{ ...springConfig, velocity: velocityY },
],
() => {
([finishedX, finishedY]) => {
// Cancel if we are interacting again with this item
if (
panGestureState.value !== State.END &&
panGestureState.value !== State.FAILED &&
states[activeId].value !== "acting"
) {
return;
}
states[activeId].value = "resting";
if (!finishedX || !finishedY) {
// console.log(`${activeId} did not finish to reach ${targetX.toFixed(2)} ${currentX}`);
}
// for (const [id, offset] of Object.entries(offsets)) {
// console.log({ [id]: [offset.x.value.toFixed(2), offset.y.value.toFixed(2)] });
// }
},
);
})
Expand Down
14 changes: 10 additions & 4 deletions src/components/Draggable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { type FunctionComponent, type PropsWithChildren } from "react";
import { type ViewProps } from "react-native";
import Animated, { useAnimatedProps, useAnimatedStyle, type AnimatedProps } from "react-native-reanimated";
import Animated, { useAnimatedStyle, withSpring, type AnimatedProps } from "react-native-reanimated";
import { useDraggable, type DraggableConstraints, type UseDroppableOptions } from "../hooks";
import type { AnimatedStyleWorklet } from "../types";

Expand Down Expand Up @@ -60,18 +60,24 @@ export const Draggable: FunctionComponent<PropsWithChildren<DraggableProps>> = (
zIndex,
transform: [
{
translateX: offset.x.value,
// translateX: offset.x.value,
translateX: isActive
? offset.x.value
: withSpring(offset.x.value, { damping: 100, stiffness: 1000 }),
},
{
translateY: offset.y.value,
// translateY: offset.y.value,
translateY: isActive
? offset.y.value
: withSpring(offset.y.value, { damping: 100, stiffness: 1000 }),
},
],
};
if (animatedStyleWorklet) {
Object.assign(style, animatedStyleWorklet(style, { isActive, isActing, isDisabled: !!disabled }));
}
return style;
}, [id, activeOpacity]);
}, [id, state, activeOpacity]);

return (
<Animated.View ref={setNodeRef} onLayout={setNodeLayout} style={[style, animatedStyle]} {...otherProps}>
Expand Down
Loading

0 comments on commit 14159a8

Please sign in to comment.