Skip to content
Open
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,13 @@
import type { ForwardedRef } from 'react';
import { useCallback, useImperativeHandle, useMemo } from 'react';
import {
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
} from 'react';
import type { LayoutChangeEvent } from 'react-native';
import { I18nManager, StyleSheet, View } from 'react-native';
import { I18nManager, NativeModules, StyleSheet, View } from 'react-native';
import Animated, {
interpolate,
measure,
Expand Down Expand Up @@ -31,6 +37,53 @@ const DEFAULT_DRAG_OFFSET = 10;
const DEFAULT_ENABLE_TRACKING_TWO_FINGER_GESTURE = false;

const Swipeable = (props: SwipeableProps) => {
const containerRef = useRef<View>(null);
const isOpenRef = useRef(false);
const swipeableRegistry = NativeModules.RNSSwipeableRegistry as
| {
setOpenSwipeableFrame?: (
x: number,
y: number,
width: number,
height: number
) => void;
clearOpenSwipeable?: () => void;
}
| undefined;

const syncOpenSwipeableFrame = useCallback(() => {
const setOpenSwipeableFrame = swipeableRegistry?.setOpenSwipeableFrame;
if (!containerRef.current || !setOpenSwipeableFrame) {
return;
}

containerRef.current.measureInWindow(
(x: number, y: number, width: number, height: number) => {
setOpenSwipeableFrame(x, y, width, height);
}
);
}, [swipeableRegistry]);

// Current coordination stores one active open swipeable frame.
// Multi-open ownership scoping (token/reactTag) can be added in follow-up.
const clearOpenSwipeableFrame = useCallback(() => {
const clearOpenSwipeable = swipeableRegistry?.clearOpenSwipeable;
if (!clearOpenSwipeable) {
return;
}
clearOpenSwipeable();
}, [swipeableRegistry]);
Comment on lines +69 to +75
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point — the current registry is intentionally minimal and stores a single active open-swipeable frame.
For this first iteration, the goal is to establish native arbitration and validate behavior for the common pattern where only one row is open at a time.
I agree multi-open scenarios need stronger ownership semantics (e.g. token/reactTag-scoped set/clear), and I can follow up with a dedicated change once this direction is accepted.


const markOpen = useCallback(() => {
isOpenRef.current = true;
syncOpenSwipeableFrame();
}, [syncOpenSwipeableFrame]);

const markClosed = useCallback(() => {
isOpenRef.current = false;
clearOpenSwipeableFrame();
}, [clearOpenSwipeableFrame]);
Comment on lines +77 to +85
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point — agreed. reset() bypasses animateRow, so I added runOnJS(markClosed)() in reset() and updated dependencies accordingly. This clears isOpenRef and native registry state to avoid stale gesture arbitration after imperative resets.


const {
ref,
leftThreshold,
Expand Down Expand Up @@ -154,8 +207,14 @@ const Swipeable = (props: SwipeableProps) => {
fromValue > 0 ? SwipeDirection.LEFT : SwipeDirection.RIGHT
);
}

if (toValue !== 0) {
runOnJS(markOpen)();
} else {
runOnJS(markClosed)();
}
},
[onSwipeableWillClose, onSwipeableWillOpen]
[markClosed, markOpen, onSwipeableWillClose, onSwipeableWillOpen]
);

const dispatchEndEvents = useCallback(
Expand Down Expand Up @@ -320,6 +379,7 @@ const Swipeable = (props: SwipeableProps) => {
showLeftProgress.value = 0;
appliedTranslation.value = 0;
rowState.value = 0;
runOnJS(markClosed)();
},
}),
[
Expand All @@ -337,10 +397,21 @@ const Swipeable = (props: SwipeableProps) => {
const onRowLayout = useCallback(
({ nativeEvent }: LayoutChangeEvent) => {
rowWidth.value = nativeEvent.layout.width;
if (isOpenRef.current) {
syncOpenSwipeableFrame();
}
},
[rowWidth]
[rowWidth, syncOpenSwipeableFrame]
);

useEffect(() => {
return () => {
if (isOpenRef.current) {
clearOpenSwipeableFrame();
}
};
}, [clearOpenSwipeableFrame]);

// As stated in `Dimensions.get` docstring, this function should be called on every render
// since dimensions may change (e.g. orientation change)

Expand Down Expand Up @@ -523,6 +594,7 @@ const Swipeable = (props: SwipeableProps) => {
const swipeableComponent = (
<GestureDetector gesture={panGesture} touchAction="pan-y">
<Animated.View
ref={containerRef}
{...remainingProps}
onLayout={onRowLayout}
style={[styles.container, containerStyle]}>
Expand Down