diff --git a/src/components/AnimatedComponents/AnimatedFlatList.ts b/src/components/AnimatedComponents/AnimatedFlatList.ts
deleted file mode 100644
index 6fe82c3740d..00000000000
--- a/src/components/AnimatedComponents/AnimatedFlatList.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { FlatList } from 'react-native-gesture-handler';
-import Animated from 'react-native-reanimated';
-
-export const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
diff --git a/src/components/DappBrowser/BrowserTab.tsx b/src/components/DappBrowser/BrowserTab.tsx
index 893b0c7f86b..b6fee2bbc05 100644
--- a/src/components/DappBrowser/BrowserTab.tsx
+++ b/src/components/DappBrowser/BrowserTab.tsx
@@ -123,7 +123,7 @@ const HomepageOrWebView = ({
const isOnHomepage = useBrowserStore(state => !state.getTabData?.(tabId)?.url || state.getTabData?.(tabId)?.url === RAINBOW_HOME);
return isOnHomepage ? (
-
+
) : (
{
+export const Homepage = () => {
const { goToUrl } = useBrowserContext();
const { isDarkMode } = useColorMode();
- const backgroundStyle = isDarkMode ? styles.pageBackgroundDark : styles.pageBackgroundLight;
-
return (
-
+
-
+
-
+
-
+
);
@@ -122,90 +114,11 @@ const Trending = ({ goToUrl }: { goToUrl: (url: string) => void }) => {
);
};
-const Favorites = ({ goToUrl, tabId }: { goToUrl: (url: string) => void; tabId: string }) => {
- const { animatedTabUrls, activeTabInfo, currentlyOpenTabIds, tabViewProgress, tabViewVisible } = useBrowserContext();
-
- const [localGridSort, setLocalGridSort] = useState(() => {
- const orderedIds = useFavoriteDappsStore.getState().getOrderedIds();
- return orderedIds && orderedIds.length > 0 ? orderedIds : undefined;
- });
-
- const favoriteDapps = useFavoriteDappsStore(state => state.getFavorites(localGridSort));
- const gridKey = useMemo(() => localGridSort?.join('-'), [localGridSort]);
- const isFirstRender = useRef(true);
-
- const reorderFavorites = useFavoriteDappsStore(state => state.reorderFavorites);
-
- const onGridOrderChange: DraggableGridProps['onOrderChange'] = useCallback(
- (value: UniqueIdentifier[]) => {
- reorderFavorites(value as string[]);
- },
- [reorderFavorites]
- );
-
- const reinitializeGridSort = useCallback(() => {
- setLocalGridSort(useFavoriteDappsStore.getState().getOrderedIds());
- }, []);
-
- const needsToSyncWorklet = useCallback(
- ({ currentGridSort, isActiveTab }: { currentGridSort: string[] | undefined; isActiveTab: boolean }) => {
- 'worklet';
- const homepageTabsCount = currentlyOpenTabIds.value.filter(
- tabId => !animatedTabUrls.value[tabId] || animatedTabUrls.value[tabId] === DEFAULT_TAB_URL
- ).length;
- const inactiveAndMounted = !isActiveTab && currentGridSort !== undefined;
-
- if (homepageTabsCount === 1) {
- if (inactiveAndMounted) return true;
- return false;
- }
-
- const activeAndUnmounted = isActiveTab && !currentGridSort;
-
- return activeAndUnmounted || inactiveAndMounted;
- },
- [animatedTabUrls, currentlyOpenTabIds]
- );
-
- // Unmount drag and drop grid on inactive homepage tabs
- useAnimatedReaction(
- () => ({
- needsToSync: needsToSyncWorklet({ currentGridSort: localGridSort, isActiveTab: activeTabInfo.value.tabId === tabId }),
- tabAnimationProgress: tabViewProgress.value,
- }),
- (current, previous) => {
- if (!previous || (!current.needsToSync && current.tabAnimationProgress === previous.tabAnimationProgress) || !favoriteDapps.length) {
- return;
- }
-
- const enterTabViewAnimationIsComplete =
- !tabViewVisible.value && previous.tabAnimationProgress < 0 && current.tabAnimationProgress >= 0;
-
- if (!enterTabViewAnimationIsComplete) return;
-
- if (activeTabInfo.value.tabId === tabId) {
- runOnJS(reinitializeGridSort)();
- } else {
- runOnJS(setLocalGridSort)(undefined);
- }
- },
- []
- );
-
- // Reinitialize grid sort when favorites are added or removed
- useLayoutEffect(() => {
- if (isFirstRender.current) {
- isFirstRender.current = false;
- return;
- }
- if (!favoriteDapps.length || !useBrowserStore.getState().isTabActive(tabId)) {
- return;
- }
- reinitializeGridSort();
- }, [favoriteDapps.length, reinitializeGridSort, tabId]);
+const Favorites = ({ goToUrl }: { goToUrl: (url: string) => void }) => {
+ const favoriteDapps = useFavoriteDappsStore(state => state.favoriteDapps);
return (
-
+
@@ -214,35 +127,14 @@ const Favorites = ({ goToUrl, tabId }: { goToUrl: (url: string) => void; tabId:
{i18n.t(i18n.l.dapp_browser.homepage.favorites)}
- {favoriteDapps.length > 0 && localGridSort ? (
-
-
- {favoriteDapps.map(dapp =>
- dapp ? (
-
-
-
- ) : null
- )}
-
-
- ) : (
-
- {favoriteDapps.length > 0
- ? favoriteDapps.map(dapp => )
- : Array(4)
- .fill(null)
- .map((_, index) => )}
-
- )}
-
+
+ {favoriteDapps.length > 0
+ ? favoriteDapps.map(dapp => )
+ : Array(4)
+ .fill(null)
+ .map((_, index) => )}
+
+
);
};
@@ -272,7 +164,7 @@ const Recents = ({ goToUrl }: { goToUrl: (url: string) => void }) => {
);
};
-const Card = memo(function Card({
+const Card = React.memo(function Card({
goToUrl,
site,
showMenuButton,
@@ -391,7 +283,25 @@ const Card = memo(function Card({
width={{ custom: CARD_WIDTH }}
>
-
+ {(site.screenshot || dappIconUrl) && (
+
+
+
+
+
+
+ )}
-
+
{IS_IOS ? (
) : (
@@ -451,19 +361,13 @@ const Card = memo(function Card({
-
+
@@ -475,47 +379,7 @@ const Card = memo(function Card({
);
});
-const CardBackground = memo(function CardBackgroundOverlay({
- imageUrl,
- isDarkMode,
-}: {
- imageUrl: string | undefined;
- isDarkMode: boolean;
-}) {
- return imageUrl ? (
-
-
- {IS_IOS ? (
- <>
-
- {!isDarkMode && (
-
- )}
- >
- ) : (
-
- )}
-
- ) : null;
-});
-
-export const PlaceholderCard = memo(function PlaceholderCard() {
+export const PlaceholderCard = React.memo(function PlaceholderCard() {
const { isDarkMode } = useColorMode();
const fillTertiary = useBackgroundColor('fillTertiary');
@@ -553,69 +417,56 @@ export const PlaceholderCard = memo(function PlaceholderCard() {
);
});
-export const Logo = memo(function Logo({ goToUrl, site }: { goToUrl: (url: string) => void; site: FavoritedSite }) {
+export const Logo = React.memo(function Logo({ goToUrl, site }: { goToUrl: (url: string) => void; site: Omit }) {
const { isDarkMode } = useColorMode();
- const imageOrFallback = useMemo(() => {
- return (
- <>
- {site.image && (
-
- )}
-
-
-
- {!site.image && (
-
-
-
-
-
- )}
- >
- );
- }, [isDarkMode, site.image]);
-
return (
goToUrl(site.url)}>
- {imageOrFallback}
+
+ {IS_IOS && !site.image && (
+
+
+
+
+
+ )}
+
+ {IS_IOS && (
+
+ )}
+
-
+
{site.name}
@@ -626,13 +477,13 @@ export const Logo = memo(function Logo({ goToUrl, site }: { goToUrl: (url: strin
);
});
-export const PlaceholderLogo = memo(function PlaceholderLogo() {
+export const PlaceholderLogo = React.memo(function PlaceholderLogo() {
const { isDarkMode } = useColorMode();
const borderRadius = IS_ANDROID ? LOGO_BORDER_RADIUS / 2 : LOGO_BORDER_RADIUS;
return (
-
+
{IS_IOS && (
= {
name: tabData?.title || '',
url: tabData?.url || '',
image: tabData?.logoUrl || '',
diff --git a/src/components/DappBrowser/search-input/SearchInput.tsx b/src/components/DappBrowser/search-input/SearchInput.tsx
index 68b1979fefc..7c880d75981 100644
--- a/src/components/DappBrowser/search-input/SearchInput.tsx
+++ b/src/components/DappBrowser/search-input/SearchInput.tsx
@@ -22,8 +22,9 @@ import { IS_IOS } from '@/env';
import * as i18n from '@/languages';
import { fontWithWidth } from '@/styles';
import font from '@/styles/fonts';
+import { Site } from '@/state/browserHistory';
import { useBrowserStore } from '@/state/browser/browserStore';
-import { FavoritedSite, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore';
+import { useFavoriteDappsStore } from '@/state/favoriteDapps';
import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton';
import { THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants';
import { FadeMask } from '@/__swaps__/screens/Swap/components/FadeMask';
@@ -63,7 +64,7 @@ const TheeDotMenu = function TheeDotMenu({
if (isFavorite) {
removeFavorite(url);
} else {
- const site: FavoritedSite = {
+ const site: Omit = {
name: getNameFromFormattedUrl(formattedUrlValue.value),
url: url,
image: useBrowserStore.getState().getActiveTabLogo() || `https://${formattedUrlValue.value}/apple-touch-icon.png`,
diff --git a/src/components/drag-and-drop/DndContext.ts b/src/components/drag-and-drop/DndContext.ts
deleted file mode 100644
index 6e58313f1c2..00000000000
--- a/src/components/drag-and-drop/DndContext.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { createContext, useContext, type RefObject } from 'react';
-import type { LayoutRectangle, View } from 'react-native';
-import type { GestureEventPayload } from 'react-native-gesture-handler';
-import type { SharedValue } from 'react-native-reanimated';
-import type { DraggableConstraints, SharedPoint } from './hooks';
-import type { SharedData, UniqueIdentifier } from './types';
-
-export type ItemOptions = { id: UniqueIdentifier; data: SharedData; disabled: boolean };
-export type DraggableItemOptions = ItemOptions & DraggableConstraints;
-export type DraggableOptions = Record;
-export type DroppableOptions = Record;
-export type Layouts = Record>;
-export type Offsets = Record;
-export type DraggableState = 'resting' | 'pending' | 'dragging' | 'dropping' | 'acting';
-export type DraggableStates = Record>;
-
-export type DndContextValue = {
- containerRef: RefObject;
- draggableLayouts: SharedValue;
- droppableLayouts: SharedValue;
- draggableOptions: SharedValue;
- droppableOptions: SharedValue;
- draggableOffsets: SharedValue;
- draggableRestingOffsets: SharedValue;
- draggableStates: SharedValue;
- draggablePendingId: SharedValue;
- draggableActiveId: SharedValue;
- droppableActiveId: SharedValue;
- draggableActiveLayout: SharedValue;
- draggableInitialOffset: SharedPoint;
- draggableContentOffset: SharedPoint;
- panGestureState: SharedValue;
-};
-
-// @ts-expect-error ignore detached state
-export const DndContext = createContext(null);
-
-export const useDndContext = () => {
- return useContext(DndContext);
-};
diff --git a/src/components/drag-and-drop/DndProvider.tsx b/src/components/drag-and-drop/DndProvider.tsx
deleted file mode 100644
index 5f0ad9553e6..00000000000
--- a/src/components/drag-and-drop/DndProvider.tsx
+++ /dev/null
@@ -1,428 +0,0 @@
-import React, {
- ComponentType,
- forwardRef,
- MutableRefObject,
- PropsWithChildren,
- RefObject,
- useCallback,
- useImperativeHandle,
- useMemo,
- useRef,
-} from 'react';
-import { LayoutRectangle, StyleProp, View, ViewStyle } from 'react-native';
-import {
- Gesture,
- GestureDetector,
- GestureEventPayload,
- GestureStateChangeEvent,
- GestureUpdateEvent,
- PanGesture,
- PanGestureHandlerEventPayload,
- State,
-} from 'react-native-gesture-handler';
-import ReactNativeHapticFeedback, { HapticFeedbackTypes } from 'react-native-haptic-feedback';
-import { cancelAnimation, runOnJS, useAnimatedReaction, useSharedValue, type WithSpringConfig } from 'react-native-reanimated';
-import { useAnimatedTimeout } from '@/hooks/reanimated/useAnimatedTimeout';
-import {
- DndContext,
- DraggableStates,
- type DndContextValue,
- type DraggableOptions,
- type DroppableOptions,
- type ItemOptions,
- type Layouts,
- type Offsets,
-} from './DndContext';
-import { useSharedPoint } from './hooks';
-import type { UniqueIdentifier } from './types';
-import { animatePointWithSpring, applyOffset, getDistance, includesPoint, overlapsRectangle, Point, Rectangle } from './utils';
-
-export type DndProviderProps = {
- activationDelay?: number;
- debug?: boolean;
- disabled?: boolean;
- gestureRef?: MutableRefObject;
- hapticFeedback?: HapticFeedbackTypes;
- minDistance?: number;
- onBegin?: (
- event: GestureStateChangeEvent,
- meta: { activeId: UniqueIdentifier; activeLayout: LayoutRectangle }
- ) => void;
- onDragEnd?: (event: { active: ItemOptions; over: ItemOptions | null }) => void;
- onFinalize?: (
- event: GestureStateChangeEvent,
- meta: { activeId: UniqueIdentifier; activeLayout: LayoutRectangle }
- ) => void;
- onUpdate?: (
- event: GestureUpdateEvent,
- meta: { activeId: UniqueIdentifier; activeLayout: LayoutRectangle }
- ) => void;
- simultaneousHandlers?: RefObject>;
- springConfig?: WithSpringConfig;
- style?: StyleProp;
- waitFor?: RefObject>;
-};
-
-export type DndProviderHandle = Pick<
- DndContextValue,
- 'draggableLayouts' | 'draggableOffsets' | 'draggableRestingOffsets' | 'draggableActiveId'
->;
-
-export const DndProvider = forwardRef>(function DndProvider(
- {
- activationDelay = 0,
- children,
- debug,
- disabled,
- gestureRef,
- hapticFeedback,
- minDistance = 0,
- onBegin,
- onDragEnd,
- onFinalize,
- onUpdate,
- simultaneousHandlers,
- springConfig = {},
- style,
- waitFor,
- },
- ref
-) {
- const containerRef = useRef(null);
- const draggableLayouts = useSharedValue({});
- const droppableLayouts = useSharedValue({});
- const draggableOptions = useSharedValue({});
- const droppableOptions = useSharedValue({});
- const draggableOffsets = useSharedValue({});
- const draggableRestingOffsets = useSharedValue({});
- const draggableStates = useSharedValue({});
- const draggablePendingId = useSharedValue(null);
- const draggableActiveId = useSharedValue(null);
- const droppableActiveId = useSharedValue(null);
- const draggableActiveLayout = useSharedValue(null);
- const draggableInitialOffset = useSharedPoint(0, 0);
- const draggableContentOffset = useSharedPoint(0, 0);
- const panGestureState = useSharedValue(0);
-
- const runFeedback = () => {
- if (hapticFeedback) {
- ReactNativeHapticFeedback.trigger(hapticFeedback);
- }
- };
-
- useAnimatedReaction(
- () => (hapticFeedback ? draggableActiveId.value : null),
- current => {
- if (current !== null) {
- runOnJS(runFeedback)();
- }
- },
- []
- );
-
- const contextValue = useRef({
- containerRef,
- draggableLayouts,
- droppableLayouts,
- draggableOptions,
- droppableOptions,
- draggableOffsets,
- draggableRestingOffsets,
- draggableStates,
- draggablePendingId,
- draggableActiveId,
- droppableActiveId,
- panGestureState,
- draggableInitialOffset,
- draggableActiveLayout,
- draggableContentOffset,
- });
-
- useImperativeHandle(
- ref,
- () => {
- return {
- draggableLayouts,
- draggableOffsets,
- draggableRestingOffsets,
- draggableActiveId,
- };
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- []
- );
-
- const setActiveId = useCallback(() => {
- 'worklet';
- const id = draggablePendingId.value;
-
- if (id !== null) {
- debug && console.log(`draggableActiveId.value = ${id}`);
- draggableActiveId.value = id;
-
- const { value: layouts } = draggableLayouts;
- const { value: offsets } = draggableOffsets;
- const { value: activeLayout } = layouts[id];
- const activeOffset = offsets[id];
-
- draggableActiveLayout.value = applyOffset(activeLayout, {
- x: activeOffset.x.value,
- y: activeOffset.y.value,
- });
- draggableStates.value[id].value = 'dragging';
- }
- }, [debug, draggableActiveId, draggableActiveLayout, draggableLayouts, draggableOffsets, draggablePendingId, draggableStates]);
-
- const { clearTimeout: clearActiveIdTimeout, start: setActiveIdWithDelay } = useAnimatedTimeout({
- delayMs: activationDelay,
- onTimeoutWorklet: setActiveId,
- });
-
- const panGesture = useMemo(() => {
- const findActiveLayoutId = (point: Point): UniqueIdentifier | null => {
- 'worklet';
- const { x, y } = point;
- const { value: layouts } = draggableLayouts;
- const { value: offsets } = draggableOffsets;
- const { value: options } = draggableOptions;
- for (const [id, layout] of Object.entries(layouts)) {
- // console.log({ [id]: floorLayout(layout.value) });
- const offset = offsets[id];
- const isDisabled = options[id].disabled;
- if (
- !isDisabled &&
- includesPoint(layout.value, {
- x: x - offset.x.value + draggableContentOffset.x.value,
- y: y - offset.y.value + draggableContentOffset.y.value,
- })
- ) {
- return id;
- }
- }
- return null;
- };
-
- const findDroppableLayoutId = (activeLayout: LayoutRectangle): UniqueIdentifier | null => {
- 'worklet';
- const { value: layouts } = droppableLayouts;
- const { value: options } = droppableOptions;
- for (const [id, layout] of Object.entries(layouts)) {
- // console.log({ [id]: floorLayout(layout.value) });
- const isDisabled = options[id].disabled;
- if (!isDisabled && overlapsRectangle(activeLayout, layout.value)) {
- return id;
- }
- }
- return null;
- };
-
- const panGesture = Gesture.Pan()
- .maxPointers(1)
- .onBegin(event => {
- const { state, x, y } = event;
- debug && console.log('begin', { state, x, y });
- // Gesture is globally disabled
- if (disabled) {
- return;
- }
- // console.log("begin", { state, x, y });
- // Track current state for cancellation purposes
- 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 });
- // Check if an item was actually selected
- if (activeId !== null) {
- // 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];
- 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 {
- // 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';
- setActiveIdWithDelay();
- } else {
- draggableActiveId.value = activeId;
- draggableActiveLayout.value = applyOffset(activeLayout, {
- x: activeOffset.x.value,
- y: activeOffset.y.value,
- });
- draggableStates.value[activeId].value = 'dragging';
- }
-
- if (onBegin) {
- onBegin(event, { activeId, activeLayout });
- }
- }
- })
- .onUpdate(event => {
- // console.log(draggableStates.value);
- const { state, translationX, translationY } = event;
- debug && console.log('update', { state, translationX, translationY });
- // Track current state for cancellation purposes
- panGestureState.value = state;
- const { value: activeId } = draggableActiveId;
- const { value: pendingId } = draggablePendingId;
- const { value: options } = draggableOptions;
- const { value: layouts } = draggableLayouts;
- const { value: offsets } = draggableOffsets;
- if (activeId === null) {
- // Check if we are currently waiting for activation delay
- if (pendingId !== null) {
- const { activationTolerance } = options[pendingId];
- // Check if we've moved beyond the activation tolerance
- const distance = getDistance(translationX, translationY);
- if (distance > activationTolerance) {
- draggablePendingId.value = null;
- clearActiveIdTimeout();
- }
- }
- // Ignore item-free interactions
- return;
- }
- // Update our active offset to pan the active item
- const activeOffset = offsets[activeId];
- activeOffset.x.value = draggableInitialOffset.x.value + translationX;
- activeOffset.y.value = draggableInitialOffset.y.value + translationY;
- // Check potential droppable candidates
- const activeLayout = layouts[activeId].value;
- draggableActiveLayout.value = applyOffset(activeLayout, {
- x: activeOffset.x.value,
- y: activeOffset.y.value,
- });
- droppableActiveId.value = findDroppableLayoutId(draggableActiveLayout.value);
- if (onUpdate) {
- onUpdate(event, { activeId, activeLayout: draggableActiveLayout.value });
- }
- })
- .onFinalize(event => {
- const { state, velocityX, velocityY } = event;
- debug && console.log('finalize', { state, velocityX, velocityY });
- // Track current state for cancellation purposes
- panGestureState.value = state; // can be `FAILED` or `ENDED`
- const { value: activeId } = draggableActiveId;
- 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) {
- // Check if we were currently waiting for activation delay
- if (pendingId !== null) {
- draggablePendingId.value = null;
- clearActiveIdTimeout();
- }
- return;
- }
- // Reset interaction-related shared state for styling purposes
- draggableActiveId.value = null;
- if (onFinalize) {
- const activeLayout = layouts[activeId].value;
- const activeOffset = offsets[activeId];
- const updatedLayout = applyOffset(activeLayout, {
- x: activeOffset.x.value,
- y: activeOffset.y.value,
- });
- onFinalize(event, { activeId, activeLayout: updatedLayout });
- }
- // Callback
- if (state !== State.FAILED && onDragEnd) {
- const { value: dropActiveId } = droppableActiveId;
- onDragEnd({
- active: draggableOptions.value[activeId],
- over: dropActiveId !== null ? droppableOptions.value[dropActiveId] : null,
- });
- }
- // Reset droppable
- 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,
- [targetX, targetY],
- [
- { ...springConfig, velocity: velocityX / 4 },
- { ...springConfig, velocity: velocityY / 4 },
- ],
- () => {
- // Cancel if we are interacting again with this item
- if (panGestureState.value !== State.END && panGestureState.value !== State.FAILED && states[activeId].value !== 'acting') {
- return;
- }
- if (states[activeId]) {
- states[activeId].value = 'resting';
- }
- // for (const [id, offset] of Object.entries(offsets)) {
- // console.log({ [id]: [offset.x.value.toFixed(2), offset.y.value.toFixed(2)] });
- // }
- }
- );
- })
- .withTestId('DndProvider.pan');
-
- if (simultaneousHandlers) {
- panGesture.simultaneousWithExternalGesture(simultaneousHandlers);
- }
-
- if (waitFor) {
- panGesture.requireExternalGestureToFail(waitFor);
- }
-
- if (gestureRef) {
- panGesture.withRef(gestureRef);
- }
-
- // Duration in milliseconds of the LongPress gesture before Pan is allowed to activate.
- // If the finger is moved during that period, the gesture will fail.
- if (activationDelay > 0) {
- panGesture.activateAfterLongPress(activationDelay);
- }
-
- // Minimum distance the finger (or multiple fingers) need to travel before the gesture activates. Expressed in points.
- if (minDistance > 0) {
- panGesture.minDistance(minDistance);
- }
-
- return panGesture;
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [disabled]);
-
- return (
-
-
-
- {children}
-
-
-
- );
-});
diff --git a/src/components/drag-and-drop/components/Draggable.tsx b/src/components/drag-and-drop/components/Draggable.tsx
deleted file mode 100644
index 24e767646ad..00000000000
--- a/src/components/drag-and-drop/components/Draggable.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import React, { type FunctionComponent, type PropsWithChildren } from 'react';
-import { type ViewProps } from 'react-native';
-import Animated, { useAnimatedStyle, withTiming, type AnimatedProps } from 'react-native-reanimated';
-import { TIMING_CONFIGS } from '@/components/animations/animationConfigs';
-import { IS_IOS } from '@/env';
-import { useDraggable, type DraggableConstraints, type UseDroppableOptions } from '../hooks';
-import type { AnimatedStyleWorklet } from '../types';
-
-export type DraggableProps = AnimatedProps &
- UseDroppableOptions &
- Partial & {
- animatedStyleWorklet?: AnimatedStyleWorklet;
- activeOpacity?: number;
- activeScale?: number;
- dragDirection?: 'x' | 'y';
- };
-
-/**
- * Draggable is a React functional component that can be used to create elements that can be dragged within a Drag and Drop context.
- *
- * @component
- * @example
- *
- * Drag me!
- *
- *
- * @param {object} props - The properties that define the Draggable component.
- * @param {number} props.activationDelay - A number representing the duration, in milliseconds, that this draggable item needs to be held for before allowing a drag to start.
- * @param {number} props.activationTolerance - A number representing the distance, in pixels, of motion that is tolerated before the drag operation is aborted.
- * @param {number} props.activeOpacity - A number that defines the opacity of the Draggable component when it is active.
- * @param {number} props.activeScale - A number that defines the scale of the Draggable component when it is active.
- * @param {Function} props.animatedStyleWorklet - A worklet function that modifies the animated style of the Draggable component.
- * @param {object} props.data - An object that contains data associated with the Draggable component.
- * @param {boolean} props.disabled - A flag that indicates whether the Draggable component is disabled.
- * @param {string} props.dragDirection - Locks the drag direction to the x or y axis.
- * @param {string} props.id - A unique identifier for the Draggable component.
- * @param {object} props.style - An object that defines the style of the Draggable component.
- * @returns {React.Component} Returns a Draggable component that can be moved by the user within a Drag and Drop context.
- */
-export const Draggable: FunctionComponent> = ({
- activationDelay,
- activationTolerance,
- activeOpacity = 1,
- activeScale = 1.05,
- animatedStyleWorklet,
- children,
- data,
- disabled,
- dragDirection,
- id,
- style,
- ...otherProps
-}) => {
- const { setNodeRef, onLayout, onLayoutWorklet, offset, state } = useDraggable({
- id,
- data,
- disabled,
- activationDelay,
- activationTolerance,
- });
-
- const animatedStyle = useAnimatedStyle(() => {
- const isActive = state.value === 'dragging';
- const isActing = state.value === 'acting';
- // eslint-disable-next-line no-nested-ternary
- const zIndex = isActive ? 999 : isActing ? 998 : 1;
- const style = {
- opacity: withTiming(isActive ? activeOpacity : 1, TIMING_CONFIGS.tabPressConfig),
- zIndex,
- transform: [
- {
- translateX:
- // eslint-disable-next-line no-nested-ternary
- dragDirection !== 'y' ? (isActive ? offset.x.value : withTiming(offset.x.value, TIMING_CONFIGS.slowestFadeConfig)) : 0,
- },
- {
- translateY:
- // eslint-disable-next-line no-nested-ternary
- dragDirection !== 'x' ? (isActive ? offset.y.value : withTiming(offset.y.value, TIMING_CONFIGS.slowestFadeConfig)) : 0,
- },
- { scale: activeScale === undefined ? 1 : withTiming(isActive ? activeScale : 1, TIMING_CONFIGS.tabPressConfig) },
- ],
- };
- if (animatedStyleWorklet) {
- Object.assign(style, animatedStyleWorklet(style, { isActive, isActing, isDisabled: !!disabled }));
- }
- return style;
- }, [id, state, activeOpacity, activeScale]);
-
- return (
-
- {children}
-
- );
-};
diff --git a/src/components/drag-and-drop/components/DraggableFlatList.tsx b/src/components/drag-and-drop/components/DraggableFlatList.tsx
deleted file mode 100644
index 597d07b8df0..00000000000
--- a/src/components/drag-and-drop/components/DraggableFlatList.tsx
+++ /dev/null
@@ -1,236 +0,0 @@
-import React, {
- ComponentProps,
- ReactElement,
- // useCallback,
-} from 'react';
-import {
- CellRendererProps,
- // FlatListProps,
-} from 'react-native';
-import { FlatList } from 'react-native-gesture-handler';
-import Animated, {
- AnimatedProps,
- // runOnJS,
- useAnimatedReaction,
- useAnimatedRef,
- useAnimatedScrollHandler,
- // useSharedValue,
-} from 'react-native-reanimated';
-import { AnimatedFlatList } from '@/components/AnimatedComponents/AnimatedFlatList';
-import { useDndContext } from '../DndContext';
-import { useDraggableSort, UseDraggableStackOptions } from '../features';
-import type { UniqueIdentifier } from '../types';
-import { swapByItemCenterPoint } from '../utils';
-import { Draggable } from './Draggable';
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-type AnimatedFlatListProps = AnimatedProps>>;
-
-export type ViewableRange = {
- first: number | null;
- last: number | null;
-};
-
-export type DraggableFlatListProps = AnimatedFlatListProps &
- Pick & {
- debug?: boolean;
- gap?: number;
- horizontal?: boolean;
- initialOrder?: UniqueIdentifier[];
- };
-
-export const DraggableFlatList = ({
- data,
- // debug,
- gap = 0,
- horizontal = false,
- initialOrder,
- onOrderChange,
- onOrderUpdate,
- renderItem,
- shouldSwapWorklet = swapByItemCenterPoint,
- ...otherProps
-}: DraggableFlatListProps): ReactElement => {
- const { draggableActiveId, draggableContentOffset, draggableLayouts, draggableOffsets, draggableRestingOffsets } = useDndContext();
- const animatedFlatListRef = useAnimatedRef>();
-
- const {
- // draggablePlaceholderIndex,
- draggableSortOrder,
- } = useDraggableSort({
- horizontal,
- initialOrder,
- onOrderChange,
- onOrderUpdate,
- shouldSwapWorklet,
- });
-
- const direction = horizontal ? 'column' : 'row';
- const size = 1;
-
- // Track sort order changes and update the offsets
- useAnimatedReaction(
- () => draggableSortOrder.value,
- (nextOrder, prevOrder) => {
- // Ignore initial reaction
- if (prevOrder === null) {
- return;
- }
- const { value: activeId } = draggableActiveId;
- const { value: layouts } = draggableLayouts;
- const { value: offsets } = draggableOffsets;
- const { value: restingOffsets } = draggableRestingOffsets;
- if (!activeId) {
- return;
- }
-
- const activeLayout = layouts[activeId].value;
- const { width, height } = activeLayout;
- const restingOffset = restingOffsets[activeId];
-
- for (let nextIndex = 0; nextIndex < nextOrder.length; nextIndex++) {
- const itemId = nextOrder[nextIndex];
- const prevIndex = prevOrder.findIndex(id => id === itemId);
- // Skip items that haven't changed position
- if (nextIndex === prevIndex) {
- continue;
- }
-
- const prevRow = Math.floor(prevIndex / size);
- const prevCol = prevIndex % size;
- const nextRow = Math.floor(nextIndex / size);
- const nextCol = nextIndex % size;
- const moveCol = nextCol - prevCol;
- const moveRow = nextRow - prevRow;
-
- const offset = itemId === activeId ? restingOffset : offsets[itemId];
-
- if (!restingOffset || !offsets[itemId]) {
- continue;
- }
-
- switch (direction) {
- case 'row':
- offset.y.value += moveRow * (height + gap);
- break;
- case 'column':
- offset.x.value += moveCol * (width + gap);
- break;
- default:
- break;
- }
- }
- },
- []
- );
-
- const scrollHandler = useAnimatedScrollHandler(event => {
- draggableContentOffset.y.value = event.contentOffset.y;
- });
-
- /** ⚠️ TODO: Implement auto scrolling when dragging above or below the visible range */
- // const scrollToIndex = useCallback(
- // (index: number) => {
- // animatedFlatListRef.current?.scrollToIndex({
- // index,
- // viewPosition: 0,
- // animated: true,
- // });
- // },
- // [animatedFlatListRef]
- // );
-
- // const viewableRange = useSharedValue({
- // first: null,
- // last: null,
- // });
-
- // const onViewableItemsChanged = useCallback['onViewableItemsChanged']>>(
- // ({ viewableItems }) => {
- // viewableRange.value = {
- // first: viewableItems[0].index,
- // last: viewableItems[viewableItems.length - 1].index,
- // };
- // if (debug)
- // console.log(`First viewable item index: ${viewableItems[0].index}, last: ${viewableItems[viewableItems.length - 1].index}`);
- // },
- // [debug, viewableRange]
- // );
-
- // useAnimatedReaction(
- // () => draggablePlaceholderIndex.value,
- // (next, prev) => {
- // if (!Array.isArray(data)) {
- // return;
- // }
- // if (debug) console.log(`placeholderIndex: ${prev} -> ${next}}, last visible= ${viewableRange.value.last}`);
- // const {
- // value: { first, last },
- // } = viewableRange;
- // if (last !== null && next >= last && last < data.length - 1) {
- // if (next < data.length) {
- // runOnJS(scrollToIndex)(next + 1);
- // }
- // } else if (first !== null && first > 0 && next <= first) {
- // if (next > 0) {
- // runOnJS(scrollToIndex)(next - 1);
- // }
- // }
- // }
- // );
- /** END */
-
- /** 🛠️ DEBUGGING */
- // useAnimatedReaction(
- // () => {
- // const activeId = draggableActiveId.value;
- // return activeId ? draggableStates.value[activeId].value : 'resting';
- // },
- // (next, prev) => {
- // if (debug) console.log(`Active item state: ${prev} -> ${next}`);
- // if (debug) console.log(`translationY.value=${draggableContentOffset.y.value}`);
- // }
- // );
-
- // useAnimatedReaction(
- // () => draggableActiveId.value,
- // (next, prev) => {
- // if (debug) console.log(`activeId: ${prev} -> ${next}`);
- // }
- // );
- /** END */
-
- return (
- {
- return (
-
- );
- }}
- viewabilityConfig={{ itemVisiblePercentThreshold: 50 }}
- // eslint-disable-next-line react/jsx-props-no-spreading, @typescript-eslint/no-explicit-any
- {...(otherProps as any)}
- />
- );
-};
-
-export const DraggableFlatListCellRenderer = function DraggableFlatListCellRenderer(
- props: CellRendererProps
-) {
- const { item, children } = props;
- return (
-
- {children}
-
- );
-};
diff --git a/src/components/drag-and-drop/components/Droppable.tsx b/src/components/drag-and-drop/components/Droppable.tsx
deleted file mode 100644
index 154a2e01dbc..00000000000
--- a/src/components/drag-and-drop/components/Droppable.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import React, { type FunctionComponent, type PropsWithChildren } from 'react';
-import { type ViewProps } from 'react-native';
-import Animated, { useAnimatedStyle, withTiming, type AnimatedProps } from 'react-native-reanimated';
-import { TIMING_CONFIGS } from '@/components/animations/animationConfigs';
-import { IS_IOS } from '@/env';
-import { useDroppable, type UseDraggableOptions } from '../hooks';
-import type { AnimatedStyleWorklet } from '../types';
-
-export type DroppableProps = AnimatedProps &
- UseDraggableOptions & {
- animatedStyleWorklet?: AnimatedStyleWorklet;
- activeOpacity?: number;
- activeScale?: number;
- };
-
-/**
- * Droppable is a React functional component that can be used to create a drop target in a Drag and Drop context.
- *
- * @component
- * @example
- *
- * Drop here!
- *
- *
- * @param {object} props - The properties that define the Droppable component.
- * @param {string} props.id - A unique identifier for the Droppable component.
- * @param {boolean} props.disabled - A flag that indicates whether the Droppable component is disabled.
- * @param {object} props.data - An object that contains data associated with the Droppable component.
- * @param {object} props.style - An object that defines the style of the Droppable component.
- * @param {number} props.activeOpacity - A number that defines the opacity of the Droppable component when it is active.
- * @param {number} props.activeScale - A number that defines the scale of the Droppable component when it is active.
- * @param {Function} props.animatedStyleWorklet - A worklet function that modifies the animated style of the Droppable component.
- * @returns {React.Component} Returns a Droppable component that can serve as a drop target within a Drag and Drop context.
- */
-export const Droppable: FunctionComponent> = ({
- children,
- id,
- disabled,
- data,
- style,
- activeOpacity = 0.9,
- activeScale,
- animatedStyleWorklet,
- ...otherProps
-}) => {
- const { setNodeRef, onLayout, onLayoutWorklet, activeId } = useDroppable({
- id,
- disabled,
- data,
- });
-
- const animatedStyle = useAnimatedStyle(() => {
- const isActive = activeId.value === id;
- const style = {
- opacity: withTiming(isActive ? activeOpacity : 1, TIMING_CONFIGS.tabPressConfig),
- transform: [{ scale: activeScale === undefined ? 1 : withTiming(isActive ? activeScale : 1, TIMING_CONFIGS.tabPressConfig) }],
- };
- if (animatedStyleWorklet) {
- Object.assign(style, animatedStyleWorklet(style, { isActive, isDisabled: !!disabled }));
- }
- return style;
- }, [id, activeOpacity, activeScale]);
-
- return (
-
- {children}
-
- );
-};
diff --git a/src/components/drag-and-drop/components/index.ts b/src/components/drag-and-drop/components/index.ts
deleted file mode 100644
index 88f40fe1543..00000000000
--- a/src/components/drag-and-drop/components/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './Draggable';
-export * from './DraggableFlatList';
-export * from './Droppable';
diff --git a/src/components/drag-and-drop/features/index.ts b/src/components/drag-and-drop/features/index.ts
deleted file mode 100644
index b79f1a3e386..00000000000
--- a/src/components/drag-and-drop/features/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './sort';
diff --git a/src/components/drag-and-drop/features/sort/components/DraggableGrid.tsx b/src/components/drag-and-drop/features/sort/components/DraggableGrid.tsx
deleted file mode 100644
index c6f9282c805..00000000000
--- a/src/components/drag-and-drop/features/sort/components/DraggableGrid.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React, { Children, useMemo, type FunctionComponent, type PropsWithChildren } from 'react';
-import { StyleProp, View, ViewStyle, type FlexStyle, type ViewProps } from 'react-native';
-import type { UniqueIdentifier } from '../../../types';
-import { useDraggableGrid, type UseDraggableGridOptions } from '../hooks/useDraggableGrid';
-
-export type DraggableGridProps = Pick &
- Pick & {
- direction?: FlexStyle['flexDirection'];
- size: number;
- gap?: number;
- };
-
-export const DraggableGrid: FunctionComponent> = ({
- children,
- direction = 'row',
- gap = 0,
- onOrderChange,
- onOrderUpdate,
- shouldSwapWorklet,
- size,
- style: styleProp,
-}) => {
- const initialOrder = useMemo(
- () =>
- Children.map(children, child => {
- if (React.isValidElement(child)) {
- return child.props.id;
- }
- return null;
- })?.filter(Boolean) as UniqueIdentifier[],
- [children]
- );
-
- const style: StyleProp = useMemo(
- () => ({
- flexDirection: direction,
- gap,
- flexWrap: 'wrap',
- ...(styleProp as object),
- }),
- [gap, direction, styleProp]
- );
-
- useDraggableGrid({
- direction: style.flexDirection,
- gap: style.gap,
- initialOrder,
- onOrderChange,
- onOrderUpdate,
- shouldSwapWorklet,
- size,
- });
-
- return {children};
-};
diff --git a/src/components/drag-and-drop/features/sort/components/DraggableStack.tsx b/src/components/drag-and-drop/features/sort/components/DraggableStack.tsx
deleted file mode 100644
index 34dddffd0d2..00000000000
--- a/src/components/drag-and-drop/features/sort/components/DraggableStack.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import React, { Children, useMemo, type FunctionComponent, type PropsWithChildren } from 'react';
-import { View, type FlexStyle, type ViewProps } from 'react-native';
-import type { UniqueIdentifier } from '../../../types';
-import { useDraggableStack, type UseDraggableStackOptions } from '../hooks/useDraggableStack';
-
-export type DraggableStackProps = Pick &
- Pick & {
- direction?: FlexStyle['flexDirection'];
- gap?: number;
- };
-
-export const DraggableStack: FunctionComponent> = ({
- children,
- direction = 'row',
- gap = 0,
- onOrderChange,
- onOrderUpdate,
- shouldSwapWorklet,
- style: styleProp,
-}) => {
- const initialOrder = useMemo(
- () =>
- Children.map(children, child => {
- // console.log("in");
- if (React.isValidElement(child)) {
- return child.props.id;
- }
- return null;
- })?.filter(Boolean) as UniqueIdentifier[],
- [children]
- );
-
- const style = useMemo(
- () => ({
- flexDirection: direction,
- gap,
- ...(styleProp as object),
- }),
- [gap, direction, styleProp]
- );
-
- const horizontal = ['row', 'row-reverse'].includes(style.flexDirection);
-
- useDraggableStack({
- gap: style.gap,
- horizontal,
- initialOrder,
- onOrderChange,
- onOrderUpdate,
- shouldSwapWorklet,
- });
-
- return {children};
-};
diff --git a/src/components/drag-and-drop/features/sort/components/index.ts b/src/components/drag-and-drop/features/sort/components/index.ts
deleted file mode 100644
index ec52d14a680..00000000000
--- a/src/components/drag-and-drop/features/sort/components/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './DraggableGrid';
-export * from './DraggableStack';
diff --git a/src/components/drag-and-drop/features/sort/hooks/index.ts b/src/components/drag-and-drop/features/sort/hooks/index.ts
deleted file mode 100644
index 0ce999dc5ab..00000000000
--- a/src/components/drag-and-drop/features/sort/hooks/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './useDraggableGrid';
-export * from './useDraggableSort';
-export * from './useDraggableStack';
diff --git a/src/components/drag-and-drop/features/sort/hooks/useDraggableGrid.ts b/src/components/drag-and-drop/features/sort/hooks/useDraggableGrid.ts
deleted file mode 100644
index af298b207c4..00000000000
--- a/src/components/drag-and-drop/features/sort/hooks/useDraggableGrid.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import { type FlexStyle } from 'react-native';
-import { useAnimatedReaction } from 'react-native-reanimated';
-import { swapByItemCenterPoint } from '../../../utils';
-import { useDndContext } from './../../../DndContext';
-import { useDraggableSort, type UseDraggableSortOptions } from './useDraggableSort';
-
-export type UseDraggableGridOptions = Pick<
- UseDraggableSortOptions,
- 'initialOrder' | 'onOrderChange' | 'onOrderUpdate' | 'shouldSwapWorklet'
-> & {
- gap?: number;
- size: number;
- direction: FlexStyle['flexDirection'];
-};
-
-export const useDraggableGrid = ({
- initialOrder,
- onOrderChange,
- onOrderUpdate,
- gap = 0,
- size,
- direction = 'row',
- shouldSwapWorklet = swapByItemCenterPoint,
-}: UseDraggableGridOptions) => {
- const { draggableActiveId, draggableOffsets, draggableRestingOffsets, draggableLayouts } = useDndContext();
- const horizontal = ['row', 'row-reverse'].includes(direction);
-
- const { draggablePlaceholderIndex, draggableSortOrder } = useDraggableSort({
- horizontal,
- initialOrder,
- onOrderChange,
- onOrderUpdate,
- shouldSwapWorklet,
- });
-
- // Track sort order changes and update the offsets
- useAnimatedReaction(
- () => draggableSortOrder.value,
- (nextOrder, prevOrder) => {
- // Ignore initial reaction
- if (prevOrder === null) {
- return;
- }
- const { value: activeId } = draggableActiveId;
- const { value: layouts } = draggableLayouts;
- const { value: offsets } = draggableOffsets;
- const { value: restingOffsets } = draggableRestingOffsets;
- if (!activeId) {
- return;
- }
-
- const activeLayout = layouts[activeId].value;
- const { width, height } = activeLayout;
- const restingOffset = restingOffsets[activeId];
-
- for (let nextIndex = 0; nextIndex < nextOrder.length; nextIndex++) {
- const itemId = nextOrder[nextIndex];
- const prevIndex = prevOrder.findIndex(id => id === itemId);
- // Skip items that haven't changed position
- if (nextIndex === prevIndex) {
- continue;
- }
-
- const prevRow = Math.floor(prevIndex / size);
- const prevCol = prevIndex % size;
- const nextRow = Math.floor(nextIndex / size);
- const nextCol = nextIndex % size;
- const moveCol = nextCol - prevCol;
- const moveRow = nextRow - prevRow;
-
- const offset = itemId === activeId ? restingOffset : offsets[itemId];
-
- if (!restingOffset || !offsets[itemId]) {
- continue;
- }
-
- switch (direction) {
- case 'row':
- offset.x.value += moveCol * (width + gap);
- offset.y.value += moveRow * (height + gap);
- break;
- case 'row-reverse':
- offset.x.value += -1 * moveCol * (width + gap);
- offset.y.value += moveRow * (height + gap);
- break;
- case 'column':
- offset.y.value += moveCol * (width + gap);
- offset.x.value += moveRow * (height + gap);
- break;
- case 'column-reverse':
- offset.y.value += -1 * moveCol * (width + gap);
- offset.x.value += moveRow * (height + gap);
- break;
- default:
- break;
- }
- }
- },
- [direction, gap, size]
- );
-
- return { draggablePlaceholderIndex, draggableSortOrder };
-};
diff --git a/src/components/drag-and-drop/features/sort/hooks/useDraggableSort.ts b/src/components/drag-and-drop/features/sort/hooks/useDraggableSort.ts
deleted file mode 100644
index 7bdf88469f8..00000000000
--- a/src/components/drag-and-drop/features/sort/hooks/useDraggableSort.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-import { LayoutRectangle } from 'react-native';
-import { runOnJS, useAnimatedReaction, useSharedValue } from 'react-native-reanimated';
-import { useDndContext } from '../../../DndContext';
-import type { UniqueIdentifier } from '../../../types';
-import { applyOffset, arraysEqual, centerAxis, moveArrayIndex, overlapsAxis, type Rectangle } from '../../../utils';
-
-export type ShouldSwapWorklet = (activeLayout: Rectangle, itemLayout: Rectangle) => boolean;
-
-export type UseDraggableSortOptions = {
- initialOrder?: UniqueIdentifier[];
- horizontal?: boolean;
- onOrderChange?: (order: UniqueIdentifier[]) => void;
- onOrderUpdate?: (nextOrder: UniqueIdentifier[], prevOrder: UniqueIdentifier[]) => void;
- shouldSwapWorklet?: ShouldSwapWorklet;
-};
-
-export const useDraggableSort = ({
- horizontal = false,
- initialOrder = [],
- onOrderChange,
- onOrderUpdate,
- shouldSwapWorklet,
-}: UseDraggableSortOptions) => {
- const { draggableActiveId, draggableActiveLayout, draggableOffsets, draggableLayouts } = useDndContext();
-
- const draggablePlaceholderIndex = useSharedValue(-1);
- const draggableLastOrder = useSharedValue(initialOrder);
- const draggableSortOrder = useSharedValue(initialOrder);
-
- // Core placeholder index logic
- const findPlaceholderIndex = (activeLayout: LayoutRectangle): number => {
- 'worklet';
- const { value: activeId } = draggableActiveId;
- const { value: layouts } = draggableLayouts;
- const { value: offsets } = draggableOffsets;
- const { value: sortOrder } = draggableSortOrder;
- const activeIndex = sortOrder.findIndex(id => id === activeId);
- // const activeCenterPoint = centerPoint(activeLayout);
- // console.log(`activeLayout: ${JSON.stringify(activeLayout)}`);
- for (let itemIndex = 0; itemIndex < sortOrder.length; itemIndex++) {
- const itemId = sortOrder[itemIndex];
- if (itemId === activeId) {
- continue;
- }
- if (!layouts[itemId]) {
- console.warn(`Unexpected missing layout ${itemId} in layouts!`);
- continue;
- }
- const itemLayout = applyOffset(layouts[itemId].value, {
- x: offsets[itemId].x.value,
- y: offsets[itemId].y.value,
- });
-
- if (shouldSwapWorklet) {
- if (shouldSwapWorklet(activeLayout, itemLayout)) {
- // console.log(`Found placeholder index ${itemIndex} using custom shouldSwapWorklet!`);
- return itemIndex;
- }
- continue;
- }
-
- // Default to center axis
- const itemCenterAxis = centerAxis(itemLayout, horizontal);
- if (overlapsAxis(activeLayout, itemCenterAxis, horizontal)) {
- return itemIndex;
- }
- }
- // Fallback to current index
- // console.log(`Fallback to current index ${activeIndex}`);
- return activeIndex;
- };
-
- // Track active layout changes and update the placeholder index
- useAnimatedReaction(
- () => [draggableActiveId.value, draggableActiveLayout.value] as const,
- ([nextActiveId, nextActiveLayout], prev) => {
- // Ignore initial reaction
- if (prev === null) {
- return;
- }
- // const [_prevActiveId, _prevActiveLayout] = prev;
- // No active layout
- if (nextActiveLayout === null) {
- return;
- }
- // Reset the placeholder index when the active id changes
- if (nextActiveId === null) {
- draggablePlaceholderIndex.value = -1;
- return;
- }
- // const axis = direction === "row" ? "x" : "y";
- // const delta = prevActiveLayout !== null ? nextActiveLayout[axis] - prevActiveLayout[axis] : 0;
- draggablePlaceholderIndex.value = findPlaceholderIndex(nextActiveLayout);
- },
- []
- );
-
- // Track placeholder index changes and update the sort order
- useAnimatedReaction(
- () => [draggableActiveId.value, draggablePlaceholderIndex.value] as const,
- (next, prev) => {
- // Ignore initial reaction
- if (prev === null) {
- return;
- }
- const [, prevPlaceholderIndex] = prev;
- const [nextActiveId, nextPlaceholderIndex] = next;
- const { value: prevOrder } = draggableSortOrder;
- // if (nextPlaceholderIndex !== prevPlaceholderIndex) {
- // console.log(`Placeholder index changed from ${prevPlaceholderIndex} to ${nextPlaceholderIndex}`);
- // }
- if (prevPlaceholderIndex !== -1 && nextPlaceholderIndex === -1) {
- // Notify the parent component of the order change
- if (nextActiveId === null && onOrderChange) {
- if (!arraysEqual(prevOrder, draggableLastOrder.value)) {
- runOnJS(onOrderChange)(prevOrder);
- }
- draggableLastOrder.value = prevOrder;
- }
- }
- // Only update the sort order when the placeholder index changes between two valid values
- if (prevPlaceholderIndex === -1 || nextPlaceholderIndex === -1) {
- return;
- }
- // Finally update the sort order
- const nextOrder = moveArrayIndex(prevOrder, prevPlaceholderIndex, nextPlaceholderIndex);
- // Notify the parent component of the order update
- if (onOrderUpdate) {
- runOnJS(onOrderUpdate)(nextOrder, prevOrder);
- }
-
- draggableSortOrder.value = nextOrder;
- },
- [onOrderChange]
- );
-
- return { draggablePlaceholderIndex, draggableSortOrder };
-};
diff --git a/src/components/drag-and-drop/features/sort/hooks/useDraggableStack.ts b/src/components/drag-and-drop/features/sort/hooks/useDraggableStack.ts
deleted file mode 100644
index cb6626aab85..00000000000
--- a/src/components/drag-and-drop/features/sort/hooks/useDraggableStack.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { useMemo } from 'react';
-import { useAnimatedReaction } from 'react-native-reanimated';
-import { useDndContext } from '../../../DndContext';
-import { swapByItemHorizontalAxis, swapByItemVerticalAxis } from '../../../utils';
-import { useDraggableSort, type UseDraggableSortOptions } from './useDraggableSort';
-
-export type UseDraggableStackOptions = Pick<
- UseDraggableSortOptions,
- 'initialOrder' | 'onOrderChange' | 'onOrderUpdate' | 'shouldSwapWorklet'
-> & {
- gap?: number;
- horizontal?: boolean;
-};
-export const useDraggableStack = ({
- initialOrder,
- onOrderChange,
- onOrderUpdate,
- gap = 0,
- horizontal = false,
- shouldSwapWorklet,
-}: UseDraggableStackOptions) => {
- const { draggableActiveId, draggableOffsets, draggableRestingOffsets, draggableLayouts } = useDndContext();
- const axis = horizontal ? 'x' : 'y';
- const size = horizontal ? 'width' : 'height';
- const worklet = useMemo(
- () =>
- // eslint-disable-next-line no-nested-ternary
- shouldSwapWorklet ? shouldSwapWorklet : horizontal ? swapByItemHorizontalAxis : swapByItemVerticalAxis,
- [horizontal, shouldSwapWorklet]
- );
-
- const { draggablePlaceholderIndex, draggableSortOrder } = useDraggableSort({
- horizontal,
- initialOrder,
- onOrderChange,
- onOrderUpdate,
- shouldSwapWorklet: worklet,
- });
-
- // Track sort order changes and update the offsets
- useAnimatedReaction(
- () => draggableSortOrder.value,
- (nextOrder, prevOrder) => {
- // Ignore initial reaction
- if (prevOrder === null) {
- return;
- }
- const { value: activeId } = draggableActiveId;
- const { value: layouts } = draggableLayouts;
- const { value: offsets } = draggableOffsets;
- const { value: restingOffsets } = draggableRestingOffsets;
- if (!activeId) {
- return;
- }
-
- const activeLayout = layouts[activeId].value;
- const prevActiveIndex = prevOrder.findIndex(id => id === activeId);
- const nextActiveIndex = nextOrder.findIndex(id => id === activeId);
- const nextActiveOffset = { x: 0, y: 0 };
- const restingOffset = restingOffsets[activeId];
-
- for (let nextIndex = 0; nextIndex < nextOrder.length; nextIndex++) {
- const itemId = nextOrder[nextIndex];
- // Skip the active item
- if (itemId === activeId) {
- continue;
- }
- // @TODO grid x,y
-
- // Skip items that haven't changed position
- const prevIndex = prevOrder.findIndex(id => id === itemId);
- if (nextIndex === prevIndex) {
- continue;
- }
- // Calculate the directional offset
- const moveCol = nextIndex - prevIndex;
- // Apply the offset to the item from its resting position
- offsets[itemId][axis].value = restingOffsets[itemId][axis].value + moveCol * (activeLayout[size] + gap);
- // Reset resting offsets to new updated position
- restingOffsets[itemId][axis].value = offsets[itemId][axis].value;
-
- // Accummulate the directional offset for the active item
- if (nextIndex < nextActiveIndex && prevIndex > prevActiveIndex) {
- nextActiveOffset[axis] += layouts[itemId].value[size] + gap;
- } else if (nextIndex > nextActiveIndex && prevIndex < prevActiveIndex) {
- nextActiveOffset[axis] -= layouts[itemId].value[size] + gap;
- }
- }
- // Update the active item offset
- restingOffset[axis].value += nextActiveOffset[axis];
- },
- [horizontal]
- );
-
- return { draggablePlaceholderIndex, draggableSortOrder };
-};
diff --git a/src/components/drag-and-drop/features/sort/index.ts b/src/components/drag-and-drop/features/sort/index.ts
deleted file mode 100644
index f76fd6f166e..00000000000
--- a/src/components/drag-and-drop/features/sort/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './components';
-export * from './hooks';
diff --git a/src/components/drag-and-drop/hooks/index.ts b/src/components/drag-and-drop/hooks/index.ts
deleted file mode 100644
index 26087b68cc4..00000000000
--- a/src/components/drag-and-drop/hooks/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-export * from './useActiveDragReaction';
-export * from './useActiveDropReaction';
-export * from './useDraggable';
-export * from './useDraggableActiveId';
-export * from './useDraggableStyle';
-export * from './useDroppable';
-export * from './useDroppableStyle';
-export * from './useEvent';
-export * from './useLatestSharedValue';
-export * from './useLatestValue';
-export * from './useNodeRef';
-export * from './useSharedPoint';
-export * from './useSharedValuePair';
diff --git a/src/components/drag-and-drop/hooks/useActiveDragReaction.ts b/src/components/drag-and-drop/hooks/useActiveDragReaction.ts
deleted file mode 100644
index cd55cd87c8a..00000000000
--- a/src/components/drag-and-drop/hooks/useActiveDragReaction.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { State } from 'react-native-gesture-handler';
-import { useAnimatedReaction } from 'react-native-reanimated';
-import { useDndContext } from '../DndContext';
-import type { UniqueIdentifier } from '../types';
-
-export const useActiveDragReaction = (id: UniqueIdentifier, callback: (isActive: boolean) => void) => {
- const { draggableActiveId: activeId, panGestureState } = useDndContext();
- useAnimatedReaction(
- () => activeId.value === id && ([State.BEGAN, State.ACTIVE] as State[]).includes(panGestureState.value),
- (next, prev) => {
- if (next !== prev) {
- callback(next);
- }
- },
- []
- );
-};
diff --git a/src/components/drag-and-drop/hooks/useActiveDropReaction.ts b/src/components/drag-and-drop/hooks/useActiveDropReaction.ts
deleted file mode 100644
index c7be27157b9..00000000000
--- a/src/components/drag-and-drop/hooks/useActiveDropReaction.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useAnimatedReaction } from 'react-native-reanimated';
-import { useDndContext } from '../DndContext';
-import type { UniqueIdentifier } from '../types';
-
-export const useActiveDropReaction = (id: UniqueIdentifier, callback: (isActive: boolean) => void) => {
- const { droppableActiveId: activeId } = useDndContext();
- useAnimatedReaction(
- () => activeId.value === id,
- (next, prev) => {
- if (next !== prev) {
- callback(next);
- }
- },
- []
- );
-};
diff --git a/src/components/drag-and-drop/hooks/useDraggable.ts b/src/components/drag-and-drop/hooks/useDraggable.ts
deleted file mode 100644
index d27ee31da55..00000000000
--- a/src/components/drag-and-drop/hooks/useDraggable.ts
+++ /dev/null
@@ -1,177 +0,0 @@
-import { useCallback, useLayoutEffect } from 'react';
-import { LayoutRectangle, ViewProps } from 'react-native';
-import { runOnUI, useSharedValue } from 'react-native-reanimated';
-import { IS_IOS } from '@/env';
-import { useLayoutWorklet } from '@/hooks/reanimated/useLayoutWorklet';
-import { DraggableState, useDndContext } from '../DndContext';
-import { useLatestSharedValue, useNodeRef } from '../hooks';
-import { Data, NativeElement, UniqueIdentifier } from '../types';
-import { assert, isReanimatedSharedValue } from '../utils';
-import { useSharedPoint } from './useSharedPoint';
-
-export type DraggableConstraints = {
- activationDelay: number;
- activationTolerance: number;
-};
-
-export type UseDraggableOptions = Partial & {
- id: UniqueIdentifier;
- data?: Data;
- disabled?: boolean;
-};
-
-/**
- * useDraggable is a custom hook that provides functionality for making a component draggable within a drag and drop context.
- *
- * @function
- * @example
- * const { offset, setNodeRef, activeId, setNodeLayout, draggableState } = useDraggable({ id: 'draggable-1' });
- *
- * @param {object} options - The options that define the behavior of the draggable component.
- * @param {string} options.id - A unique identifier for the draggable component.
- * @param {object} [options.data={}] - Optional data associated with the draggable component.
- * @param {boolean} [options.disabled=false] - A flag that indicates whether the draggable component is disabled.
- * @param {number} [options.activationDelay=0] - A number representing the duration, in milliseconds, that this draggable item needs to be held for before allowing a drag to start.
- * @param {number} [options.activationTolerance=Infinity] - A number representing the distance, in points, of motion that is tolerated before the drag operation is aborted.
- *
- * @returns {object} Returns an object with properties and methods related to the draggable component.
- * @property {object} offset - An object representing the current offset of the draggable component.
- * @property {Function} setNodeRef - A function that can be used to set the ref of the draggable component.
- * @property {string} activeId - The unique identifier of the currently active draggable component.
- * @property {string} actingId - The unique identifier of the currently interacti draggable component.
- * @property {Function} setNodeLayout - A function that handles the layout event of the draggable component.
- * @property {object} draggableState - An object representing the current state of the draggable component.
- */
-export const useDraggable = ({
- id,
- data = {},
- disabled = false,
- activationDelay = 0,
- activationTolerance = Infinity,
-}: UseDraggableOptions) => {
- const {
- containerRef,
- draggableLayouts,
- draggableOffsets,
- draggableRestingOffsets,
- draggableOptions,
- draggableStates,
- draggableActiveId,
- draggablePendingId,
- panGestureState,
- } = useDndContext();
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const [node, setNodeRef] = useNodeRef();
- // const key = useUniqueId("Draggable");
- // eslint-disable-next-line react-hooks/rules-of-hooks
- const sharedData = isReanimatedSharedValue(data) ? data : useLatestSharedValue(data);
-
- const layout = useSharedValue({
- x: 0,
- y: 0,
- width: 0,
- height: 0,
- });
- const offset = useSharedPoint(0, 0);
- const restingOffset = useSharedPoint(0, 0);
- const state = useSharedValue('resting');
-
- // Register early to allow proper referencing in useDraggableStyle
- draggableStates.value[id] = state;
-
- useLayoutEffect(() => {
- const runLayoutEffect = () => {
- 'worklet';
- draggableLayouts.modify(prev => {
- const newValue = { ...prev, [id]: layout };
- return newValue;
- });
- draggableOffsets.modify(prev => {
- const newValue = { ...prev, [id]: offset };
- return newValue;
- });
- draggableRestingOffsets.modify(prev => {
- const newValue = { ...prev, [id]: restingOffset };
- return newValue;
- });
- draggableOptions.modify(prev => {
- const newValue = { ...prev, [id]: { id, data: sharedData, disabled, activationDelay, activationTolerance } };
- return newValue;
- });
- draggableStates.modify(prev => {
- const newValue = { ...prev, [id]: state };
- return newValue;
- });
- };
- runOnUI(runLayoutEffect)();
-
- return () => {
- const cleanupLayoutEffect = () => {
- 'worklet';
- draggableLayouts.modify(value => {
- delete value[id];
- return value;
- });
- draggableOffsets.modify(value => {
- delete value[id];
- return value;
- });
- draggableRestingOffsets.modify(value => {
- delete value[id];
- return value;
- });
- draggableOptions.modify(value => {
- delete value[id];
- return value;
- });
- draggableStates.modify(value => {
- delete value[id];
- return value;
- });
- };
- runOnUI(cleanupLayoutEffect)();
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [id]);
-
- // Standard onLayout event for Android — also required to trigger 'topLayout' event on iOS
- const onLayout: ViewProps['onLayout'] = useCallback(() => {
- if (IS_IOS) return;
-
- assert(containerRef.current);
- node.current?.measureLayout(containerRef.current, (x, y, width, height) => {
- layout.modify(value => {
- 'worklet';
- value.x = x;
- value.y = y;
- value.width = width;
- value.height = height;
- return value;
- });
- });
- }, [containerRef, node, layout]);
-
- // Worklet-based onLayout event for iOS
- const onLayoutWorklet = useLayoutWorklet(layoutInfo => {
- 'worklet';
-
- layout.modify(value => {
- value.x = layoutInfo.x;
- value.y = layoutInfo.y;
- value.width = layoutInfo.width;
- value.height = layoutInfo.height;
- return value;
- });
- });
-
- return {
- offset,
- state,
- setNodeRef,
- activeId: draggableActiveId,
- pendingId: draggablePendingId,
- onLayout,
- onLayoutWorklet,
- panGestureState,
- };
-};
diff --git a/src/components/drag-and-drop/hooks/useDraggableActiveId.ts b/src/components/drag-and-drop/hooks/useDraggableActiveId.ts
deleted file mode 100644
index e40b948ce5f..00000000000
--- a/src/components/drag-and-drop/hooks/useDraggableActiveId.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useState } from 'react';
-import { runOnJS, useAnimatedReaction } from 'react-native-reanimated';
-import { useDndContext } from '../DndContext';
-import type { UniqueIdentifier } from '../types';
-
-export const useDraggableActiveId = () => {
- const [activeId, setActiveId] = useState(null);
- const { draggableActiveId } = useDndContext();
- useAnimatedReaction(
- () => draggableActiveId.value,
- (next, prev) => {
- if (next !== prev) {
- runOnJS(setActiveId)(next);
- }
- },
- []
- );
- return activeId;
-};
diff --git a/src/components/drag-and-drop/hooks/useDraggableStyle.tsx b/src/components/drag-and-drop/hooks/useDraggableStyle.tsx
deleted file mode 100644
index 6d610c9aa15..00000000000
--- a/src/components/drag-and-drop/hooks/useDraggableStyle.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { useAnimatedStyle } from 'react-native-reanimated';
-import { useDndContext } from '..';
-import type { AnimatedStyle, UniqueIdentifier } from '../types';
-
-export type UseDraggableStyleCallback = (_: {
- isActive: boolean;
- isDisabled: boolean;
- isActing: boolean;
-}) => StyleT;
-
-export const useDraggableStyle = (
- id: UniqueIdentifier,
- callback: UseDraggableStyleCallback
-): StyleT => {
- const { draggableStates: states, draggableActiveId: activeId, draggableOptions: options } = useDndContext();
- const state = states.value[id];
- return useAnimatedStyle(() => {
- const isActive = activeId.value === id;
- const isActing = state?.value === 'acting';
- const isDisabled = !options.value[id]?.disabled;
- return callback({ isActive, isActing, isDisabled });
- }, [id, state]);
-};
diff --git a/src/components/drag-and-drop/hooks/useDroppable.ts b/src/components/drag-and-drop/hooks/useDroppable.ts
deleted file mode 100644
index 803104d4329..00000000000
--- a/src/components/drag-and-drop/hooks/useDroppable.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import { useCallback, useLayoutEffect } from 'react';
-import { ViewProps, type LayoutRectangle } from 'react-native';
-import { runOnUI, useAnimatedReaction, useSharedValue } from 'react-native-reanimated';
-import { IS_IOS } from '@/env';
-import { useLayoutWorklet } from '@/hooks/reanimated/useLayoutWorklet';
-import { useDndContext } from '../DndContext';
-import { useLatestSharedValue, useNodeRef } from '../hooks';
-import type { Data, NativeElement, UniqueIdentifier } from '../types';
-import { assert, isReanimatedSharedValue } from '../utils';
-
-export type UseDroppableOptions = { id: UniqueIdentifier; data?: Data; disabled?: boolean };
-
-/**
- * useDroppable is a custom hook that provides functionality for making a component droppable within a drag and drop context.
- *
- * @function
- * @example
- * const { setNodeRef, setNodeLayout, activeId, panGestureState } = useDroppable({ id: 'droppable-1' });
- *
- * @param {object} options - The options that define the behavior of the droppable component.
- * @param {string} options.id - A unique identifier for the droppable component.
- * @param {object} [options.data={}] - Optional data associated with the droppable component.
- * @param {boolean} [options.disabled=false] - A flag that indicates whether the droppable component is disabled.
- *
- * @returns {object} Returns an object with properties and methods related to the droppable component.
- * @property {Function} setNodeRef - A function that can be used to set the ref of the droppable component.
- * @property {Function} setNodeLayout - A function that handles the layout event of the droppable component.
- * @property {string} activeId - The unique identifier of the currently active droppable component.
- * @property {object} panGestureState - An object representing the current state of the draggable component within the context.
- */
-export const useDroppable = ({ id, data = {}, disabled = false }: UseDroppableOptions) => {
- const { droppableLayouts, droppableOptions, droppableActiveId, containerRef, panGestureState } = useDndContext();
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const [node, setNodeRef] = useNodeRef();
- // eslint-disable-next-line react-hooks/rules-of-hooks
- const sharedData = isReanimatedSharedValue(data) ? data : useLatestSharedValue(data);
-
- const layout = useSharedValue({
- x: 0,
- y: 0,
- width: 0,
- height: 0,
- });
-
- useAnimatedReaction(
- () => disabled,
- (next, prev) => {
- if (next !== prev) {
- droppableOptions.value[id].disabled = disabled;
- }
- },
- [disabled]
- );
-
- useLayoutEffect(() => {
- const runLayoutEffect = () => {
- 'worklet';
- // droppableLayouts.value[id] = layout;
- // droppableOptions.value[id] = { id, data: sharedData, disabled };
-
- droppableLayouts.modify(value => {
- const newValue = { ...value, [id]: layout };
- return newValue;
- });
- droppableOptions.modify(value => {
- const newValue = { ...value, [id]: { id, data: sharedData, disabled } };
- return newValue;
- });
- };
- runOnUI(runLayoutEffect)();
- return () => {
- const runLayoutEffect = () => {
- 'worklet';
- droppableLayouts.modify(value => {
- delete value[id];
- return value;
- });
- droppableOptions.modify(value => {
- delete value[id];
- return value;
- });
- };
- // if(node && node.key === key)
- runOnUI(runLayoutEffect)();
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- // Standard onLayout event for Android — also required to trigger 'topLayout' event on iOS
- const onLayout: ViewProps['onLayout'] = useCallback(() => {
- if (IS_IOS) return;
-
- assert(containerRef.current);
- node.current?.measureLayout(containerRef.current, (x, y, width, height) => {
- layout.modify(value => {
- 'worklet';
- value.x = x;
- value.y = y;
- value.width = width;
- value.height = height;
- return value;
- });
- });
- }, [containerRef, node, layout]);
-
- // Worklet-based onLayout event for iOS
- const onLayoutWorklet = useLayoutWorklet(layoutInfo => {
- 'worklet';
-
- layout.modify(value => {
- value.x = layoutInfo.x;
- value.y = layoutInfo.y;
- value.width = layoutInfo.width;
- value.height = layoutInfo.height;
- return value;
- });
- });
-
- return { setNodeRef, onLayout, onLayoutWorklet, activeId: droppableActiveId, panGestureState };
-};
diff --git a/src/components/drag-and-drop/hooks/useDroppableStyle.tsx b/src/components/drag-and-drop/hooks/useDroppableStyle.tsx
deleted file mode 100644
index 3d7c6c4c3fd..00000000000
--- a/src/components/drag-and-drop/hooks/useDroppableStyle.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { useAnimatedStyle } from 'react-native-reanimated';
-import { useDndContext } from '..';
-import type { AnimatedStyle, UniqueIdentifier } from '../types';
-
-export type UseDroppableStyleCallback = (_: { isActive: boolean; isDisabled: boolean }) => StyleT;
-
-export const useDroppableStyle = (
- id: UniqueIdentifier,
- callback: UseDroppableStyleCallback
-): StyleT => {
- const { droppableActiveId: activeId, droppableOptions: options } = useDndContext();
- return useAnimatedStyle(() => {
- const isActive = activeId.value === id;
- const isDisabled = !options.value[id]?.disabled;
- return callback({ isActive, isDisabled });
- }, []);
-};
diff --git a/src/components/drag-and-drop/hooks/useEvent.ts b/src/components/drag-and-drop/hooks/useEvent.ts
deleted file mode 100644
index 8e4615a6b72..00000000000
--- a/src/components/drag-and-drop/hooks/useEvent.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useCallback, useLayoutEffect, useRef } from 'react';
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-type EventHandler = (...args: any[]) => void;
-
-/**
- * Hook to define an event handler with a function identity that is always stable
- * {@link https://blog.logrocket.com/what-you-need-know-react-useevent-hook-rfc/}
- */
-export const useEvent = (handler: T | undefined) => {
- const handlerRef = useRef(handler);
-
- useLayoutEffect(() => {
- handlerRef.current = handler;
- });
-
- return useCallback((...args: unknown[]) => {
- return handlerRef.current?.(...args);
- }, []);
-};
diff --git a/src/components/drag-and-drop/hooks/useLatestSharedValue.ts b/src/components/drag-and-drop/hooks/useLatestSharedValue.ts
deleted file mode 100644
index 20d506fd149..00000000000
--- a/src/components/drag-and-drop/hooks/useLatestSharedValue.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useAnimatedReaction, useSharedValue } from 'react-native-reanimated';
-import type { DependencyList } from '../types';
-
-export function useLatestSharedValue(value: T, dependencies: DependencyList = [value]) {
- const sharedValue = useSharedValue(value);
-
- useAnimatedReaction(
- () => value,
- (next, prev) => {
- // Ignore initial reaction
- if (prev === null) {
- return;
- }
- sharedValue.value = next;
- },
- dependencies
- );
-
- return sharedValue;
-}
diff --git a/src/components/drag-and-drop/hooks/useLatestValue.ts b/src/components/drag-and-drop/hooks/useLatestValue.ts
deleted file mode 100644
index 1d9f8869117..00000000000
--- a/src/components/drag-and-drop/hooks/useLatestValue.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { DependencyList, useLayoutEffect, useRef } from 'react';
-
-export function useLatestValue(value: T, dependencies: DependencyList = [value]) {
- const valueRef = useRef(value);
-
- useLayoutEffect(() => {
- if (valueRef.current !== value) {
- valueRef.current = value;
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, dependencies);
-
- return valueRef;
-}
diff --git a/src/components/drag-and-drop/hooks/useNodeRef.ts b/src/components/drag-and-drop/hooks/useNodeRef.ts
deleted file mode 100644
index 07bdf66dd5d..00000000000
--- a/src/components/drag-and-drop/hooks/useNodeRef.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { useCallback, useRef } from 'react';
-import { useEvent } from './useEvent';
-
-type NodeChangeHandler = (nextElement: T | null, prevElement: T | null) => void;
-
-/**
- * Hook to receive a stable ref setter with an optional onChange handler
- */
-export const useNodeRef = (onChange?: NodeChangeHandler) => {
- const onChangeHandler = useEvent(onChange);
- const nodeRef = useRef(null);
- const setNodeRef = useCallback(
- (element: U | null) => {
- if (element !== nodeRef.current) {
- onChangeHandler?.(element, nodeRef.current);
- }
- nodeRef.current = element as T;
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- []
- );
-
- return [nodeRef, setNodeRef] as const;
-};
diff --git a/src/components/drag-and-drop/hooks/useSharedPoint.ts b/src/components/drag-and-drop/hooks/useSharedPoint.ts
deleted file mode 100644
index 0335c41ad61..00000000000
--- a/src/components/drag-and-drop/hooks/useSharedPoint.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { useSharedValue, type SharedValue } from 'react-native-reanimated';
-import type { Point } from '../utils';
-
-export type SharedPoint = Point>;
-
-export const useSharedPoint = (x: number, y: number): SharedPoint => {
- return {
- x: useSharedValue(x),
- y: useSharedValue(y),
- };
-};
diff --git a/src/components/drag-and-drop/hooks/useSharedValuePair.ts b/src/components/drag-and-drop/hooks/useSharedValuePair.ts
deleted file mode 100644
index a921b8059fd..00000000000
--- a/src/components/drag-and-drop/hooks/useSharedValuePair.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { useSharedValue, type SharedValue } from 'react-native-reanimated';
-
-export type SharedValues> = {
- [K in keyof T]: SharedValue;
-};
-
-export const useSharedValuePair = (x: number, y: number) => {
- return [useSharedValue(x), useSharedValue(y)];
-};
diff --git a/src/components/drag-and-drop/index.ts b/src/components/drag-and-drop/index.ts
deleted file mode 100644
index 3ed5a7f1cfa..00000000000
--- a/src/components/drag-and-drop/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export * from './DndContext';
-export * from './DndProvider';
-export * from './components';
-export * from './features';
-export * from './hooks';
-export * from './types';
-export * from './utils';
diff --git a/src/components/drag-and-drop/types/common.ts b/src/components/drag-and-drop/types/common.ts
deleted file mode 100644
index 455f5bbc0d8..00000000000
--- a/src/components/drag-and-drop/types/common.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { HostComponent, ViewProps, ViewStyle } from 'react-native';
-import type { SharedValue, useAnimatedStyle } from 'react-native-reanimated';
-
-export type UniqueIdentifier = string | number;
-export type ObjectWithId = { id: UniqueIdentifier; [s: string]: unknown };
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export type AnyData = Record;
-export type Data = T | SharedValue;
-export type SharedData = SharedValue;
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export type NativeElement = InstanceType>;
-
-export type AnimatedStyle = ReturnType;
-export type AnimatedViewStyle = ReturnType>;
-export type AnimatedStyleWorklet = (
- style: Readonly,
- options: { isActive: boolean; isDisabled: boolean; isActing?: boolean }
-) => T;
diff --git a/src/components/drag-and-drop/types/index.ts b/src/components/drag-and-drop/types/index.ts
deleted file mode 100644
index 251629bf35e..00000000000
--- a/src/components/drag-and-drop/types/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './common';
-export * from './reanimated';
diff --git a/src/components/drag-and-drop/types/reanimated.ts b/src/components/drag-and-drop/types/reanimated.ts
deleted file mode 100644
index 6c14f0cba3d..00000000000
--- a/src/components/drag-and-drop/types/reanimated.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { useAnimatedReaction } from 'react-native-reanimated';
-
-export type DependencyList = Parameters[2];
diff --git a/src/components/drag-and-drop/utils/array.ts b/src/components/drag-and-drop/utils/array.ts
deleted file mode 100644
index de114bd58c8..00000000000
--- a/src/components/drag-and-drop/utils/array.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export const arraysEqual = (a: unknown[], b: unknown[]): boolean => {
- 'worklet';
- if (a === b) {
- return true;
- }
- if (a.length !== b.length) {
- return false;
- }
-
- for (let i = 0; i < a.length; i++) {
- if (a[i] !== b[i]) {
- return false;
- }
- }
-
- return true;
-};
diff --git a/src/components/drag-and-drop/utils/assert.ts b/src/components/drag-and-drop/utils/assert.ts
deleted file mode 100644
index 9f290f53f05..00000000000
--- a/src/components/drag-and-drop/utils/assert.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-class AssertionError extends Error {
- name = 'AssertionError';
- code = 'ERR_ASSERTION';
- constructor(
- // eslint-disable-next-line default-param-last
- message = '',
- public actual: unknown,
- public expected: unknown = 'true',
- public operator = '=='
- ) {
- super(message || `${actual} ${operator} ${expected}`);
- Object.setPrototypeOf(this, new.target.prototype);
- }
-}
-
-export const assert: (value: unknown, message?: string) => asserts value = (value, message) => {
- if (value === undefined || value === null) {
- throw new AssertionError(message, value);
- }
-};
diff --git a/src/components/drag-and-drop/utils/geometry.ts b/src/components/drag-and-drop/utils/geometry.ts
deleted file mode 100644
index 6f017685a10..00000000000
--- a/src/components/drag-and-drop/utils/geometry.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-export type Point = {
- x: T;
- y: T;
-};
-
-export type Offset = {
- x: number;
- y: number;
-};
-
-export type Rectangle = {
- x: number;
- y: number;
- width: number;
- height: number;
-};
-
-/**
- * @summary Split a `Rectangle` in two
- * @worklet
- */
-export const splitLayout = (layout: Rectangle, axis: 'x' | 'y') => {
- 'worklet';
- const { x, y, width, height } = layout;
- if (axis === 'x') {
- return [
- { x, y, width: width / 2, height },
- { x: x + width / 2, y, width: width / 2, height },
- ];
- }
- return [
- { x, y, width, height: height / 2 },
- { x, y: y + height / 2, width, height: height / 2 },
- ];
-};
-
-/**
- * @summary Checks if a `Point` is included inside a `Rectangle`
- * @worklet
- */
-export const includesPoint = (layout: Rectangle, { x, y }: Point, strict?: boolean) => {
- 'worklet';
- if (strict) {
- return layout.x < x && x < layout.x + layout.width && layout.y < y && y < layout.y + layout.height;
- }
- return layout.x <= x && x <= layout.x + layout.width && layout.y <= y && y <= layout.y + layout.height;
-};
-
-/**
- * @summary Checks if a `Rectangle` overlaps with another `Rectangle`
- * @worklet
- */
-export const overlapsRectangle = (layout: Rectangle, other: Rectangle) => {
- 'worklet';
- return (
- layout.x < other.x + other.width &&
- layout.x + layout.width > other.x &&
- layout.y < other.y + other.height &&
- layout.y + layout.height > other.y
- );
-};
-
-/**
- * @summary Checks if a `Rectange` overlaps with another `Rectangle` with a margin
- * @worklet
- */
-export const overlapsRectangleBy = (layout: Rectangle, other: Rectangle, by: number) => {
- 'worklet';
- return (
- layout.x < other.x + other.width - by &&
- layout.x + layout.width > other.x + by &&
- layout.y < other.y + other.height - by &&
- layout.y + layout.height > other.y + by
- );
-};
-
-/**
- * @summary Apply an offset to a layout
- * @worklet
- */
-export const applyOffset = (layout: Rectangle, { x, y }: Offset): Rectangle => {
- 'worklet';
- return {
- width: layout.width,
- height: layout.height,
- x: layout.x + x,
- y: layout.y + y,
- };
-};
-
-/**
- * @summary Compute a center point
- * @worklet
- */
-export const centerPoint = (layout: Rectangle): Point => {
- 'worklet';
- return {
- x: layout.x + layout.width / 2,
- y: layout.y + layout.height / 2,
- };
-};
-
-/**
- * @summary Compute a center axis
- * @worklet
- */
-export const centerAxis = (layout: Rectangle, horizontal: boolean): number => {
- 'worklet';
- return horizontal ? layout.x + layout.width / 2 : layout.y + layout.height / 2;
-};
-
-/**
- * @summary Checks if a `Rectangle` overlaps with an axis
- * @worklet
- */
-export const overlapsAxis = (layout: Rectangle, axis: number, horizontal: boolean) => {
- 'worklet';
- return horizontal ? layout.x < axis && layout.x + layout.width > axis : layout.y < axis && layout.y + layout.height > axis;
-};
-
-export const getDistance = (x: number, y: number): number => {
- 'worklet';
- return Math.sqrt(Math.abs(x) ** 2 + Math.abs(y) ** 2);
-};
diff --git a/src/components/drag-and-drop/utils/index.ts b/src/components/drag-and-drop/utils/index.ts
deleted file mode 100644
index 513e2860213..00000000000
--- a/src/components/drag-and-drop/utils/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export * from './array';
-export * from './assert';
-export * from './geometry';
-export * from './random';
-export * from './reanimated';
-export * from './swap';
diff --git a/src/components/drag-and-drop/utils/random.ts b/src/components/drag-and-drop/utils/random.ts
deleted file mode 100644
index 54baa9f23b4..00000000000
--- a/src/components/drag-and-drop/utils/random.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * Returns a random integer between min (inclusive) and max (inclusive).
- * The value is no lower than min (or the next integer greater than min
- * if min isn't an integer) and no greater than max (or the next integer
- * lower than max if max isn't an integer).
- * Using Math.round() will give you a non-uniform distribution!
- */
-export const getRandomInt = (min: number, max: number): number => {
- 'worklet';
- // eslint-disable-next-line no-param-reassign
- min = Math.ceil(min);
- // eslint-disable-next-line no-param-reassign
- max = Math.floor(max);
- return Math.floor(Math.random() * (max - min + 1)) + min;
-};
diff --git a/src/components/drag-and-drop/utils/reanimated.ts b/src/components/drag-and-drop/utils/reanimated.ts
deleted file mode 100644
index 6c1b59bfce9..00000000000
--- a/src/components/drag-and-drop/utils/reanimated.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import { LayoutRectangle } from 'react-native';
-import { SharedValue, withSpring, type AnimatableValue, type AnimationCallback, type WithSpringConfig } from 'react-native-reanimated';
-import type { SharedPoint } from '../hooks';
-import type { AnyData } from '../types';
-
-export const DND_DEFAULT_SPRING_CONFIG: WithSpringConfig = {
- damping: 10, // Defines how the spring’s motion should be damped due to the forces of friction. Default 10.
- mass: 1, // The mass of the object attached to the end of the spring. Default 1.
- stiffness: 100, // The spring stiffness coefficient. Default 100.
- overshootClamping: false, // Indicates whether the spring should be clamped and not bounce. Default false.
- restSpeedThreshold: 0.001, // The speed at which the spring should be considered at rest in pixels per second. Default 0.001.
- restDisplacementThreshold: 0.2, // The threshold of displacement from rest below which the spring should be considered at rest. Default 0.001.
-};
-export const DND_FAST_SPRING_CONFIG: WithSpringConfig = {
- damping: 20, // Defines how the spring’s motion should be damped due to the forces of friction. Default 10.
- mass: 0.5, // The mass of the object attached to the end of the spring. Default 1.
- stiffness: 100, // The spring stiffness coefficient. Default 100.
- overshootClamping: false, // Indicates whether the spring should be clamped and not bounce. Default false.
- restSpeedThreshold: 0.2, // The speed at which the spring should be considered at rest in pixels per second. Default 0.001.
- restDisplacementThreshold: 0.2, // The threshold of displacement from rest below which the spring should be considered at rest. Default 0.001.
-};
-export const DND_DEFAULT_SPRING_CONFIG_3: WithSpringConfig = {
- damping: 20, // Defines how the spring’s motion should be damped due to the forces of friction. Default 10.
- mass: 0.5, // The mass of the object attached to the end of the spring. Default 1.
- stiffness: 100, // The spring stiffness coefficient. Default 100.
- overshootClamping: false, // Indicates whether the spring should be clamped and not bounce. Default false.
- restSpeedThreshold: 0.01, // The speed at which the spring should be considered at rest in pixels per second. Default 0.001.
- restDisplacementThreshold: 0.2, // The threshold of displacement from rest below which the spring should be considered at rest. Default 0.001.
-};
-export const DND_SLOW_SPRING_CONFIG: WithSpringConfig = {
- damping: 20, // Defines how the spring’s motion should be damped due to the forces of friction. Default 10.
- mass: 1, // The mass of the object attached to the end of the spring. Default 1.
- stiffness: 10, // The spring stiffness coefficient. Default 100.
- overshootClamping: false, // Indicates whether the spring should be clamped and not bounce. Default false.
- restSpeedThreshold: 0.01, // The speed at which the spring should be considered at rest in pixels per second. Default 0.001.
- restDisplacementThreshold: 0.2, // The threshold of displacement from rest below which the spring should be considered at rest. Default 0.001.
-};
-
-/**
- * @summary Waits for n-callbacks
- * @worklet
- */
-export const waitForAll = (callback: (...args: T) => void, count = 2) => {
- 'worklet';
- const status = new Array(count).fill(false);
- const result = new Array(count).fill(undefined);
- return status.map((_v, index) => {
- return (...args: unknown[]) => {
- status[index] = true;
- result[index] = args;
- if (status.every(Boolean)) {
- callback(...(result as T));
- }
- };
- });
-};
-
-type AnimationCallbackParams = Parameters;
-
-export type AnimationPointCallback = (
- finished: [boolean | undefined, boolean | undefined],
- current: [AnimatableValue | undefined, AnimatableValue | undefined]
-) => void;
-
-// eslint-disable-next-line default-param-last
-export const withDefaultSpring: typeof withSpring = (toValue, userConfig: WithSpringConfig = {}, callback) => {
- 'worklet';
- // eslint-disable-next-line prefer-object-spread
- const config: WithSpringConfig = Object.assign({}, DND_SLOW_SPRING_CONFIG, userConfig);
- return withSpring(toValue, config, callback);
-};
-
-/**
- * @summary Easily animate a `SharePoint`
- * @worklet
- */
-export const animatePointWithSpring = (
- point: SharedPoint,
- [toValueX, toValueY]: [number, number],
- // eslint-disable-next-line default-param-last
- [configX, configY]: [WithSpringConfig | undefined, WithSpringConfig | undefined] = [undefined, undefined],
- callback?: AnimationPointCallback
-) => {
- 'worklet';
- const [waitForX, waitForY] = waitForAll<[AnimationCallbackParams, AnimationCallbackParams]>(
- ([finishedX, currentX], [finishedY, currentY]) => {
- if (!callback) {
- return;
- }
- callback([finishedX, finishedY], [currentX, currentY]);
- }
- );
- point.x.value = withSpring(toValueX, configX, waitForX);
- point.y.value = withSpring(toValueY, configY, waitForY);
-};
-
-export const moveArrayIndex = (input: T[], from: number, to: number) => {
- 'worklet';
- const output = input.slice();
- output.splice(to, 0, output.splice(from, 1)[0]);
- return output;
-};
-
-export const stringifySharedPoint = ({ x, y }: SharedPoint) => {
- 'worklet';
- return `{"x": ${Math.floor(x.value)}, "y": ${Math.floor(y.value)}}`;
-};
-
-export const stringifyLayout = ({ x, y, width, height }: LayoutRectangle) => {
- 'worklet';
- return `{"x": ${Math.floor(x)}, "y": ${Math.floor(y)}, "width": ${Math.floor(width)}, "height": ${Math.floor(height)}}`;
-};
-
-export const floorLayout = ({ x, y, width, height }: LayoutRectangle) => {
- 'worklet';
- return {
- x: Math.floor(x),
- y: Math.floor(y),
- width: Math.floor(width),
- height: Math.floor(height),
- };
-};
-
-/**
- * @summary Checks if a value is a `Reanimated` shared value
- * @param {object} value - The value to check
- * @returns {boolean} Whether the value is a `Reanimated` shared value
- */
-export const isReanimatedSharedValue = (value: unknown): value is SharedValue =>
- typeof value === 'object' && (value as { _isReanimatedSharedValue: boolean })?._isReanimatedSharedValue;
diff --git a/src/components/drag-and-drop/utils/swap.ts b/src/components/drag-and-drop/utils/swap.ts
deleted file mode 100644
index 5290df09dbb..00000000000
--- a/src/components/drag-and-drop/utils/swap.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { centerAxis, centerPoint, includesPoint, overlapsAxis, type Rectangle } from './geometry';
-
-export const swapByItemCenterPoint = (activeLayout: Rectangle, itemLayout: Rectangle) => {
- 'worklet';
- const itemCenterPoint = centerPoint(itemLayout);
- return includesPoint(activeLayout, itemCenterPoint);
-};
-
-export const swapByItemAxis = (activeLayout: Rectangle, itemLayout: Rectangle, horizontal: boolean) => {
- 'worklet';
- const itemCenterAxis = centerAxis(itemLayout, horizontal);
- return overlapsAxis(activeLayout, itemCenterAxis, horizontal);
-};
-
-export const swapByItemHorizontalAxis = (activeLayout: Rectangle, itemLayout: Rectangle) => {
- 'worklet';
- const itemCenterAxis = centerAxis(itemLayout, true);
- return overlapsAxis(activeLayout, itemCenterAxis, true);
-};
-
-export const swapByItemVerticalAxis = (activeLayout: Rectangle, itemLayout: Rectangle) => {
- 'worklet';
- const itemCenterAxis = centerAxis(itemLayout, false);
- return overlapsAxis(activeLayout, itemCenterAxis, false);
-};
diff --git a/src/components/easing-gradient/EasingGradient.tsx b/src/components/easing-gradient/EasingGradient.tsx
deleted file mode 100644
index 0cddb0157b9..00000000000
--- a/src/components/easing-gradient/EasingGradient.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import React, { memo } from 'react';
-import { ViewProps } from 'react-native';
-import LinearGradient from 'react-native-linear-gradient';
-import { useEasingGradient, UseEasingGradientParams } from '@/hooks/useEasingGradient';
-
-interface EasingGradientProps extends UseEasingGradientParams, ViewProps {}
-
-/**
- * ### EasingGradient
- *
- * Renders a linear gradient with easing applied to the color transitions.
- *
- * **Required:**
- * @param endColor The color at the end of the gradient.
- * @param startColor The color at the start of the gradient.
- *
- * **Optional:**
- * @param easing The easing function to apply to the gradient.
- * @param endOpacity The opacity at the end of the gradient.
- * @param endPosition The end position of the gradient ('top', 'bottom', 'left', 'right').
- * @param startOpacity The opacity at the start of the gradient.
- * @param startPosition The start position of the gradient ('top', 'bottom', 'left', 'right'). Defaults to 'top'.
- * @param steps The number of color steps in the gradient. Defaults to 16.
- * @param props Additional ViewProps to apply to the LinearGradient component.
- *
- * @returns A LinearGradient component with the specified easing and color properties.
- *
- * @example
- * ```tsx
- *
- * ```
- */
-export const EasingGradient = memo(function EasingGradient({
- easing,
- endColor,
- endOpacity,
- endPosition,
- startColor,
- startOpacity,
- startPosition = 'top',
- steps = 16,
- ...props
-}: EasingGradientProps) {
- const { colors, end, locations, start } = useEasingGradient({
- easing,
- endColor,
- endOpacity,
- endPosition,
- startColor,
- startOpacity,
- startPosition,
- steps,
- });
-
- // eslint-disable-next-line react/jsx-props-no-spreading
- return ;
-});
diff --git a/src/hooks/reanimated/useAnimatedTimeout.ts b/src/hooks/reanimated/useAnimatedTimeout.ts
index c075662a09b..04704dedc7c 100644
--- a/src/hooks/reanimated/useAnimatedTimeout.ts
+++ b/src/hooks/reanimated/useAnimatedTimeout.ts
@@ -20,7 +20,6 @@ interface TimeoutConfig {
* - `onTimeoutWorklet` - The worklet function to be executed when the timeout completes.
*
* @returns An object containing:
- * - `clearTimeout` - A function to clear the timeout.
* - `start` - A function to initiate the timeout.
*
* @example
@@ -39,11 +38,11 @@ interface TimeoutConfig {
export function useAnimatedTimeout(config: TimeoutConfig) {
const { autoStart, delayMs, onTimeoutWorklet } = config;
- const { start, stop: clearTimeout } = useAnimatedTime({
+ const { start } = useAnimatedTime({
autoStart,
durationMs: delayMs,
onEndWorklet: onTimeoutWorklet,
});
- return { clearTimeout, start };
+ return { start };
}
diff --git a/src/hooks/reanimated/useLayoutWorklet.ts b/src/hooks/reanimated/useLayoutWorklet.ts
deleted file mode 100644
index 84d2efb81be..00000000000
--- a/src/hooks/reanimated/useLayoutWorklet.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { useEvent } from 'react-native-reanimated';
-import { WorkletFunction } from 'react-native-reanimated/lib/typescript/commonTypes';
-import { IS_IOS } from '@/env';
-
-interface Layout {
- x: number;
- y: number;
- width: number;
- height: number;
-}
-
-const SHOULD_REBUILD = IS_IOS ? undefined : false;
-
-/**
- * ### `📐 useLayoutWorklet 📐`
- * @warning This hook is experimental and currently only works on iOS.
- *
- * Allows reacting to `onLayout` events directly from the UI thread.
- *
- * Meant to be used with ``.
- *
- * @param worklet - A worklet function to be called when the layout changes.
- * The worklet receives a {@link Layout} object containing the new layout information.
- *
- * @returns A worklet function that can be passed to an `Animated.View` to handle layout changes.
- *
- * @example
- * ```tsx
- * const onLayoutWorklet = useLayoutWorklet((layout) => {
- * 'worklet';
- * console.log('New layout:', layout);
- * });
- *
- * return (
- * {
- * if (IS_IOS) return;
- * handleAndroidLayout(layout);
- * }}
- * // @ts-expect-error The name of this prop does not matter but the
- * // function must be passed to a prop
- * onLayoutWorklet={IS_IOS ? onLayoutWorklet : undefined}
- * >
- * Measure me
- *
- * );
- * ```
- */
-
-// @ts-expect-error This overload is required by the Reanimated API
-export function useLayoutWorklet(worklet: (layout: Layout) => void);
-export function useLayoutWorklet(worklet: WorkletFunction) {
- return useEvent(
- (event: { layout: Layout }) => {
- 'worklet';
- worklet(event.layout);
- },
- ['topLayout'],
- SHOULD_REBUILD
- );
-}
diff --git a/src/hooks/useEasingGradient.ts b/src/hooks/useEasingGradient.ts
deleted file mode 100644
index a70a6a95b6d..00000000000
--- a/src/hooks/useEasingGradient.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import chroma from 'chroma-js';
-import { useMemo } from 'react';
-import { Easing, EasingFunction } from 'react-native-reanimated';
-
-type PositionObject = { x: number; y: number };
-type Position = 'bottom' | 'left' | 'right' | 'top' | PositionObject;
-
-export interface UseEasingGradientParams {
- easing?: EasingFunction;
- endColor: string;
- endOpacity?: number;
- endPosition?: Position;
- startColor: string;
- startOpacity?: number;
- startPosition?: Position;
- steps?: number;
-}
-
-interface GradientOutput {
- colors: string[];
- end: PositionObject;
- locations: number[];
- start: PositionObject;
-}
-
-const getPositionCoordinates = (position: Position): PositionObject => {
- if (typeof position === 'object') {
- return position;
- }
- switch (position) {
- case 'bottom':
- return { x: 0.5, y: 1 };
- case 'left':
- return { x: 0, y: 0.5 };
- case 'right':
- return { x: 1, y: 0.5 };
- case 'top':
- default:
- return { x: 0.5, y: 0 };
- }
-};
-
-export const useEasingGradient = ({
- easing = Easing.inOut(Easing.sin),
- endColor,
- endOpacity = 1,
- endPosition = 'bottom',
- startColor,
- startOpacity = 0,
- startPosition = 'top',
- steps = 16,
-}: UseEasingGradientParams): GradientOutput => {
- return useMemo(() => {
- const colors: string[] = [];
- const locations: number[] = [];
-
- const startColorWithOpacity = chroma(startColor).alpha(startOpacity);
- const endColorWithOpacity = chroma(endColor).alpha(endOpacity);
-
- for (let i = 0; i <= steps; i++) {
- const t = i / steps;
- const easedT = easing(t);
-
- const interpolatedColor = chroma.mix(startColorWithOpacity, endColorWithOpacity, easedT, 'rgb');
-
- colors.push(interpolatedColor.css());
- locations.push(t);
- }
-
- return {
- colors,
- end: getPositionCoordinates(endPosition),
- locations,
- start: getPositionCoordinates(startPosition),
- };
- }, [easing, endColor, endOpacity, endPosition, startColor, startOpacity, startPosition, steps]);
-};
diff --git a/src/model/migrations.ts b/src/model/migrations.ts
index c79715a0db9..4eb72cf0de7 100644
--- a/src/model/migrations.ts
+++ b/src/model/migrations.ts
@@ -37,8 +37,6 @@ import { queryClient } from '@/react-query';
import { favoritesQueryKey } from '@/resources/favorites';
import { EthereumAddress, RainbowToken } from '@/entities';
import { getUniqueId } from '@/utils/ethereumUtils';
-import { standardizeUrl, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore';
-import { useLegacyFavoriteDappsStore } from '@/state/legacyFavoriteDapps';
export default async function runMigrations() {
// get current version
@@ -639,35 +637,6 @@ export default async function runMigrations() {
migrations.push(v18);
- /**
- *************** Migration v19 ******************
- * Migrates dapp browser favorites store from createStore to createRainbowStore
- */
- const v19 = async () => {
- const initializeLegacyStore = () => {
- return new Promise(resolve => {
- // Give the async legacy store a moment to initialize
- setTimeout(() => {
- resolve();
- }, 1000);
- });
- };
-
- await initializeLegacyStore();
- const legacyFavorites = useLegacyFavoriteDappsStore.getState().favoriteDapps;
-
- if (legacyFavorites.length > 0) {
- // Re-standardize URLs to ensure they're in the correct format
- for (const favorite of legacyFavorites) {
- favorite.url = standardizeUrl(favorite.url);
- }
- useFavoriteDappsStore.setState({ favoriteDapps: legacyFavorites });
- useLegacyFavoriteDappsStore.setState({ favoriteDapps: [] });
- }
- };
-
- migrations.push(v19);
-
logger.sentry(`Migrations: ready to run migrations starting on number ${currentVersion}`);
// await setMigrationVersion(17);
if (migrations.length === currentVersion) {
diff --git a/src/state/browser/favoriteDappsStore.ts b/src/state/browser/favoriteDappsStore.ts
deleted file mode 100644
index 532e9a88b21..00000000000
--- a/src/state/browser/favoriteDappsStore.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { createRainbowStore } from '../internal/createRainbowStore';
-
-export interface FavoritedSite {
- name: string;
- url: string;
- image: string;
-}
-
-interface FavoriteDappsStore {
- favoriteDapps: FavoritedSite[];
- addFavorite: (site: FavoritedSite) => void;
- getFavorites: (sort?: FavoritedSite['url'][]) => FavoritedSite[];
- getOrderedIds: () => FavoritedSite['url'][];
- removeFavorite: (url: string) => void;
- isFavorite: (url: string) => boolean;
- reorderFavorites: (newOrder: FavoritedSite['url'][]) => void;
-}
-
-/**
- * Strips a URL down from e.g. `https://www.rainbow.me/app/` to `rainbow.me/app`.
- * @param stripPath - Optionally strip the path from the URL, leaving `rainbow.me`.
- */
-export const standardizeUrl = (url: string, stripPath?: boolean) => {
- let standardizedUrl = url?.trim();
- standardizedUrl = standardizedUrl?.replace(/^https?:\/\//, '');
- standardizedUrl = standardizedUrl?.replace(/^www\./, '');
- if (standardizedUrl?.endsWith('/')) {
- standardizedUrl = standardizedUrl?.slice(0, -1);
- }
- if (standardizedUrl?.includes('?')) {
- standardizedUrl = standardizedUrl?.split('?')[0];
- }
- if (stripPath) {
- standardizedUrl = standardizedUrl?.split('/')?.[0] || standardizedUrl;
- }
- return standardizedUrl;
-};
-
-export const useFavoriteDappsStore = createRainbowStore(
- (set, get) => ({
- favoriteDapps: [],
-
- addFavorite: site => {
- const { favoriteDapps } = get();
- const standardizedUrl = standardizeUrl(site.url);
-
- if (!favoriteDapps.some(dapp => dapp.url === standardizedUrl)) {
- set({ favoriteDapps: [...favoriteDapps, { ...site, url: standardizedUrl }] });
- }
- },
-
- getFavorites: sort => {
- const { favoriteDapps } = get();
- if (!sort) return favoriteDapps;
-
- const sortMap = new Map(sort.map((url, index) => [url, index]));
-
- return [...favoriteDapps].sort((a, b) => {
- const indexA = sortMap.get(a.url) ?? Infinity;
- const indexB = sortMap.get(b.url) ?? Infinity;
- return indexA - indexB;
- });
- },
-
- getOrderedIds: () => get().favoriteDapps.map(dapp => dapp.url),
-
- isFavorite: url => {
- const { favoriteDapps } = get();
- const standardizedUrl = standardizeUrl(url);
- const foundMatch = favoriteDapps.some(dapp => dapp.url === standardizedUrl);
- if (foundMatch) return true;
-
- const baseUrl = standardizeUrl(url, true);
- return favoriteDapps.some(dapp => dapp.url.startsWith(baseUrl));
- },
-
- removeFavorite: url => {
- const { favoriteDapps } = get();
- const standardizedUrl = standardizeUrl(url);
- const match = favoriteDapps.find(dapp => dapp.url === standardizedUrl);
-
- if (match) {
- set({ favoriteDapps: favoriteDapps.filter(dapp => dapp.url !== standardizedUrl) });
- } else {
- const baseUrl = standardizeUrl(url, true);
- const baseUrlMatch = favoriteDapps.find(dapp => dapp.url.startsWith(baseUrl));
- if (baseUrlMatch) {
- set({ favoriteDapps: favoriteDapps.filter(dapp => dapp.url !== baseUrlMatch.url) });
- }
- }
- },
-
- reorderFavorites: newOrder => {
- const { favoriteDapps } = get();
- const urlMap = new Map(favoriteDapps.map(dapp => [dapp.url, dapp]));
- const reorderedFavorites = newOrder.map(url => urlMap.get(url)).filter((dapp): dapp is FavoritedSite => dapp !== undefined);
- const remainingFavorites = favoriteDapps.filter(dapp => !newOrder.includes(dapp.url));
- set({ favoriteDapps: [...reorderedFavorites, ...remainingFavorites] });
- },
- }),
- {
- storageKey: 'browserFavorites',
- version: 1,
- }
-);
diff --git a/src/state/browser/favoriteDappsStore.test.ts b/src/state/favoriteDapps/index.test.ts
similarity index 59%
rename from src/state/browser/favoriteDappsStore.test.ts
rename to src/state/favoriteDapps/index.test.ts
index 10cd8e636a3..566645fed99 100644
--- a/src/state/browser/favoriteDappsStore.test.ts
+++ b/src/state/favoriteDapps/index.test.ts
@@ -1,10 +1,10 @@
-import { useFavoriteDappsStore } from './favoriteDappsStore';
+import { favoriteDappsStore } from '.';
// TODO: Fix test. skipping for now to unblock CI
describe.skip('FavoriteDappsStore', () => {
beforeEach(() => {
// Reset the store to its initial state before each test
- useFavoriteDappsStore.setState(
+ favoriteDappsStore.setState(
{
favoriteDapps: [],
},
@@ -13,52 +13,52 @@ describe.skip('FavoriteDappsStore', () => {
});
test('should be able to add a favorite site', () => {
- const { addFavorite } = useFavoriteDappsStore.getState();
- expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(0);
+ const { addFavorite } = favoriteDappsStore.getState();
+ expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(0);
addFavorite({
name: 'Uniswap',
url: 'uniswap.org',
image: 'uniswap.org/favicon',
});
- expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(1);
+ expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(1);
});
test('adding a duplicate favorite site should not increase the array', () => {
- const { addFavorite } = useFavoriteDappsStore.getState();
+ const { addFavorite } = favoriteDappsStore.getState();
addFavorite({
name: 'Zora',
url: 'zora.co',
image: 'zora.png',
});
- expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(1);
+ expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(1);
addFavorite({
name: 'Zora',
url: 'zora.co',
image: 'zora.png',
});
- expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(1);
+ expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(1);
});
test('should be able to remove a favorite site', () => {
- const { addFavorite, removeFavorite } = useFavoriteDappsStore.getState();
+ const { addFavorite, removeFavorite } = favoriteDappsStore.getState();
addFavorite({
name: 'Mint.fun',
url: 'mint.fun',
image: 'mint.fun/favicon',
});
- expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(1);
+ expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(1);
removeFavorite('mint.fun');
- expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(0);
+ expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(0);
});
test('removing a non-existent favorite site should do nothing', () => {
- const { removeFavorite } = useFavoriteDappsStore.getState();
+ const { removeFavorite } = favoriteDappsStore.getState();
removeFavorite('https://nonexistentdapp.com');
- expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(0);
+ expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(0);
});
test('should be able to check if a site is a favorite', () => {
- const { addFavorite, isFavorite } = useFavoriteDappsStore.getState();
+ const { addFavorite, isFavorite } = favoriteDappsStore.getState();
addFavorite({
name: 'Uniswap',
url: 'uniswap.org',
diff --git a/src/state/legacyFavoriteDapps/index.ts b/src/state/favoriteDapps/index.ts
similarity index 65%
rename from src/state/legacyFavoriteDapps/index.ts
rename to src/state/favoriteDapps/index.ts
index 7808f57d452..8298eba7fb4 100644
--- a/src/state/legacyFavoriteDapps/index.ts
+++ b/src/state/favoriteDapps/index.ts
@@ -1,24 +1,34 @@
import create from 'zustand';
-import { standardizeUrl } from '../browser/favoriteDappsStore';
import { createStore } from '../internal/createStore';
+// need to combine types here
interface Site {
name: string;
url: string;
image: string;
}
-interface LegacyFavoriteDappsStore {
+interface FavoriteDappsStore {
favoriteDapps: Site[];
addFavorite: (site: Site) => void;
removeFavorite: (url: string) => void;
isFavorite: (url: string) => boolean;
}
-export const legacyFavoriteDappsStore = createStore(
+const standardizeUrl = (url: string) => {
+ // Strips the URL down from e.g. "https://www.rainbow.me/app/" to "rainbow.me/app"
+ let standardizedUrl = url?.trim();
+ standardizedUrl = standardizedUrl?.replace(/^https?:\/\//, '');
+ standardizedUrl = standardizedUrl?.replace(/^www\./, '');
+ if (standardizedUrl?.endsWith('/')) {
+ standardizedUrl = standardizedUrl?.slice(0, -1);
+ }
+ return standardizedUrl;
+};
+
+export const favoriteDappsStore = createStore(
(set, get) => ({
favoriteDapps: [],
-
addFavorite: site => {
const { favoriteDapps } = get();
const standardizedUrl = standardizeUrl(site.url);
@@ -27,17 +37,15 @@ export const legacyFavoriteDappsStore = createStore(
set({ favoriteDapps: [...favoriteDapps, { ...site, url: standardizedUrl }] });
}
},
-
- isFavorite: url => {
+ removeFavorite: url => {
const { favoriteDapps } = get();
const standardizedUrl = standardizeUrl(url);
- return favoriteDapps.some(dapp => dapp.url === standardizedUrl);
+ set({ favoriteDapps: favoriteDapps.filter(dapp => dapp.url !== standardizedUrl) });
},
-
- removeFavorite: url => {
+ isFavorite: url => {
const { favoriteDapps } = get();
const standardizedUrl = standardizeUrl(url);
- set({ favoriteDapps: favoriteDapps.filter(dapp => dapp.url !== standardizedUrl) });
+ return favoriteDapps.some(dapp => dapp.url === standardizedUrl);
},
}),
{
@@ -48,4 +56,4 @@ export const legacyFavoriteDappsStore = createStore(
}
);
-export const useLegacyFavoriteDappsStore = create(legacyFavoriteDappsStore);
+export const useFavoriteDappsStore = create(favoriteDappsStore);
diff --git a/src/state/remoteCards/remoteCards.ts b/src/state/remoteCards/remoteCards.ts
index bd354a10588..205c59a1cb4 100644
--- a/src/state/remoteCards/remoteCards.ts
+++ b/src/state/remoteCards/remoteCards.ts
@@ -7,25 +7,28 @@ import { createRainbowStore } from '@/state/internal/createRainbowStore';
export type CardKey = string;
export interface RemoteCardsState {
- cards: Map;
cardsById: Set;
- dismissCard: (id: string) => void;
+ cards: Map;
+
+ setCards: (cards: TrimmedCards) => void;
+
getCard: (id: string) => TrimmedCard | undefined;
- getCardIdsForScreen: (screen: keyof typeof Routes) => string[];
getCardPlacement: (id: string) => TrimmedCard['placement'] | undefined;
- setCards: (cards: TrimmedCards) => void;
+ dismissCard: (id: string) => void;
+
+ getCardIdsForScreen: (screen: keyof typeof Routes) => string[];
}
type RoutesWithIndex = typeof Routes & { [key: string]: string };
-type SerializedRemoteCardsState = Omit, 'cards' | 'cardsById'> & {
- cards: Array<[string, TrimmedCard]>;
+type RemoteCardsStateWithTransforms = Omit, 'cards' | 'cardsById'> & {
cardsById: Array;
+ cards: Array<[string, TrimmedCard]>;
};
function serializeState(state: Partial, version?: number) {
try {
- const validCards = Array.from(state.cards?.entries() ?? []).filter(([, card]) => card && card.sys?.id);
+ const validCards = Array.from(state.cards?.entries() ?? []).filter(([, card]) => card && card.sys && card.sys.id);
if (state.cards && validCards.length < state.cards.size) {
logger.error(new RainbowError('remoteCardsStore: filtered cards without sys.id during serialization'), {
@@ -33,10 +36,10 @@ function serializeState(state: Partial, version?: number) {
});
}
- const transformedStateToPersist: SerializedRemoteCardsState = {
+ const transformedStateToPersist: RemoteCardsStateWithTransforms = {
...state,
- cards: validCards,
cardsById: state.cardsById ? Array.from(state.cardsById) : [],
+ cards: validCards,
};
return JSON.stringify({
@@ -50,7 +53,7 @@ function serializeState(state: Partial, version?: number) {
}
function deserializeState(serializedState: string) {
- let parsedState: { state: SerializedRemoteCardsState; version: number };
+ let parsedState: { state: RemoteCardsStateWithTransforms; version: number };
try {
parsedState = JSON.parse(serializedState);
} catch (error) {
@@ -73,7 +76,7 @@ function deserializeState(serializedState: string) {
let cardsData: Map = new Map();
try {
if (state.cards.length) {
- const validCards = state.cards.filter(([, card]) => card && card.sys?.id);
+ const validCards = state.cards.filter(([, card]) => card && card.sys && typeof card.sys.id === 'string');
if (validCards.length < state.cards.length) {
logger.error(new RainbowError('Filtered out cards without sys.id during deserialization'), {
@@ -105,10 +108,10 @@ export const remoteCardsStore = createRainbowStore(
setCards: (cards: TrimmedCards) => {
const cardsData = new Map();
- const validCards = Object.values(cards).filter(card => card?.sys?.id);
+ const validCards = Object.values(cards).filter(card => card.sys.id);
validCards.forEach(card => {
- const existingCard = get().getCard(card.sys.id);
+ const existingCard = get().getCard(card.sys.id as string);
if (existingCard) {
cardsData.set(card.sys.id, { ...existingCard, ...card });
} else {
@@ -118,18 +121,18 @@ export const remoteCardsStore = createRainbowStore(
set({
cards: cardsData,
- cardsById: new Set(validCards.map(card => card.sys.id)),
+ cardsById: new Set(validCards.map(card => card.sys.id as string)),
});
},
getCard: (id: string) => {
const card = get().cards.get(id);
- return card?.sys?.id ? card : undefined;
+ return card && card.sys.id ? card : undefined;
},
getCardPlacement: (id: string) => {
const card = get().getCard(id);
- if (!card || !card.sys?.id || !card.placement) {
+ if (!card || !card.sys.id || !card.placement) {
return undefined;
}
@@ -155,21 +158,17 @@ export const remoteCardsStore = createRainbowStore(
// NOTE: This is kinda a hack to immediately dismiss the card from the carousel and not have an empty space
// it will be added back during the next fetch
- const newCardsById = new Set(state.cardsById);
- newCardsById.delete(id);
+ state.cardsById.delete(id);
+
return {
...state,
cards: new Map(state.cards.set(id, newCard)),
- cardsById: newCardsById,
};
}),
getCardIdsForScreen: (screen: keyof typeof Routes) => {
return Array.from(get().cards.values())
- .filter(
- (card): card is TrimmedCard & { sys: { id: string } } =>
- !!card?.sys?.id && !card.dismissed && get().getCardPlacement(card.sys.id) === screen
- )
+ .filter(card => card.sys.id && get().getCardPlacement(card.sys.id) === screen && !card.dismissed)
.sort((a, b) => {
if (a.index === b.index) return 0;
if (a.index === undefined || a.index === null) return 1;