diff --git a/.changeset/clever-feet-camp.md b/.changeset/clever-feet-camp.md
new file mode 100644
index 00000000..f454553d
--- /dev/null
+++ b/.changeset/clever-feet-camp.md
@@ -0,0 +1,5 @@
+---
+"react-swipeable": minor
+---
+
+Add ability to pass a function to preventScrollOnSwipe
diff --git a/__tests__/useSwipeable.spec.tsx b/__tests__/useSwipeable.spec.tsx
index 648fdf5f..d4ca2c8b 100644
--- a/__tests__/useSwipeable.spec.tsx
+++ b/__tests__/useSwipeable.spec.tsx
@@ -1093,4 +1093,84 @@ describe("useSwipeable", () => {
// verify we did NOT trigger another swipe
expect(onSwiped).toHaveBeenCalledTimes(1);
});
+
+ it("calls preventDefault when passing a function that returns true", () => {
+ const onSwipedDown = jest.fn();
+ const preventScrollOnSwipe = () => true;
+
+ const { getByText, rerender } = render(
+
+ );
+
+ const touchArea = getByText(TESTING_TEXT);
+
+ fireEvent[TS](touchArea, cte({ x: 100, y: 100 }));
+ fireEvent[TM](touchArea, cte({ x: 100, y: 150 }));
+ fireEvent[TM](touchArea, cte({ x: 100, y: 200 }));
+ fireEvent[TE](touchArea, cte({}));
+
+ // Validate `undefined` does not trigger defaultPrevented
+ expect(onSwipedDown).not.toHaveBeenCalled();
+ expect(defaultPrevented).toBe(0);
+
+ rerender(
+
+ );
+
+ fireEvent[TS](touchArea, cte({ x: 100, y: 100 }));
+ fireEvent[TM](touchArea, cte({ x: 100, y: 125 }));
+ fireEvent[TM](touchArea, cte({ x: 100, y: 150 }));
+ fireEvent[TM](touchArea, cte({ x: 100, y: 175 }));
+ fireEvent[TM](touchArea, cte({ x: 100, y: 200 }));
+ fireEvent[TE](touchArea, cte({}));
+
+ expect(onSwipedDown).toHaveBeenCalled();
+ expect(defaultPrevented).toBe(4);
+ });
+
+ it("does not preventDefault when passing a function that returns false", () => {
+ const onSwipedDown = jest.fn();
+ const preventScrollOnSwipe = () => false;
+
+ const { getByText, rerender } = render(
+
+ );
+
+ const touchArea = getByText(TESTING_TEXT);
+
+ fireEvent[TS](touchArea, cte({ x: 100, y: 100 }));
+ fireEvent[TM](touchArea, cte({ x: 100, y: 150 }));
+ fireEvent[TM](touchArea, cte({ x: 100, y: 200 }));
+ fireEvent[TE](touchArea, cte({}));
+
+ // Validate `undefined` does not trigger defaultPrevented
+ expect(onSwipedDown).not.toHaveBeenCalled();
+ expect(defaultPrevented).toBe(0);
+
+ rerender(
+
+ );
+
+ fireEvent[TS](touchArea, cte({ x: 100, y: 100 }));
+ fireEvent[TM](touchArea, cte({ x: 100, y: 125 }));
+ fireEvent[TM](touchArea, cte({ x: 100, y: 150 }));
+ fireEvent[TM](touchArea, cte({ x: 100, y: 175 }));
+ fireEvent[TM](touchArea, cte({ x: 100, y: 200 }));
+ fireEvent[TE](touchArea, cte({}));
+
+ expect(onSwipedDown).toHaveBeenCalled();
+ expect(defaultPrevented).toBe(0);
+ });
});
diff --git a/docs/docs/api.md b/docs/docs/api.md
index 3066738e..cd04771d 100644
--- a/docs/docs/api.md
+++ b/docs/docs/api.md
@@ -116,15 +116,18 @@ Swipeable will call `e.preventDefault()` internally in an attempt to stop the br
Please experiment with the [Feature Testing Console](examples/feature-test-console) to test `preventScrollOnSwipe`.
+You may also pass a function to this option which accepts `SwipeEventData` and returns a boolean. This could be useful if you only want to prevent scroll based on certain criteria.
+
#### passive listener details
Swipeable adds the passive event listener option, by default, to **internal uses** of touch `addEventListener`'s. We set the `passive` option to `false` only when `preventScrollOnSwipe` is `true` and only to `touchmove`. Other listeners will retain `passive: true`.
**When `preventScrollOnSwipe` is:**
- `true` => `el.addEventListener('touchmove', cb, { passive: false })`
- `false` => `el.addEventListener('touchmove', cb, { passive: true })`
+ - `(swipeEventData) => boolean` => `el.addEventListener('touchmove', cb, { passive: false })`
Here is more information on react's long running passive [event issue](https://github.com/facebook/react/issues/6436).
We previously had issues with chrome lighthouse performance deducting points for not having passive option set so it is now on by default except in the case mentioned above.
-If, however, you really **need** _all_ of the listeners to be passive (for performance reasons or otherwise), you can prevent all scrolling on the swipeable container by using the `touch-action` css property instead, [see an example](faq#how-to-use-touch-action-to-prevent-scrolling).
\ No newline at end of file
+If, however, you really **need** _all_ of the listeners to be passive (for performance reasons or otherwise), you can prevent all scrolling on the swipeable container by using the `touch-action` css property instead, [see an example](faq#how-to-use-touch-action-to-prevent-scrolling).
diff --git a/src/index.ts b/src/index.ts
index 2958bae4..c052ed40 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -156,7 +156,7 @@ function getHandlers(
defaultProps.delta;
if (absX < delta && absY < delta && !state.swiping) return state;
- const eventData = {
+ const eventData: SwipeEventData = {
absX,
absY,
deltaX,
@@ -192,7 +192,13 @@ function getHandlers(
props.trackTouch &&
event.cancelable
) {
- event.preventDefault();
+ if (typeof props.preventScrollOnSwipe === "function") {
+ if (props.preventScrollOnSwipe(eventData)) {
+ event.preventDefault();
+ }
+ } else {
+ event.preventDefault();
+ }
}
return {
diff --git a/src/types.ts b/src/types.ts
index 7e4a213b..d2edf9d7 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -116,7 +116,7 @@ export interface ConfigurationOptions {
/**
* Prevents scroll during swipe in most cases. **Default**: `false`
*/
- preventScrollOnSwipe: boolean;
+ preventScrollOnSwipe: boolean | ((eventData: SwipeEventData) => boolean);
/**
* Set a rotation angle. **Default**: `0`
*/