Skip to content

Commit

Permalink
feat: add closeOnPullDown controller option
Browse files Browse the repository at this point in the history
  • Loading branch information
igordanchenko committed May 12, 2023
1 parent a442f46 commit 80df05e
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 62 deletions.
64 changes: 32 additions & 32 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
"devDependencies": {
"@commitlint/cli": "^17.6.3",
"@commitlint/config-conventional": "^17.6.3",
"@rollup/plugin-typescript": "^11.1.0",
"@rollup/plugin-typescript": "^11.1.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
Expand All @@ -189,7 +189,7 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"glob": "^10.2.2",
"glob": "^10.2.3",
"husky": "^8.0.3",
"jsdom": "^22.0.0",
"lint-staged": "^13.2.2",
Expand Down
29 changes: 24 additions & 5 deletions src/modules/Controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,32 @@ export function Controller({ children, ...props }: ComponentProps) {
isSwipeValid,
containerRect?.width || 0,
animation.swipe,
() => setSwipeState(SwipeState.SWIPE), // onSwipeStart
(offset: number) => setSwipeOffset(offset), // onSwipeProgress
(offset: number, duration: number) => swipe({ offset, duration, count: 1 }), // onSwipeFinish
(offset: number) => swipe({ offset, count: 0 }), // onSwipeCancel
// onSwipeStart
() => setSwipeState(SwipeState.SWIPE),
// onSwipeProgress
(offset: number) => setSwipeOffset(offset),
// onSwipeFinish
(offset: number, duration: number) => swipe({ offset, duration, count: 1 }),
// onSwipeCancel
(offset: number) => swipe({ offset, count: 0 }),
] as const;

usePointerSwipe(...swipeParams);
const pullDownParams = [
// onPullDownStart
() => {},
// onPullDownProgress
() => {},
// onPullDownFinish
() => {
if (controller.closeOnPullDown) {
close();
}
},
// onPullDownCancel
() => {},
] as const;

usePointerSwipe(...swipeParams, ...pullDownParams);

useWheelSwipe(swipeState, ...swipeParams);

Expand Down
82 changes: 59 additions & 23 deletions src/modules/controller/usePointerSwipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import { UseSensors } from "../../hooks/useSensors.js";
import { useEventCallback } from "../../hooks/useEventCallback.js";
import { usePointerEvents } from "../../hooks/usePointerEvents.js";

enum Gesture {
NONE,
SWIPE,
PULL_DOWN,
}

const SWIPE_THRESHOLD = 30;

export function usePointerSwipe<T extends Element = Element>(
subscribeSensors: UseSensors<T>["subscribeSensors"],
isSwipeValid: (offset: number) => boolean,
Expand All @@ -12,16 +20,22 @@ export function usePointerSwipe<T extends Element = Element>(
onSwipeStart: () => void,
onSwipeProgress: (offset: number) => void,
onSwipeFinish: (offset: number, duration: number) => void,
onSwipeCancel: (offset: number) => void
onSwipeCancel: (offset: number) => void,
onPullDownStart: () => void,
onPullDownProgress: (offset: number) => void,
onPullDownFinish: (offset: number, duration: number) => void,
onPullDownCancel: (offset: number) => void
) {
const offset = React.useRef<number>(0);
const pointers = React.useRef<React.PointerEvent[]>([]);
const activePointer = React.useRef<number>();
const startTime = React.useRef<number>(0);
const gesture = React.useRef(Gesture.NONE);

const clearPointer = React.useCallback((event: React.PointerEvent) => {
if (activePointer.current === event.pointerId) {
activePointer.current = undefined;
gesture.current = Gesture.NONE;
}

const currentPointers = pointers.current;
Expand Down Expand Up @@ -52,16 +66,26 @@ export function usePointerSwipe<T extends Element = Element>(
) {
const duration = Date.now() - startTime.current;
const currentOffset = offset.current;
if (
Math.abs(currentOffset) > 0.3 * containerWidth ||
(Math.abs(currentOffset) > 5 && duration < swipeAnimationDuration)
) {
onSwipeFinish(currentOffset, duration);
} else {
onSwipeCancel(currentOffset);

if (gesture.current === Gesture.SWIPE) {
if (
Math.abs(currentOffset) > 0.3 * containerWidth ||
(Math.abs(currentOffset) > 5 && duration < swipeAnimationDuration)
) {
onSwipeFinish(currentOffset, duration);
} else {
onSwipeCancel(currentOffset);
}
} else if (gesture.current === Gesture.PULL_DOWN) {
if (currentOffset > 2 * SWIPE_THRESHOLD) {
onPullDownFinish(currentOffset, duration);
} else {
onPullDownCancel(currentOffset);
}
}

offset.current = 0;
gesture.current = Gesture.NONE;
}

clearPointer(event);
Expand All @@ -87,22 +111,34 @@ export function usePointerSwipe<T extends Element = Element>(
const deltaX = event.clientX - pointer.clientX;
const deltaY = event.clientY - pointer.clientY;

if (
activePointer.current === undefined &&
isSwipeValid(deltaX) &&
Math.abs(deltaX) > Math.abs(deltaY) &&
Math.abs(deltaX) > 30
) {
addPointer(event);

activePointer.current = event.pointerId;
startTime.current = Date.now();

onSwipeStart();
// no gesture in progress
if (activePointer.current === undefined) {
const startGesture = (newGesture: Gesture) => {
addPointer(event);

activePointer.current = event.pointerId;
startTime.current = Date.now();

gesture.current = newGesture;
};

if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > SWIPE_THRESHOLD && isSwipeValid(deltaX)) {
// start swipe gesture
startGesture(Gesture.SWIPE);
onSwipeStart();
} else if (Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > SWIPE_THRESHOLD) {
// start pull-down gesture
startGesture(Gesture.PULL_DOWN);
onPullDownStart();
}
} else if (isCurrentPointer) {
offset.current = deltaX;

onSwipeProgress(deltaX);
if (gesture.current === Gesture.SWIPE) {
offset.current = deltaX;
onSwipeProgress(deltaX);
} else if (gesture.current === Gesture.PULL_DOWN) {
offset.current = deltaY;
onPullDownProgress(deltaY);
}
}
}
});
Expand Down
1 change: 1 addition & 0 deletions src/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const LightboxDefaultProps: LightboxProps = {
focus: true,
aria: false,
touchAction: "none",
closeOnPullDown: false,
closeOnBackdropClick: false,
},
portal: {},
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ export interface ControllerSettings {
touchAction: "none" | "pan-y";
/** if `true`, set ARIA attributes on the controller div */
aria: boolean;
/** if `true`, close the lightbox on pull-down gesture */
closeOnPullDown: boolean;
/** if `true`, close the lightbox when the backdrop is clicked */
closeOnBackdropClick: boolean;
}
Expand Down

0 comments on commit 80df05e

Please sign in to comment.