diff --git a/dist/@types/index.d.ts b/dist/@types/index.d.ts
new file mode 100644
index 0000000..a8628f6
--- /dev/null
+++ b/dist/@types/index.d.ts
@@ -0,0 +1,17 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { ImageURISource, ImageRequireSource } from "react-native";
+export declare type Dimensions = {
+ width: number;
+ height: number;
+};
+export declare type Position = {
+ x: number;
+ y: number;
+};
+export declare type ImageSource = ImageURISource | ImageRequireSource;
diff --git a/dist/@types/index.js b/dist/@types/index.js
new file mode 100644
index 0000000..50a575c
--- /dev/null
+++ b/dist/@types/index.js
@@ -0,0 +1,7 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
diff --git a/dist/ImageViewing.d.ts b/dist/ImageViewing.d.ts
new file mode 100644
index 0000000..a94d73e
--- /dev/null
+++ b/dist/ImageViewing.d.ts
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { ComponentType } from "react";
+import { ModalProps } from "react-native";
+import { ImageSource } from "./@types";
+declare type Props = {
+ images: ImageSource[];
+ keyExtractor?: (imageSrc: ImageSource, index: number) => string;
+ imageIndex: number;
+ visible: boolean;
+ onRequestClose: () => void;
+ onLongPress?: (image: ImageSource) => void;
+ onImageIndexChange?: (imageIndex: number) => void;
+ presentationStyle?: ModalProps["presentationStyle"];
+ animationType?: ModalProps["animationType"];
+ backgroundColor?: string;
+ swipeToCloseEnabled?: boolean;
+ doubleTapToZoomEnabled?: boolean;
+ hideHeaderOnZoom?: boolean;
+ hideFooterOnZoom?: boolean;
+ delayLongPress?: number;
+ HeaderComponent?: ComponentType<{
+ imageIndex: number;
+ }>;
+ FooterComponent?: ComponentType<{
+ imageIndex: number;
+ }>;
+};
+declare const EnhancedImageViewing: (props: Props) => JSX.Element;
+export default EnhancedImageViewing;
diff --git a/dist/ImageViewing.js b/dist/ImageViewing.js
new file mode 100644
index 0000000..6dd03be
--- /dev/null
+++ b/dist/ImageViewing.js
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import React, { useCallback, useEffect } from "react";
+import { Animated, Dimensions, StyleSheet, View, VirtualizedList, Modal, } from "react-native";
+import ImageItem from "./components/ImageItem/ImageItem";
+import ImageDefaultHeader from "./components/ImageDefaultHeader";
+import StatusBarManager from "./components/StatusBarManager";
+import useAnimatedComponents from "./hooks/useAnimatedComponents";
+import useImageIndexChange from "./hooks/useImageIndexChange";
+import useRequestClose from "./hooks/useRequestClose";
+const DEFAULT_ANIMATION_TYPE = "fade";
+const DEFAULT_BG_COLOR = "#000";
+const DEFAULT_DELAY_LONG_PRESS = 800;
+const SCREEN = Dimensions.get("screen");
+const SCREEN_WIDTH = SCREEN.width;
+const DEFAULT_HIDE_HEADER_ON_ZOOM = true;
+const DEFAULT_HIDE_FOOTER_ON_ZOOM = true;
+function ImageViewing({ images, keyExtractor, imageIndex, visible, onRequestClose, onLongPress = () => { }, onImageIndexChange, animationType = DEFAULT_ANIMATION_TYPE, backgroundColor = DEFAULT_BG_COLOR, presentationStyle, swipeToCloseEnabled, doubleTapToZoomEnabled, delayLongPress = DEFAULT_DELAY_LONG_PRESS, HeaderComponent, FooterComponent, hideHeaderOnZoom = DEFAULT_HIDE_HEADER_ON_ZOOM, hideFooterOnZoom = DEFAULT_HIDE_FOOTER_ON_ZOOM, }) {
+ const imageList = React.createRef();
+ const [opacity, onRequestCloseEnhanced] = useRequestClose(onRequestClose);
+ const [currentImageIndex, onScroll] = useImageIndexChange(imageIndex, SCREEN);
+ const [headerTransform, footerTransform, toggleBarsVisible,] = useAnimatedComponents();
+ useEffect(() => {
+ if (onImageIndexChange) {
+ onImageIndexChange(currentImageIndex);
+ }
+ }, [currentImageIndex]);
+ const onZoom = useCallback((isScaled) => {
+ var _a, _b;
+ // @ts-ignore
+ (_b = (_a = imageList) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.setNativeProps({ scrollEnabled: !isScaled });
+ toggleBarsVisible(!isScaled, hideHeaderOnZoom, hideFooterOnZoom);
+ }, [imageList, hideHeaderOnZoom, hideFooterOnZoom]);
+ if (!visible) {
+ return null;
+ }
+ return (
+
+
+
+ {typeof HeaderComponent !== "undefined"
+ ? (React.createElement(HeaderComponent, {
+ imageIndex: currentImageIndex,
+ }))
+ : ()}
+
+ images[index]} getItemCount={() => images.length} getItemLayout={(_, index) => ({
+ length: SCREEN_WIDTH,
+ offset: SCREEN_WIDTH * index,
+ index,
+ })} renderItem={({ item: imageSrc }) => ()} onMomentumScrollEnd={onScroll}
+ //@ts-ignore
+ keyExtractor={(imageSrc, index) => keyExtractor ? keyExtractor(imageSrc, index) : imageSrc.uri || `${imageSrc}`}/>
+ {typeof FooterComponent !== "undefined" && (
+ {React.createElement(FooterComponent, {
+ imageIndex: currentImageIndex,
+ })}
+ )}
+
+ );
+}
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: "#000",
+ },
+ header: {
+ position: "absolute",
+ width: "100%",
+ zIndex: 1,
+ top: 0,
+ },
+ footer: {
+ position: "absolute",
+ width: "100%",
+ zIndex: 1,
+ bottom: 0,
+ },
+});
+const EnhancedImageViewing = (props) => ();
+export default EnhancedImageViewing;
diff --git a/dist/components/ImageDefaultHeader.d.ts b/dist/components/ImageDefaultHeader.d.ts
new file mode 100644
index 0000000..5f1205d
--- /dev/null
+++ b/dist/components/ImageDefaultHeader.d.ts
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+///
+declare type Props = {
+ onRequestClose: () => void;
+};
+declare const ImageDefaultHeader: ({ onRequestClose }: Props) => JSX.Element;
+export default ImageDefaultHeader;
diff --git a/dist/components/ImageDefaultHeader.js b/dist/components/ImageDefaultHeader.js
new file mode 100644
index 0000000..1278de0
--- /dev/null
+++ b/dist/components/ImageDefaultHeader.js
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import React from "react";
+import { SafeAreaView, Text, TouchableOpacity, StyleSheet } from "react-native";
+const HIT_SLOP = { top: 16, left: 16, bottom: 16, right: 16 };
+const ImageDefaultHeader = ({ onRequestClose }) => (
+
+ ✕
+
+ );
+const styles = StyleSheet.create({
+ root: {
+ alignItems: "flex-end",
+ },
+ closeButton: {
+ marginRight: 8,
+ marginTop: 8,
+ width: 45,
+ height: 45,
+ alignItems: "center",
+ justifyContent: "center",
+ borderRadius: 22.5,
+ backgroundColor: "#00000077",
+ },
+ closeText: {
+ lineHeight: 25,
+ fontSize: 25,
+ paddingTop: 2,
+ textAlign: "center",
+ color: "#FFF",
+ includeFontPadding: false,
+ },
+});
+export default ImageDefaultHeader;
diff --git a/dist/components/ImageItem/ImageItem.android.d.ts b/dist/components/ImageItem/ImageItem.android.d.ts
new file mode 100644
index 0000000..0b1aa96
--- /dev/null
+++ b/dist/components/ImageItem/ImageItem.android.d.ts
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import React from "react";
+import { ImageSource } from "../../@types";
+declare type Props = {
+ imageSrc: ImageSource;
+ onRequestClose: () => void;
+ onZoom: (isZoomed: boolean) => void;
+ onLongPress: (image: ImageSource) => void;
+ delayLongPress: number;
+ swipeToCloseEnabled?: boolean;
+ doubleTapToZoomEnabled?: boolean;
+};
+declare const _default: React.MemoExoticComponent<({ imageSrc, onZoom, onRequestClose, onLongPress, delayLongPress, swipeToCloseEnabled, doubleTapToZoomEnabled, }: Props) => JSX.Element>;
+export default _default;
diff --git a/dist/components/ImageItem/ImageItem.android.js b/dist/components/ImageItem/ImageItem.android.js
new file mode 100644
index 0000000..7099ccb
--- /dev/null
+++ b/dist/components/ImageItem/ImageItem.android.js
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import React, { useState, useCallback } from "react";
+import { Animated, Dimensions, StyleSheet, } from "react-native";
+import useImageDimensions from "../../hooks/useImageDimensions";
+import usePanResponder from "../../hooks/usePanResponder";
+import { getImageStyles, getImageTransform } from "../../utils";
+import { ImageLoading } from "./ImageLoading";
+const SWIPE_CLOSE_OFFSET = 75;
+const SWIPE_CLOSE_VELOCITY = 1.75;
+const SCREEN = Dimensions.get("window");
+const SCREEN_WIDTH = SCREEN.width;
+const SCREEN_HEIGHT = SCREEN.height;
+const ImageItem = ({ imageSrc, onZoom, onRequestClose, onLongPress, delayLongPress, swipeToCloseEnabled = true, doubleTapToZoomEnabled = true, }) => {
+ const imageContainer = React.createRef();
+ const imageDimensions = useImageDimensions(imageSrc);
+ const [translate, scale] = getImageTransform(imageDimensions, SCREEN);
+ const scrollValueY = new Animated.Value(0);
+ const [isLoaded, setLoadEnd] = useState(false);
+ const onLoaded = useCallback(() => setLoadEnd(true), []);
+ const onZoomPerformed = (isZoomed) => {
+ var _a;
+ onZoom(isZoomed);
+ if ((_a = imageContainer) === null || _a === void 0 ? void 0 : _a.current) {
+ // @ts-ignore
+ imageContainer.current.setNativeProps({
+ scrollEnabled: !isZoomed,
+ });
+ }
+ };
+ const onLongPressHandler = useCallback(() => {
+ onLongPress(imageSrc);
+ }, [imageSrc, onLongPress]);
+ const [panHandlers, scaleValue, translateValue] = usePanResponder({
+ initialScale: scale || 1,
+ initialTranslate: translate || { x: 0, y: 0 },
+ onZoom: onZoomPerformed,
+ doubleTapToZoomEnabled,
+ onLongPress: onLongPressHandler,
+ delayLongPress,
+ });
+ const imagesStyles = getImageStyles(imageDimensions, translateValue, scaleValue);
+ const imageOpacity = scrollValueY.interpolate({
+ inputRange: [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET],
+ outputRange: [0.7, 1, 0.7],
+ });
+ const imageStylesWithOpacity = { ...imagesStyles, opacity: imageOpacity };
+ const onScrollEndDrag = ({ nativeEvent, }) => {
+ var _a, _b, _c, _d, _e, _f;
+ const velocityY = (_c = (_b = (_a = nativeEvent) === null || _a === void 0 ? void 0 : _a.velocity) === null || _b === void 0 ? void 0 : _b.y, (_c !== null && _c !== void 0 ? _c : 0));
+ const offsetY = (_f = (_e = (_d = nativeEvent) === null || _d === void 0 ? void 0 : _d.contentOffset) === null || _e === void 0 ? void 0 : _e.y, (_f !== null && _f !== void 0 ? _f : 0));
+ if ((Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY &&
+ offsetY > SWIPE_CLOSE_OFFSET) ||
+ offsetY > SCREEN_HEIGHT / 2) {
+ onRequestClose();
+ }
+ };
+ const onScroll = ({ nativeEvent, }) => {
+ var _a, _b, _c;
+ const offsetY = (_c = (_b = (_a = nativeEvent) === null || _a === void 0 ? void 0 : _a.contentOffset) === null || _b === void 0 ? void 0 : _b.y, (_c !== null && _c !== void 0 ? _c : 0));
+ scrollValueY.setValue(offsetY);
+ };
+ return (
+
+ {(!isLoaded || !imageDimensions) && }
+ );
+};
+const styles = StyleSheet.create({
+ listItem: {
+ width: SCREEN_WIDTH,
+ height: SCREEN_HEIGHT,
+ },
+ imageScrollContainer: {
+ height: SCREEN_HEIGHT * 2,
+ },
+});
+export default React.memo(ImageItem);
diff --git a/dist/components/ImageItem/ImageItem.ios.d.ts b/dist/components/ImageItem/ImageItem.ios.d.ts
new file mode 100644
index 0000000..1dd5e0e
--- /dev/null
+++ b/dist/components/ImageItem/ImageItem.ios.d.ts
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import React from "react";
+import { ImageSource } from "../../@types";
+declare type Props = {
+ imageSrc: ImageSource;
+ onRequestClose: () => void;
+ onZoom: (scaled: boolean) => void;
+ onLongPress: (image: ImageSource) => void;
+ delayLongPress: number;
+ swipeToCloseEnabled?: boolean;
+ doubleTapToZoomEnabled?: boolean;
+};
+declare const _default: React.MemoExoticComponent<({ imageSrc, onZoom, onRequestClose, onLongPress, delayLongPress, swipeToCloseEnabled, doubleTapToZoomEnabled, }: Props) => JSX.Element>;
+export default _default;
diff --git a/dist/components/ImageItem/ImageItem.ios.js b/dist/components/ImageItem/ImageItem.ios.js
new file mode 100644
index 0000000..c87e88c
--- /dev/null
+++ b/dist/components/ImageItem/ImageItem.ios.js
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import React, { useCallback, useRef, useState } from "react";
+import { Animated, Dimensions, ScrollView, StyleSheet, View, TouchableWithoutFeedback, } from "react-native";
+import useDoubleTapToZoom from "../../hooks/useDoubleTapToZoom";
+import useImageDimensions from "../../hooks/useImageDimensions";
+import { getImageStyles, getImageTransform } from "../../utils";
+import { ImageLoading } from "./ImageLoading";
+const SWIPE_CLOSE_OFFSET = 75;
+const SWIPE_CLOSE_VELOCITY = 1.55;
+const SCREEN = Dimensions.get("screen");
+const SCREEN_WIDTH = SCREEN.width;
+const SCREEN_HEIGHT = SCREEN.height;
+const ImageItem = ({ imageSrc, onZoom, onRequestClose, onLongPress, delayLongPress, swipeToCloseEnabled = true, doubleTapToZoomEnabled = true, }) => {
+ const scrollViewRef = useRef(null);
+ const [loaded, setLoaded] = useState(false);
+ const [scaled, setScaled] = useState(false);
+ const imageDimensions = useImageDimensions(imageSrc);
+ const handleDoubleTap = useDoubleTapToZoom(scrollViewRef, scaled, SCREEN);
+ const [translate, scale] = getImageTransform(imageDimensions, SCREEN);
+ const scrollValueY = new Animated.Value(0);
+ const scaleValue = new Animated.Value(scale || 1);
+ const translateValue = new Animated.ValueXY(translate);
+ const maxScale = scale && scale > 0 ? Math.max(1 / scale, 1) : 1;
+ const imageOpacity = scrollValueY.interpolate({
+ inputRange: [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET],
+ outputRange: [0.5, 1, 0.5],
+ });
+ const imagesStyles = getImageStyles(imageDimensions, translateValue, scaleValue);
+ const imageStylesWithOpacity = { ...imagesStyles, opacity: imageOpacity };
+ const onScrollEndDrag = useCallback(({ nativeEvent }) => {
+ var _a, _b, _c, _d;
+ const velocityY = (_c = (_b = (_a = nativeEvent) === null || _a === void 0 ? void 0 : _a.velocity) === null || _b === void 0 ? void 0 : _b.y, (_c !== null && _c !== void 0 ? _c : 0));
+ const scaled = ((_d = nativeEvent) === null || _d === void 0 ? void 0 : _d.zoomScale) > 1;
+ onZoom(scaled);
+ setScaled(scaled);
+ if (!scaled &&
+ swipeToCloseEnabled &&
+ Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY) {
+ onRequestClose();
+ }
+ }, [scaled]);
+ const onScroll = ({ nativeEvent, }) => {
+ var _a, _b, _c, _d;
+ const offsetY = (_c = (_b = (_a = nativeEvent) === null || _a === void 0 ? void 0 : _a.contentOffset) === null || _b === void 0 ? void 0 : _b.y, (_c !== null && _c !== void 0 ? _c : 0));
+ if (((_d = nativeEvent) === null || _d === void 0 ? void 0 : _d.zoomScale) > 1) {
+ return;
+ }
+ scrollValueY.setValue(offsetY);
+ };
+ const onLongPressHandler = useCallback((event) => {
+ onLongPress(imageSrc);
+ }, [imageSrc, onLongPress]);
+ return (
+
+ {(!loaded || !imageDimensions) && }
+
+ setLoaded(true)}/>
+
+
+ );
+};
+const styles = StyleSheet.create({
+ listItem: {
+ width: SCREEN_WIDTH,
+ height: SCREEN_HEIGHT,
+ },
+ imageScrollContainer: {
+ height: SCREEN_HEIGHT,
+ },
+});
+export default React.memo(ImageItem);
diff --git a/dist/components/ImageItem/ImageLoading.d.ts b/dist/components/ImageItem/ImageLoading.d.ts
new file mode 100644
index 0000000..865b895
--- /dev/null
+++ b/dist/components/ImageItem/ImageLoading.d.ts
@@ -0,0 +1,9 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+///
+export declare const ImageLoading: () => JSX.Element;
diff --git a/dist/components/ImageItem/ImageLoading.js b/dist/components/ImageItem/ImageLoading.js
new file mode 100644
index 0000000..9770f7d
--- /dev/null
+++ b/dist/components/ImageItem/ImageLoading.js
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import React from "react";
+import { ActivityIndicator, Dimensions, StyleSheet, View } from "react-native";
+const SCREEN = Dimensions.get("screen");
+const SCREEN_WIDTH = SCREEN.width;
+const SCREEN_HEIGHT = SCREEN.height;
+export const ImageLoading = () => (
+
+ );
+const styles = StyleSheet.create({
+ listItem: {
+ width: SCREEN_WIDTH,
+ height: SCREEN_HEIGHT,
+ },
+ loading: {
+ width: SCREEN_WIDTH,
+ height: SCREEN_HEIGHT,
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ imageScrollContainer: {
+ height: SCREEN_HEIGHT,
+ },
+});
diff --git a/dist/components/StatusBarManager.d.ts b/dist/components/StatusBarManager.d.ts
new file mode 100644
index 0000000..942c70f
--- /dev/null
+++ b/dist/components/StatusBarManager.d.ts
@@ -0,0 +1,4 @@
+declare const StatusBarManager: ({ presentationStyle, }: {
+ presentationStyle?: "fullScreen" | "pageSheet" | "formSheet" | "overFullScreen" | undefined;
+}) => null;
+export default StatusBarManager;
diff --git a/dist/components/StatusBarManager.js b/dist/components/StatusBarManager.js
new file mode 100644
index 0000000..7677856
--- /dev/null
+++ b/dist/components/StatusBarManager.js
@@ -0,0 +1,14 @@
+import { useEffect } from "react";
+import { Platform, StatusBar, } from "react-native";
+const StatusBarManager = ({ presentationStyle, }) => {
+ if (Platform.OS === "ios" || presentationStyle !== "overFullScreen") {
+ return null;
+ }
+ //Can't get an actual state of app status bar with default RN. Gonna rely on "presentationStyle === overFullScreen" prop and guess application status bar state to be visible in this case.
+ StatusBar.setHidden(true);
+ useEffect(() => {
+ return () => StatusBar.setHidden(false);
+ }, []);
+ return null;
+};
+export default StatusBarManager;
diff --git a/dist/hooks/useAnimatedComponents.d.ts b/dist/hooks/useAnimatedComponents.d.ts
new file mode 100644
index 0000000..1f31475
--- /dev/null
+++ b/dist/hooks/useAnimatedComponents.d.ts
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { Animated } from "react-native";
+declare const useAnimatedComponents: () => readonly [{
+ [key: string]: Animated.Value;
+}[], {
+ [key: string]: Animated.Value;
+}[], (isVisible: boolean, hideHeaderOnZoom: boolean, hideFooterOnZoom: boolean) => void];
+export default useAnimatedComponents;
diff --git a/dist/hooks/useAnimatedComponents.js b/dist/hooks/useAnimatedComponents.js
new file mode 100644
index 0000000..705445f
--- /dev/null
+++ b/dist/hooks/useAnimatedComponents.js
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { Animated } from "react-native";
+const INITIAL_POSITION = { x: 0, y: 0 };
+const ANIMATION_CONFIG = {
+ duration: 200,
+ useNativeDriver: true,
+};
+const useAnimatedComponents = () => {
+ const headerTranslate = new Animated.ValueXY(INITIAL_POSITION);
+ const footerTranslate = new Animated.ValueXY(INITIAL_POSITION);
+ const toggleVisible = (isVisible, hideHeaderOnZoom, hideFooterOnZoom) => {
+ if (isVisible) {
+ Animated.parallel([
+ Animated.timing(headerTranslate.y, { ...ANIMATION_CONFIG, toValue: 0 }),
+ Animated.timing(footerTranslate.y, { ...ANIMATION_CONFIG, toValue: 0 }),
+ ]).start();
+ }
+ else {
+ const hideHeaderAnimation = hideHeaderOnZoom
+ ? [Animated.timing(headerTranslate.y, {
+ ...ANIMATION_CONFIG,
+ toValue: -300,
+ })]
+ : [];
+ const hideFooterAnimation = hideFooterOnZoom
+ ? [Animated.timing(footerTranslate.y, {
+ ...ANIMATION_CONFIG,
+ toValue: 300,
+ })]
+ : [];
+ Animated.parallel([...hideHeaderAnimation, ...hideFooterAnimation]).start();
+ }
+ };
+ const headerTransform = headerTranslate.getTranslateTransform();
+ const footerTransform = footerTranslate.getTranslateTransform();
+ return [headerTransform, footerTransform, toggleVisible];
+};
+export default useAnimatedComponents;
diff --git a/dist/hooks/useDoubleTapToZoom.d.ts b/dist/hooks/useDoubleTapToZoom.d.ts
new file mode 100644
index 0000000..1df97a3
--- /dev/null
+++ b/dist/hooks/useDoubleTapToZoom.d.ts
@@ -0,0 +1,16 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import React from "react";
+import { ScrollView, NativeTouchEvent, NativeSyntheticEvent } from "react-native";
+import { Dimensions } from "../@types";
+/**
+ * This is iOS only.
+ * Same functionality for Android implemented inside usePanResponder hook.
+ */
+declare function useDoubleTapToZoom(scrollViewRef: React.RefObject, scaled: boolean, screen: Dimensions): (event: NativeSyntheticEvent) => void;
+export default useDoubleTapToZoom;
diff --git a/dist/hooks/useDoubleTapToZoom.js b/dist/hooks/useDoubleTapToZoom.js
new file mode 100644
index 0000000..112e0b2
--- /dev/null
+++ b/dist/hooks/useDoubleTapToZoom.js
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { useCallback } from "react";
+const DOUBLE_TAP_DELAY = 300;
+let lastTapTS = null;
+/**
+ * This is iOS only.
+ * Same functionality for Android implemented inside usePanResponder hook.
+ */
+function useDoubleTapToZoom(scrollViewRef, scaled, screen) {
+ const handleDoubleTap = useCallback((event) => {
+ var _a, _b, _c;
+ const nowTS = new Date().getTime();
+ const scrollResponderRef = (_b = (_a = scrollViewRef) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.getScrollResponder();
+ if (lastTapTS && nowTS - lastTapTS < DOUBLE_TAP_DELAY) {
+ const { pageX, pageY } = event.nativeEvent;
+ let targetX = 0;
+ let targetY = 0;
+ let targetWidth = screen.width;
+ let targetHeight = screen.height;
+ // Zooming in
+ // TODO: Add more precise calculation of targetX, targetY based on touch
+ if (!scaled) {
+ targetX = pageX / 2;
+ targetY = pageY / 2;
+ targetWidth = screen.width / 2;
+ targetHeight = screen.height / 2;
+ }
+ // @ts-ignore
+ (_c = scrollResponderRef) === null || _c === void 0 ? void 0 : _c.scrollResponderZoomTo({
+ x: targetX,
+ y: targetY,
+ width: targetWidth,
+ height: targetHeight,
+ animated: true,
+ });
+ }
+ else {
+ lastTapTS = nowTS;
+ }
+ }, [scaled]);
+ return handleDoubleTap;
+}
+export default useDoubleTapToZoom;
diff --git a/dist/hooks/useImageDimensions.d.ts b/dist/hooks/useImageDimensions.d.ts
new file mode 100644
index 0000000..5613dca
--- /dev/null
+++ b/dist/hooks/useImageDimensions.d.ts
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { Dimensions, ImageSource } from "../@types";
+declare const useImageDimensions: (image: ImageSource) => Dimensions | null;
+export default useImageDimensions;
diff --git a/dist/hooks/useImageDimensions.js b/dist/hooks/useImageDimensions.js
new file mode 100644
index 0000000..d89a3a1
--- /dev/null
+++ b/dist/hooks/useImageDimensions.js
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { useEffect, useState } from "react";
+import { Image } from "react-native";
+import { createCache } from "../utils";
+const CACHE_SIZE = 50;
+const imageDimensionsCache = createCache(CACHE_SIZE);
+const useImageDimensions = (image) => {
+ const [dimensions, setDimensions] = useState(null);
+ const getImageDimensions = (image) => {
+ return new Promise((resolve) => {
+ if (typeof image == "number") {
+ const cacheKey = `${image}`;
+ let imageDimensions = imageDimensionsCache.get(cacheKey);
+ if (!imageDimensions) {
+ const { width, height } = Image.resolveAssetSource(image);
+ imageDimensions = { width, height };
+ imageDimensionsCache.set(cacheKey, imageDimensions);
+ }
+ resolve(imageDimensions);
+ return;
+ }
+ // @ts-ignore
+ if (image.uri) {
+ const source = image;
+ const cacheKey = source.uri;
+ const imageDimensions = imageDimensionsCache.get(cacheKey);
+ if (imageDimensions) {
+ resolve(imageDimensions);
+ }
+ else {
+ // @ts-ignore
+ Image.getSizeWithHeaders(source.uri, source.headers, (width, height) => {
+ imageDimensionsCache.set(cacheKey, { width, height });
+ resolve({ width, height });
+ }, () => {
+ resolve({ width: 0, height: 0 });
+ });
+ }
+ }
+ else {
+ resolve({ width: 0, height: 0 });
+ }
+ });
+ };
+ let isImageUnmounted = false;
+ useEffect(() => {
+ getImageDimensions(image).then((dimensions) => {
+ if (!isImageUnmounted) {
+ setDimensions(dimensions);
+ }
+ });
+ return () => {
+ isImageUnmounted = true;
+ };
+ }, [image]);
+ return dimensions;
+};
+export default useImageDimensions;
diff --git a/dist/hooks/useImageIndexChange.d.ts b/dist/hooks/useImageIndexChange.d.ts
new file mode 100644
index 0000000..badccaf
--- /dev/null
+++ b/dist/hooks/useImageIndexChange.d.ts
@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { NativeSyntheticEvent, NativeScrollEvent } from "react-native";
+import { Dimensions } from "../@types";
+declare const useImageIndexChange: (imageIndex: number, screen: Dimensions) => readonly [number, (event: NativeSyntheticEvent) => void];
+export default useImageIndexChange;
diff --git a/dist/hooks/useImageIndexChange.js b/dist/hooks/useImageIndexChange.js
new file mode 100644
index 0000000..cb5f0f7
--- /dev/null
+++ b/dist/hooks/useImageIndexChange.js
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { useState } from "react";
+const useImageIndexChange = (imageIndex, screen) => {
+ const [currentImageIndex, setImageIndex] = useState(imageIndex);
+ const onScroll = (event) => {
+ const { nativeEvent: { contentOffset: { x: scrollX }, }, } = event;
+ if (screen.width) {
+ const nextIndex = Math.round(scrollX / screen.width);
+ setImageIndex(nextIndex < 0 ? 0 : nextIndex);
+ }
+ };
+ return [currentImageIndex, onScroll];
+};
+export default useImageIndexChange;
diff --git a/dist/hooks/useImagePrefetch.d.ts b/dist/hooks/useImagePrefetch.d.ts
new file mode 100644
index 0000000..7dcef2c
--- /dev/null
+++ b/dist/hooks/useImagePrefetch.d.ts
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { ImageSource } from "../@types";
+declare const useImagePrefetch: (images: ImageSource[]) => void;
+export default useImagePrefetch;
diff --git a/dist/hooks/useImagePrefetch.js b/dist/hooks/useImagePrefetch.js
new file mode 100644
index 0000000..de1375d
--- /dev/null
+++ b/dist/hooks/useImagePrefetch.js
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { useEffect } from "react";
+import { Image } from "react-native";
+const useImagePrefetch = (images) => {
+ useEffect(() => {
+ images.forEach((image) => {
+ //@ts-ignore
+ if (image.uri) {
+ //@ts-ignore
+ return Image.prefetch(image.uri);
+ }
+ });
+ }, []);
+};
+export default useImagePrefetch;
diff --git a/dist/hooks/usePanResponder.d.ts b/dist/hooks/usePanResponder.d.ts
new file mode 100644
index 0000000..a4e41f0
--- /dev/null
+++ b/dist/hooks/usePanResponder.d.ts
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { Animated, GestureResponderHandlers } from "react-native";
+import { Position } from "../@types";
+declare type Props = {
+ initialScale: number;
+ initialTranslate: Position;
+ onZoom: (isZoomed: boolean) => void;
+ doubleTapToZoomEnabled: boolean;
+ onLongPress: () => void;
+ delayLongPress: number;
+};
+declare const usePanResponder: ({ initialScale, initialTranslate, onZoom, doubleTapToZoomEnabled, onLongPress, delayLongPress, }: Props) => readonly [GestureResponderHandlers, Animated.Value, Animated.ValueXY];
+export default usePanResponder;
diff --git a/dist/hooks/usePanResponder.js b/dist/hooks/usePanResponder.js
new file mode 100644
index 0000000..99dcbc7
--- /dev/null
+++ b/dist/hooks/usePanResponder.js
@@ -0,0 +1,273 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { useMemo, useEffect } from "react";
+import { Animated, Dimensions, } from "react-native";
+import { createPanResponder, getDistanceBetweenTouches, getImageTranslate, getImageDimensionsByTranslate, } from "../utils";
+const SCREEN = Dimensions.get("window");
+const SCREEN_WIDTH = SCREEN.width;
+const SCREEN_HEIGHT = SCREEN.height;
+const MIN_DIMENSION = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT);
+const SCALE_MAX = 2;
+const DOUBLE_TAP_DELAY = 300;
+const OUT_BOUND_MULTIPLIER = 0.75;
+const usePanResponder = ({ initialScale, initialTranslate, onZoom, doubleTapToZoomEnabled, onLongPress, delayLongPress, }) => {
+ let numberInitialTouches = 1;
+ let initialTouches = [];
+ let currentScale = initialScale;
+ let currentTranslate = initialTranslate;
+ let tmpScale = 0;
+ let tmpTranslate = null;
+ let isDoubleTapPerformed = false;
+ let lastTapTS = null;
+ let longPressHandlerRef = null;
+ const meaningfulShift = MIN_DIMENSION * 0.01;
+ const scaleValue = new Animated.Value(initialScale);
+ const translateValue = new Animated.ValueXY(initialTranslate);
+ const imageDimensions = getImageDimensionsByTranslate(initialTranslate, SCREEN);
+ const getBounds = (scale) => {
+ const scaledImageDimensions = {
+ width: imageDimensions.width * scale,
+ height: imageDimensions.height * scale,
+ };
+ const translateDelta = getImageTranslate(scaledImageDimensions, SCREEN);
+ const left = initialTranslate.x - translateDelta.x;
+ const right = left - (scaledImageDimensions.width - SCREEN.width);
+ const top = initialTranslate.y - translateDelta.y;
+ const bottom = top - (scaledImageDimensions.height - SCREEN.height);
+ return [top, left, bottom, right];
+ };
+ const getTranslateInBounds = (translate, scale) => {
+ const inBoundTranslate = { x: translate.x, y: translate.y };
+ const [topBound, leftBound, bottomBound, rightBound] = getBounds(scale);
+ if (translate.x > leftBound) {
+ inBoundTranslate.x = leftBound;
+ }
+ else if (translate.x < rightBound) {
+ inBoundTranslate.x = rightBound;
+ }
+ if (translate.y > topBound) {
+ inBoundTranslate.y = topBound;
+ }
+ else if (translate.y < bottomBound) {
+ inBoundTranslate.y = bottomBound;
+ }
+ return inBoundTranslate;
+ };
+ const fitsScreenByWidth = () => imageDimensions.width * currentScale < SCREEN_WIDTH;
+ const fitsScreenByHeight = () => imageDimensions.height * currentScale < SCREEN_HEIGHT;
+ useEffect(() => {
+ scaleValue.addListener(({ value }) => {
+ if (typeof onZoom === "function") {
+ onZoom(value !== initialScale);
+ }
+ });
+ return () => scaleValue.removeAllListeners();
+ });
+ const cancelLongPressHandle = () => {
+ longPressHandlerRef && clearTimeout(longPressHandlerRef);
+ };
+ const handlers = {
+ onGrant: (_, gestureState) => {
+ numberInitialTouches = gestureState.numberActiveTouches;
+ if (gestureState.numberActiveTouches > 1)
+ return;
+ longPressHandlerRef = setTimeout(onLongPress, delayLongPress);
+ },
+ onStart: (event, gestureState) => {
+ initialTouches = event.nativeEvent.touches;
+ numberInitialTouches = gestureState.numberActiveTouches;
+ if (gestureState.numberActiveTouches > 1)
+ return;
+ const tapTS = Date.now();
+ // Handle double tap event by calculating diff between first and second taps timestamps
+ isDoubleTapPerformed = Boolean(lastTapTS && tapTS - lastTapTS < DOUBLE_TAP_DELAY);
+ if (doubleTapToZoomEnabled && isDoubleTapPerformed) {
+ const isScaled = currentTranslate.x !== initialTranslate.x; // currentScale !== initialScale;
+ const { pageX: touchX, pageY: touchY } = event.nativeEvent.touches[0];
+ const targetScale = SCALE_MAX;
+ const nextScale = isScaled ? initialScale : targetScale;
+ const nextTranslate = isScaled
+ ? initialTranslate
+ : getTranslateInBounds({
+ x: initialTranslate.x +
+ (SCREEN_WIDTH / 2 - touchX) * (targetScale / currentScale),
+ y: initialTranslate.y +
+ (SCREEN_HEIGHT / 2 - touchY) * (targetScale / currentScale),
+ }, targetScale);
+ onZoom(!isScaled);
+ Animated.parallel([
+ Animated.timing(translateValue.x, {
+ toValue: nextTranslate.x,
+ duration: 300,
+ useNativeDriver: true,
+ }),
+ Animated.timing(translateValue.y, {
+ toValue: nextTranslate.y,
+ duration: 300,
+ useNativeDriver: true,
+ }),
+ Animated.timing(scaleValue, {
+ toValue: nextScale,
+ duration: 300,
+ useNativeDriver: true,
+ }),
+ ], { stopTogether: false }).start(() => {
+ currentScale = nextScale;
+ currentTranslate = nextTranslate;
+ });
+ lastTapTS = null;
+ }
+ else {
+ lastTapTS = Date.now();
+ }
+ },
+ onMove: (event, gestureState) => {
+ const { dx, dy } = gestureState;
+ if (Math.abs(dx) >= meaningfulShift || Math.abs(dy) >= meaningfulShift) {
+ cancelLongPressHandle();
+ }
+ // Don't need to handle move because double tap in progress (was handled in onStart)
+ if (doubleTapToZoomEnabled && isDoubleTapPerformed) {
+ cancelLongPressHandle();
+ return;
+ }
+ if (numberInitialTouches === 1 &&
+ gestureState.numberActiveTouches === 2) {
+ numberInitialTouches = 2;
+ initialTouches = event.nativeEvent.touches;
+ }
+ const isTapGesture = numberInitialTouches == 1 && gestureState.numberActiveTouches === 1;
+ const isPinchGesture = numberInitialTouches === 2 && gestureState.numberActiveTouches === 2;
+ if (isPinchGesture) {
+ cancelLongPressHandle();
+ const initialDistance = getDistanceBetweenTouches(initialTouches);
+ const currentDistance = getDistanceBetweenTouches(event.nativeEvent.touches);
+ let nextScale = (currentDistance / initialDistance) * currentScale;
+ /**
+ * In case image is scaling smaller than initial size ->
+ * slow down this transition by applying OUT_BOUND_MULTIPLIER
+ */
+ if (nextScale < initialScale) {
+ nextScale =
+ nextScale + (initialScale - nextScale) * OUT_BOUND_MULTIPLIER;
+ }
+ /**
+ * In case image is scaling down -> move it in direction of initial position
+ */
+ if (currentScale > initialScale && currentScale > nextScale) {
+ const k = (currentScale - initialScale) / (currentScale - nextScale);
+ const nextTranslateX = nextScale < initialScale
+ ? initialTranslate.x
+ : currentTranslate.x -
+ (currentTranslate.x - initialTranslate.x) / k;
+ const nextTranslateY = nextScale < initialScale
+ ? initialTranslate.y
+ : currentTranslate.y -
+ (currentTranslate.y - initialTranslate.y) / k;
+ translateValue.x.setValue(nextTranslateX);
+ translateValue.y.setValue(nextTranslateY);
+ tmpTranslate = { x: nextTranslateX, y: nextTranslateY };
+ }
+ scaleValue.setValue(nextScale);
+ tmpScale = nextScale;
+ }
+ if (isTapGesture && currentScale > initialScale) {
+ const { x, y } = currentTranslate;
+ const { dx, dy } = gestureState;
+ const [topBound, leftBound, bottomBound, rightBound] = getBounds(currentScale);
+ let nextTranslateX = x + dx;
+ let nextTranslateY = y + dy;
+ if (nextTranslateX > leftBound) {
+ nextTranslateX =
+ nextTranslateX -
+ (nextTranslateX - leftBound) * OUT_BOUND_MULTIPLIER;
+ }
+ if (nextTranslateX < rightBound) {
+ nextTranslateX =
+ nextTranslateX -
+ (nextTranslateX - rightBound) * OUT_BOUND_MULTIPLIER;
+ }
+ if (nextTranslateY > topBound) {
+ nextTranslateY =
+ nextTranslateY - (nextTranslateY - topBound) * OUT_BOUND_MULTIPLIER;
+ }
+ if (nextTranslateY < bottomBound) {
+ nextTranslateY =
+ nextTranslateY -
+ (nextTranslateY - bottomBound) * OUT_BOUND_MULTIPLIER;
+ }
+ if (fitsScreenByWidth()) {
+ nextTranslateX = x;
+ }
+ if (fitsScreenByHeight()) {
+ nextTranslateY = y;
+ }
+ translateValue.x.setValue(nextTranslateX);
+ translateValue.y.setValue(nextTranslateY);
+ tmpTranslate = { x: nextTranslateX, y: nextTranslateY };
+ }
+ },
+ onRelease: () => {
+ cancelLongPressHandle();
+ if (isDoubleTapPerformed) {
+ isDoubleTapPerformed = false;
+ }
+ if (tmpScale > 0) {
+ if (tmpScale < initialScale || tmpScale > SCALE_MAX) {
+ tmpScale = tmpScale < initialScale ? initialScale : SCALE_MAX;
+ Animated.timing(scaleValue, {
+ toValue: tmpScale,
+ duration: 100,
+ useNativeDriver: true,
+ }).start();
+ }
+ currentScale = tmpScale;
+ tmpScale = 0;
+ }
+ if (tmpTranslate) {
+ const { x, y } = tmpTranslate;
+ const [topBound, leftBound, bottomBound, rightBound] = getBounds(currentScale);
+ let nextTranslateX = x;
+ let nextTranslateY = y;
+ if (!fitsScreenByWidth()) {
+ if (nextTranslateX > leftBound) {
+ nextTranslateX = leftBound;
+ }
+ else if (nextTranslateX < rightBound) {
+ nextTranslateX = rightBound;
+ }
+ }
+ if (!fitsScreenByHeight()) {
+ if (nextTranslateY > topBound) {
+ nextTranslateY = topBound;
+ }
+ else if (nextTranslateY < bottomBound) {
+ nextTranslateY = bottomBound;
+ }
+ }
+ Animated.parallel([
+ Animated.timing(translateValue.x, {
+ toValue: nextTranslateX,
+ duration: 100,
+ useNativeDriver: true,
+ }),
+ Animated.timing(translateValue.y, {
+ toValue: nextTranslateY,
+ duration: 100,
+ useNativeDriver: true,
+ }),
+ ]).start();
+ currentTranslate = { x: nextTranslateX, y: nextTranslateY };
+ tmpTranslate = null;
+ }
+ },
+ };
+ const panResponder = useMemo(() => createPanResponder(handlers), [handlers]);
+ return [panResponder.panHandlers, scaleValue, translateValue];
+};
+export default usePanResponder;
diff --git a/dist/hooks/useRequestClose.d.ts b/dist/hooks/useRequestClose.d.ts
new file mode 100644
index 0000000..d747f58
--- /dev/null
+++ b/dist/hooks/useRequestClose.d.ts
@@ -0,0 +1,9 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+declare const useRequestClose: (onRequestClose: () => void) => readonly [number, () => void];
+export default useRequestClose;
diff --git a/dist/hooks/useRequestClose.js b/dist/hooks/useRequestClose.js
new file mode 100644
index 0000000..0fe2d86
--- /dev/null
+++ b/dist/hooks/useRequestClose.js
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { useState } from "react";
+const useRequestClose = (onRequestClose) => {
+ const [opacity, setOpacity] = useState(1);
+ return [
+ opacity,
+ () => {
+ setOpacity(0);
+ onRequestClose();
+ setTimeout(() => setOpacity(1), 0);
+ },
+ ];
+};
+export default useRequestClose;
diff --git a/dist/index.d.ts b/dist/index.d.ts
new file mode 100644
index 0000000..6e58101
--- /dev/null
+++ b/dist/index.d.ts
@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+export { default } from "./ImageViewing";
diff --git a/dist/index.js b/dist/index.js
new file mode 100644
index 0000000..6e58101
--- /dev/null
+++ b/dist/index.js
@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+export { default } from "./ImageViewing";
diff --git a/dist/utils.d.ts b/dist/utils.d.ts
new file mode 100644
index 0000000..edeea10
--- /dev/null
+++ b/dist/utils.d.ts
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { Animated, GestureResponderEvent, PanResponderGestureState, PanResponderInstance, NativeTouchEvent } from "react-native";
+import { Dimensions, Position } from "./@types";
+declare type CacheStorageItem = {
+ key: string;
+ value: any;
+};
+export declare const createCache: (cacheSize: number) => {
+ _storage: CacheStorageItem[];
+ get(key: string): any;
+ set(key: string, value: any): void;
+};
+export declare const splitArrayIntoBatches: (arr: any[], batchSize: number) => any[];
+export declare const getImageTransform: (image: Dimensions | null, screen: Dimensions) => readonly [] | readonly [{
+ readonly x: number;
+ readonly y: number;
+}, number];
+export declare const getImageStyles: (image: Dimensions | null, translate: Animated.ValueXY, scale?: Animated.Value | undefined) => {
+ width: number;
+ height: number;
+ transform?: undefined;
+} | {
+ width: number;
+ height: number;
+ transform: {
+ [key: string]: Animated.Value;
+ }[];
+};
+export declare const getImageTranslate: (image: Dimensions, screen: Dimensions) => Position;
+export declare const getImageDimensionsByTranslate: (translate: Position, screen: Dimensions) => Dimensions;
+export declare const getImageTranslateForScale: (currentTranslate: Position, targetScale: number, screen: Dimensions) => Position;
+declare type HandlerType = (event: GestureResponderEvent, state: PanResponderGestureState) => void;
+declare type PanResponderProps = {
+ onGrant: HandlerType;
+ onStart?: HandlerType;
+ onMove: HandlerType;
+ onRelease?: HandlerType;
+ onTerminate?: HandlerType;
+};
+export declare const createPanResponder: ({ onGrant, onStart, onMove, onRelease, onTerminate, }: PanResponderProps) => PanResponderInstance;
+export declare const getDistanceBetweenTouches: (touches: NativeTouchEvent[]) => number;
+export {};
diff --git a/dist/utils.js b/dist/utils.js
new file mode 100644
index 0000000..69aae88
--- /dev/null
+++ b/dist/utils.js
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) JOB TODAY S.A. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+import { Animated, PanResponder, } from "react-native";
+export const createCache = (cacheSize) => ({
+ _storage: [],
+ get(key) {
+ const { value } = this._storage.find(({ key: storageKey }) => storageKey === key) || {};
+ return value;
+ },
+ set(key, value) {
+ if (this._storage.length >= cacheSize) {
+ this._storage.shift();
+ }
+ this._storage.push({ key, value });
+ },
+});
+export const splitArrayIntoBatches = (arr, batchSize) => arr.reduce((result, item) => {
+ const batch = result.pop() || [];
+ if (batch.length < batchSize) {
+ batch.push(item);
+ result.push(batch);
+ }
+ else {
+ result.push(batch, [item]);
+ }
+ return result;
+}, []);
+export const getImageTransform = (image, screen) => {
+ var _a, _b;
+ if (!((_a = image) === null || _a === void 0 ? void 0 : _a.width) || !((_b = image) === null || _b === void 0 ? void 0 : _b.height)) {
+ return [];
+ }
+ const wScale = screen.width / image.width;
+ const hScale = screen.height / image.height;
+ const scale = Math.min(wScale, hScale);
+ const { x, y } = getImageTranslate(image, screen);
+ return [{ x, y }, scale];
+};
+export const getImageStyles = (image, translate, scale) => {
+ var _a, _b;
+ if (!((_a = image) === null || _a === void 0 ? void 0 : _a.width) || !((_b = image) === null || _b === void 0 ? void 0 : _b.height)) {
+ return { width: 0, height: 0 };
+ }
+ const transform = translate.getTranslateTransform();
+ if (scale) {
+ transform.push({ scale }, { perspective: new Animated.Value(1000) });
+ }
+ return {
+ width: image.width,
+ height: image.height,
+ transform,
+ };
+};
+export const getImageTranslate = (image, screen) => {
+ const getTranslateForAxis = (axis) => {
+ const imageSize = axis === "x" ? image.width : image.height;
+ const screenSize = axis === "x" ? screen.width : screen.height;
+ return (screenSize - imageSize) / 2;
+ };
+ return {
+ x: getTranslateForAxis("x"),
+ y: getTranslateForAxis("y"),
+ };
+};
+export const getImageDimensionsByTranslate = (translate, screen) => ({
+ width: screen.width - translate.x * 2,
+ height: screen.height - translate.y * 2,
+});
+export const getImageTranslateForScale = (currentTranslate, targetScale, screen) => {
+ const { width, height } = getImageDimensionsByTranslate(currentTranslate, screen);
+ const targetImageDimensions = {
+ width: width * targetScale,
+ height: height * targetScale,
+ };
+ return getImageTranslate(targetImageDimensions, screen);
+};
+export const createPanResponder = ({ onGrant, onStart, onMove, onRelease, onTerminate, }) => PanResponder.create({
+ onStartShouldSetPanResponder: () => true,
+ onStartShouldSetPanResponderCapture: () => true,
+ onMoveShouldSetPanResponder: () => true,
+ onMoveShouldSetPanResponderCapture: () => true,
+ onPanResponderGrant: onGrant,
+ onPanResponderStart: onStart,
+ onPanResponderMove: onMove,
+ onPanResponderRelease: onRelease,
+ onPanResponderTerminate: onTerminate,
+ onPanResponderTerminationRequest: () => false,
+ onShouldBlockNativeResponder: () => false,
+});
+export const getDistanceBetweenTouches = (touches) => {
+ const [a, b] = touches;
+ if (a == null || b == null) {
+ return 0;
+ }
+ return Math.sqrt(Math.pow(a.pageX - b.pageX, 2) + Math.pow(a.pageY - b.pageY, 2));
+};