Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { type PropsWithChildren, useCallback, useEffect } from 'react';
import type { StyleProp, ViewStyle } from 'react-native';
import { View } from 'react-native';
import { GestureDetector } from 'react-native-gesture-handler';
import { runOnUI, useAnimatedRef } from 'react-native-reanimated';

import {
Expand All @@ -10,6 +9,7 @@ import {
useItemContext
} from '../../providers';
import { error } from '../../utils';
import SortableGestureDetector from './SortableGestureDetector';

/** Props for the Sortable Handle component */
export type CustomHandleProps = PropsWithChildren<{
Expand Down Expand Up @@ -74,7 +74,7 @@ function CustomHandleComponent({
}, [itemKey, isActive, updateActiveHandleMeasurements]);

return (
<GestureDetector gesture={gesture.enabled(dragEnabled)} userSelect='none'>
<SortableGestureDetector gesture={gesture.enabled(dragEnabled)}>
<View
collapsable={false}
ref={handleRef}
Expand All @@ -84,6 +84,6 @@ function CustomHandleComponent({
}}>
{children}
</View>
</GestureDetector>
</SortableGestureDetector>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Fragment, memo, useCallback, useEffect, useState } from 'react';
import type { LayoutChangeEvent } from 'react-native';
import { GestureDetector } from 'react-native-gesture-handler';
import {
LayoutAnimationConfig,
runOnUI,
Expand All @@ -22,6 +21,7 @@ import {
useMeasurementsContext,
usePortalContext
} from '../../../providers';
import SortableGestureDetector from '../SortableGestureDetector';
import ActiveItemPortal from './ActiveItemPortal';
import ItemCell from './ItemCell';

Expand Down Expand Up @@ -100,9 +100,9 @@ function DraggableView({
{customHandle ? (
innerComponent
) : (
<GestureDetector gesture={gesture} userSelect='none'>
<SortableGestureDetector gesture={gesture}>
{innerComponent}
</GestureDetector>
</SortableGestureDetector>
)}
</ItemContextProvider>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { PropsWithChildren } from 'react';
import type {
ComposedGesture,
GestureType
} from 'react-native-gesture-handler';
import { GestureDetector } from 'react-native-gesture-handler';

export type SortableGestureDetectorProps = PropsWithChildren<{
gesture: ComposedGesture | GestureType;
}>;

/**
* Wrapper over gesture handler's `GestureDetector` used by all draggable item
* parts. On native it is a passthrough; the web counterpart (`.web`) layers on
* the browser-specific props needed to coexist with native scrolling.
*/
export default function SortableGestureDetector({
children,
gesture
}: SortableGestureDetectorProps) {
return <GestureDetector gesture={gesture}>{children}</GestureDetector>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { type PropsWithChildren, useCallback, useEffect, useRef } from 'react';
import type {
ComposedGesture,
GestureType
} from 'react-native-gesture-handler';
import { GestureDetector } from 'react-native-gesture-handler';
import { runOnJS, useAnimatedReaction } from 'react-native-reanimated';

import { useCommonValuesContext, useItemContext } from '../../providers';

// A single non-passive `touchmove` listener shared by all sortables, attached
// only while at least one item is being dragged (ref counted).
let activeDragCount = 0;
const preventScroll = (event: TouchEvent) => event.preventDefault();

function blockNativeScroll() {
if (activeDragCount++ === 0) {
document.addEventListener('touchmove', preventScroll, { passive: false });
}
}

function releaseNativeScroll() {
if (activeDragCount > 0 && --activeDragCount === 0) {
document.removeEventListener('touchmove', preventScroll);
}
}

export type SortableGestureDetectorProps = PropsWithChildren<{
gesture: ComposedGesture | GestureType;
}>;

/**
* Web `GestureDetector`: relaxes `touch-action` to the scroll axis (so items
* don't block scrolling the ScrollView) and blocks native scroll while dragging.
*/
export default function SortableGestureDetector({
children,
gesture
}: SortableGestureDetectorProps) {
const { autoScrollDirection } = useCommonValuesContext();
const { isActive } = useItemContext();
const isBlockingRef = useRef(false);
const touchAction = autoScrollDirection === 'horizontal' ? 'pan-x' : 'pan-y';

const setBlocking = useCallback((blocking: boolean) => {
if (blocking === isBlockingRef.current) {
return;
}
isBlockingRef.current = blocking;
if (blocking) {
blockNativeScroll();
} else {
releaseNativeScroll();
}
}, []);

useAnimatedReaction(
() => isActive.value,
(active, previous) => {
if (active !== previous) {
runOnJS(setBlocking)(active);
}
}
);

// Release the lock if the item unmounts mid-drag
useEffect(() => () => setBlocking(false), [setBlocking]);

return (
<GestureDetector
gesture={gesture}
touchAction={touchAction}
userSelect='none'>
{children}
</GestureDetector>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { type PropsWithChildren, useMemo } from 'react';
import type { ViewProps } from 'react-native';
import { View } from 'react-native';
import type { GestureType } from 'react-native-gesture-handler';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { Gesture } from 'react-native-gesture-handler';

import { useItemContext } from '../../providers';
import SortableGestureDetector from './SortableGestureDetector';

type SortableTouchableProps = PropsWithChildren<
ViewProps & {
Expand Down Expand Up @@ -92,10 +93,10 @@ export default function SortableTouchable({
]);

return (
<GestureDetector gesture={gesture} userSelect='none'>
<SortableGestureDetector gesture={gesture}>
<View {...viewProps} collapsable={false}>
{children}
</View>
</GestureDetector>
</SortableGestureDetector>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import type {
ActiveItemDecorationSettings,
ActiveItemSnapSettings,
AutoScrollSettings,
CommonValuesContextType,
ControlledDimensions,
Dimensions,
Expand All @@ -31,6 +32,7 @@ type CommonValuesProviderProps = PropsWithChildren<
ActiveItemDecorationSettings &
ActiveItemSnapSettings &
Omit<ItemDragSettings, 'overDrag' | 'reorderTriggerOrigin'> & {
autoScrollDirection: AutoScrollSettings['autoScrollDirection'];
sortEnabled: Animatable<boolean>;
customHandle: boolean;
controlledContainerDimensions: ControlledDimensions;
Expand All @@ -49,6 +51,7 @@ const { CommonValuesContext, CommonValuesProvider, useCommonValuesContext } =
activeItemOpacity: _activeItemOpacity,
activeItemScale: _activeItemScale,
activeItemShadowOpacity: _activeItemShadowOpacity,
autoScrollDirection,
controlledContainerDimensions,
controlledItemDimensions,
customHandle,
Expand Down Expand Up @@ -153,6 +156,7 @@ const { CommonValuesContext, CommonValuesProvider, useCommonValuesContext } =
activeItemScale,
activeItemShadowOpacity,
animateLayoutOnReorderOnly,
autoScrollDirection,
containerHeight,
containerId,
containerRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export default function SharedProvider({
__DEV__ && debug && <DebugProvider />,
// Provider used for shared values between all providers below
<CommonValuesProvider
autoScrollDirection={autoScrollDirection}
customHandle={customHandle}
sortEnabled={sortEnabled}
{...rest}
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native-sortables/src/types/providers/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { Dimensions, ItemSizes, Vector } from '../layout/shared';
import type {
ActiveItemDecorationSettings,
ActiveItemSnapSettings,
AutoScrollSettings,
ItemDragSettings,
ReorderTriggerOrigin
} from '../props/shared';
Expand Down Expand Up @@ -97,6 +98,7 @@ export type CommonValuesContextType =
animateLayoutOnReorderOnly: SharedValue<boolean>;
customHandle: boolean;
isStackingOrderDesc: boolean;
autoScrollDirection: AutoScrollSettings['autoScrollDirection'];
};

// MEASUREMENTS
Expand Down
Loading