Skip to content

Commit

Permalink
Merge pull request #41 from Glazzes/dev
Browse files Browse the repository at this point in the history
feat(Gallery): pinchCenteringMode and onVerticalPull properties
  • Loading branch information
Glazzes authored Aug 1, 2024
2 parents 9174082 + 4541ec7 commit cf96400
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 38 deletions.
2 changes: 1 addition & 1 deletion docs/docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default defineConfig({
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: '2.0.1', items: [
{ text: '2.1.0', items: [
{text: 'Releases', link: 'https://github.com/Glazzes/react-native-zoom-toolkit/releases'},
{text: 'Contributing', link: 'https://github.com/Glazzes/react-native-zoom-toolkit/blob/main/CONTRIBUTING.md'},
]}
Expand Down
33 changes: 33 additions & 0 deletions docs/docs/components/gallery.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,17 @@ For more information see this [Gesture Handler's issue](https://github.com/softw

Lets the user drag the current item around as they pinch, it also provides a more accurate pinch gesture calculation to user interaction.

### pinchCenteringMode
| Type | Default | Additional Info |
|------|----------|-----------------|
| `PinchCenteringMode` | `PinchCenteringMode.CLAMP` | see [PinchCenteringMode](#pinchcenteringmode-enum) |

::: tip Tip
To get the best out of this feature keep `allowPinchPanning` set to `true`.
:::

Modify the way the pinch gesture reacts to the user interaction.

### onIndexChange
| Type | Default |
|------|----------|
Expand All @@ -213,6 +224,20 @@ Callback triggered when the list scrolls to the next or previous item.

Callback triggered when the user taps the current item once, provides additional metadata like index if you need it.

### onVerticalPull
| Type | Default | Additional Info |
|------|----------|-----------------|
| `(translateY: number, released: boolean) => void` | `undefined` | see [worklets](https://docs.swmansion.com/react-native-reanimated/docs/2.x/fundamentals/worklets/) |

::: tip Conditions
- Gallery must be on horizontal mode
- The current item must be at a scale of one.
:::

Worklet callback triggered as the user drags the component vertically when this one is at a scale of one, it includes metadata like `released` parameter which indicates whether the user stopped pulling.

This property is useful for instance to animate the background color based on the translateY parameter.

### onSwipe
| Type | Default |
|------|---------|
Expand Down Expand Up @@ -329,6 +354,14 @@ Jump to the item at the given index.
- Returns `void`
## Type Definitions
### PinchCenteringMode Enum
Determine the behavior used by the pinch gesture relative to the boundaries of its enclosing component.
| Property | Description |
|--------------|--------------|
| `CLAMP` | Keeps the pinch gesture clamped to the borders or its enclosing container during the entirity of the gesture, just like seen on Android galleries. |
| `INTERACTION` | Keeps the pinch gesture in sync with user interaction, if the pinch gesture was released in an out bonds position it will animate back to a position within the bondaries of its enclosing container. |
### ResumableZoomState
| Property | Type | Description |
|--------------|----------|------------------------------------------|
Expand Down
15 changes: 11 additions & 4 deletions src/components/gallery/Gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { getPanWithPinchStatus } from '../../commons/utils/getPanWithPinchStatus
import Reflection from './Reflection';
import GalleryItem from './GalleryItem';
import { GalleryContext } from './context';
import type { GalleryProps, GalleryType } from './types';
import {
PinchCenteringMode,
type GalleryProps,
type GalleryType,
} from './types';

type GalleryPropsWithRef<T> = GalleryProps<T> & {
reference?: React.ForwardedRef<GalleryType>;
Expand All @@ -32,6 +36,7 @@ const Gallery = <T extends unknown>(props: GalleryPropsWithRef<T>) => {
maxScale: userMaxScale = 6,
vertical = false,
tapOnEdgeToItem = true,
pinchCenteringMode = PinchCenteringMode.CLAMP,
allowPinchPanning: pinchPanning,
customTransition,
onIndexChange,
Expand All @@ -42,6 +47,7 @@ const Gallery = <T extends unknown>(props: GalleryPropsWithRef<T>) => {
onPinchStart,
onPinchEnd,
onSwipe,
onVerticalPull,
} = props;

const allowPinchPanning = pinchPanning ?? getPanWithPinchStatus();
Expand Down Expand Up @@ -93,9 +99,7 @@ const Gallery = <T extends unknown>(props: GalleryPropsWithRef<T>) => {

useAnimatedReaction(
() => activeIndex.value,
(value) => {
if (onIndexChange) runOnJS(onIndexChange)(value);
},
(value) => onIndexChange && runOnJS(onIndexChange)(value),
[activeIndex]
);

Expand All @@ -114,6 +118,7 @@ const Gallery = <T extends unknown>(props: GalleryPropsWithRef<T>) => {
[vertical, activeIndex, rootSize]
);

// Reference handling
const setIndex = (index: number) => {
const clamped = clamp(index, 0, data.length);
activeIndex.value = clamped;
Expand Down Expand Up @@ -173,12 +178,14 @@ const Gallery = <T extends unknown>(props: GalleryPropsWithRef<T>) => {
vertical={vertical}
tapOnEdgeToItem={tapOnEdgeToItem}
allowPinchPanning={allowPinchPanning}
pinchCenteringMode={pinchCenteringMode}
onTap={onTap}
onPanStart={onPanStart}
onPanEnd={onPanEnd}
onPinchStart={onPinchStart}
onPinchEnd={onPinchEnd}
onSwipe={onSwipe}
onVerticalPull={onVerticalPull}
/>
</GestureHandlerRootView>
);
Expand Down
99 changes: 66 additions & 33 deletions src/components/gallery/Reflection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,20 @@ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { clamp } from '../../commons/utils/clamp';
import { pinchTransform } from '../../commons/utils/pinchTransform';
import { useVector } from '../../commons/hooks/useVector';
import { snapPoint } from '../../commons/utils/snapPoint';
import getSwipeDirection from '../../commons/utils/getSwipeDirection';
import { GalleryContext } from './context';
import { crop } from '../../commons/utils/crop';
import { usePinchCommons } from '../../commons/hooks/usePinchCommons';

import { type GalleryProps, PinchCenteringMode } from './types';
import {
PanMode,
ScaleMode,
SwipeDirection,
type BoundsFuction,
type PanGestureEvent,
type PanGestureEventCallback,
type PinchGestureEventCallback,
type TapGestureEvent,
} from '../../commons/types';
import { snapPoint } from '../../commons/utils/snapPoint';
import getSwipeDirection from '../../commons/utils/getSwipeDirection';
import { GalleryContext } from './context';
import { crop } from '../../commons/utils/crop';
import { usePinchCommons } from '../../commons/hooks/usePinchCommons';

const minScale = 1;
const config = { duration: 300, easing: Easing.linear };
Expand All @@ -41,12 +40,14 @@ type ReflectionProps = {
vertical: boolean;
tapOnEdgeToItem: boolean;
allowPinchPanning: boolean;
onTap?: (e: TapGestureEvent, index: number) => void;
onPanStart?: PanGestureEventCallback;
onPanEnd?: PanGestureEventCallback;
onPinchStart?: PinchGestureEventCallback;
onPinchEnd?: PinchGestureEventCallback;
onSwipe?: (direction: SwipeDirection) => void;
pinchCenteringMode: PinchCenteringMode;
onTap?: GalleryProps['onTap'];
onPanStart?: GalleryProps['onPanStart'];
onPanEnd?: GalleryProps['onPanEnd'];
onPinchStart?: GalleryProps['onPinchStart'];
onPinchEnd?: GalleryProps['onPinchEnd'];
onSwipe?: GalleryProps['onSwipe'];
onVerticalPull?: GalleryProps['onVerticalPull'];
};

/*
Expand All @@ -62,12 +63,14 @@ const Reflection = ({
vertical,
tapOnEdgeToItem,
allowPinchPanning,
pinchCenteringMode: pinchMode,
onTap,
onPanStart,
onPanEnd,
onPinchStart: onUserPinchStart,
onPinchEnd: onUserPinchEnd,
onSwipe: onUserSwipe,
onVerticalPull,
}: ReflectionProps) => {
const {
activeIndex,
Expand All @@ -93,6 +96,9 @@ const Reflection = ({
const time = useSharedValue<number>(0);
const position = useVector(0, 0);

const isPullingVertical = useSharedValue<boolean>(false);
const pullReleased = useSharedValue<boolean>(false);

const boundsFn: BoundsFuction = (scaleValue) => {
'worklet';

Expand Down Expand Up @@ -176,21 +182,28 @@ const Reflection = ({
};

useAnimatedReaction(
() => rootSize.width.value,
() => reset(0, 0, minScale, false),
[rootSize]
);

useAnimatedReaction(
() => activeIndex.value,
() => reset(0, 0, minScale, false),
[activeIndex]
() => ({
translate: translate.y.value,
scale: scale.value,
isPulling: isPullingVertical.value,
released: pullReleased.value,
}),
(val) => {
if (!vertical && val.scale === 1 && val.isPulling) {
onVerticalPull?.(val.translate, pullReleased.value);
}
},
[translate, scale, isPullingVertical, pullReleased]
);

useAnimatedReaction(
() => resetIndex.value,
() => ({
root: rootSize.width.value,
active: activeIndex.value,
reset: resetIndex.value,
}),
() => reset(0, 0, minScale, false),
[resetIndex]
[rootSize, activeIndex, resetIndex]
);

const { gesturesEnabled, onPinchStart, onPinchUpdate, onPinchEnd } =
Expand All @@ -208,7 +221,8 @@ const Reflection = ({
delta,
allowPinchPanning,
scaleMode: ScaleMode.BOUNCE,
panMode: PanMode.CLAMP,
panMode:
pinchMode === PinchCenteringMode.CLAMP ? PanMode.CLAMP : PanMode.FREE,
boundFn: boundsFn,
userCallbacks: {
onPinchStart: onUserPinchStart,
Expand Down Expand Up @@ -236,6 +250,11 @@ const Reflection = ({
position.x.value = e.absoluteX;
position.y.value = e.absoluteY;

const isVerticalPan = Math.abs(e.velocityY) > Math.abs(e.velocityX);
if (isVerticalPan && scale.value === 1 && !vertical) {
isPullingVertical.value = true;
}

cancelAnimation(translate.x);
cancelAnimation(translate.y);
cancelAnimation(detectorTranslate.x);
Expand All @@ -245,6 +264,11 @@ const Reflection = ({
offset.y.value = translate.y.value;
})
.onUpdate(({ translationX, translationY }) => {
if (isPullingVertical.value) {
translate.y.value = translationY;
return;
}

const toX = offset.x.value + translationX;
const toY = offset.y.value + translationY;

Expand Down Expand Up @@ -276,6 +300,17 @@ const Reflection = ({
detectorTranslate.y.value = clamp(toY, -1 * boundY, boundY);
})
.onEnd((e) => {
if (isPullingVertical.value) {
pullReleased.value = true;
translate.y.value = withTiming(0, undefined, (finished) => {
if (finished) {
isPullingVertical.value = false;
pullReleased.value = false;
}
});
return;
}

const boundaries = boundsFn(scale.value);
const direction = getSwipeDirection(e, {
boundaries,
Expand All @@ -286,16 +321,12 @@ const Reflection = ({

if (direction !== undefined) {
onSwipe(direction);
if (onUserSwipe !== undefined) runOnJS(onUserSwipe)(direction);

onUserSwipe && runOnJS(onUserSwipe)(direction);
return;
}

if (onPanEnd !== undefined) {
runOnJS(onPanEnd)(e);
}

onScrollEnd(e);
onPanEnd && runOnJS(onPanEnd)(e);

const clampX: [number, number] = [-1 * boundaries.x, boundaries.x];
const clampY: [number, number] = [-1 * boundaries.y, boundaries.y];
Expand Down Expand Up @@ -417,6 +448,8 @@ export default React.memo(Reflection, (prev, next) => {
prev.length === next.length &&
prev.vertical === next.vertical &&
prev.tapOnEdgeToItem === next.tapOnEdgeToItem &&
prev.allowPinchPanning === next.allowPinchPanning
prev.allowPinchPanning === next.allowPinchPanning &&
prev.pinchCenteringMode === next.pinchCenteringMode &&
prev.onVerticalPull === next.onVerticalPull
);
});
7 changes: 7 additions & 0 deletions src/components/gallery/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import type {
} from '../../commons/types';
import type { ResumableZoomState } from '../resumable/types';

export enum PinchCenteringMode {
CLAMP,
INTERACTION,
}

export type GalleryTransitionState = {
index: number;
activeIndex: number;
Expand All @@ -31,11 +36,13 @@ export type GalleryProps<T = unknown> = {
vertical?: boolean;
tapOnEdgeToItem?: boolean;
allowPinchPanning?: boolean;
pinchCenteringMode?: PinchCenteringMode;
customTransition?: GalleryTransitionCallback;
onTap?: (e: TapGestureEvent, index: number) => void;
onSwipe?: (direction: SwipeDirection) => void;
onIndexChange?: (index: number) => void;
onScroll?: (scroll: number, contentOffset: number) => void;
onVerticalPull?: (translateY: number, released: boolean) => void;
} & PinchGestureCallbacks &
PanGestureCallbacks;

Expand Down

0 comments on commit cf96400

Please sign in to comment.