diff --git a/src/components/ImageView/index.native.js b/src/components/ImageView/index.native.js
deleted file mode 100644
index 98349b213aa5..000000000000
--- a/src/components/ImageView/index.native.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import Lightbox from '@components/Lightbox';
-import {zoomRangeDefaultProps, zoomRangePropTypes} from '@components/MultiGestureCanvas/propTypes';
-import {imageViewDefaultProps, imageViewPropTypes} from './propTypes';
-
-/**
- * On the native layer, we use a image library to handle zoom functionality
- */
-const propTypes = {
- ...imageViewPropTypes,
- ...zoomRangePropTypes,
-
- /** Function for handle on press */
- onPress: PropTypes.func,
-
- /** Additional styles to add to the component */
- style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
-};
-
-const defaultProps = {
- ...imageViewDefaultProps,
- ...zoomRangeDefaultProps,
-
- onPress: () => {},
- style: {},
-};
-
-function ImageView({isAuthTokenRequired, url, onScaleChanged, onPress, style, zoomRange, onError, isUsedInCarousel, isSingleCarouselItem, carouselItemIndex, carouselActiveItemIndex}) {
- const hasSiblingCarouselItems = isUsedInCarousel && !isSingleCarouselItem;
-
- return (
-
- );
-}
-
-ImageView.propTypes = propTypes;
-ImageView.defaultProps = defaultProps;
-ImageView.displayName = 'ImageView';
-
-export default ImageView;
diff --git a/src/components/ImageView/index.native.tsx b/src/components/ImageView/index.native.tsx
new file mode 100644
index 000000000000..e36bb39d2bed
--- /dev/null
+++ b/src/components/ImageView/index.native.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import Lightbox from '@components/Lightbox';
+import {zoomRangeDefaultProps} from '@components/MultiGestureCanvas/propTypes';
+import type {ImageViewProps} from './types';
+
+function ImageView({
+ isAuthTokenRequired = false,
+ url,
+ onScaleChanged,
+ onPress,
+ style,
+ zoomRange = zoomRangeDefaultProps.zoomRange,
+ onError,
+ isUsedInCarousel = false,
+ isSingleCarouselItem = false,
+ carouselItemIndex = 0,
+ carouselActiveItemIndex = 0,
+}: ImageViewProps) {
+ const hasSiblingCarouselItems = isUsedInCarousel && !isSingleCarouselItem;
+
+ return (
+
+ );
+}
+
+ImageView.displayName = 'ImageView';
+
+export default ImageView;
diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.tsx
similarity index 79%
rename from src/components/ImageView/index.js
rename to src/components/ImageView/index.tsx
index f16b37f328f5..ec37abf6d275 100644
--- a/src/components/ImageView/index.js
+++ b/src/components/ImageView/index.tsx
@@ -1,15 +1,21 @@
+import type {SyntheticEvent} from 'react';
import React, {useCallback, useEffect, useRef, useState} from 'react';
+import type {GestureResponderEvent, LayoutChangeEvent, NativeSyntheticEvent} from 'react-native';
import {View} from 'react-native';
import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import Image from '@components/Image';
+import RESIZE_MODES from '@components/Image/resizeModes';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import CONST from '@src/CONST';
-import {imageViewDefaultProps, imageViewPropTypes} from './propTypes';
+import viewRef from '@src/types/utils/viewRef';
+import type {ImageLoadNativeEventData, ImageViewProps} from './types';
-function ImageView({isAuthTokenRequired, url, fileName, onError}) {
+type ZoomDelta = {offsetX: number; offsetY: number};
+
+function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageViewProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const [isLoading, setIsLoading] = useState(true);
@@ -25,18 +31,12 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
const [imgWidth, setImgWidth] = useState(0);
const [imgHeight, setImgHeight] = useState(0);
const [zoomScale, setZoomScale] = useState(0);
- const [zoomDelta, setZoomDelta] = useState({offsetX: 0, offsetY: 0});
+ const [zoomDelta, setZoomDelta] = useState();
- const scrollableRef = useRef(null);
+ const scrollableRef = useRef(null);
const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen();
- /**
- * @param {Number} newContainerWidth
- * @param {Number} newContainerHeight
- * @param {Number} newImageWidth
- * @param {Number} newImageHeight
- */
- const setScale = (newContainerWidth, newContainerHeight, newImageWidth, newImageHeight) => {
+ const setScale = (newContainerWidth: number, newContainerHeight: number, newImageWidth: number, newImageHeight: number) => {
if (!newContainerWidth || !newImageWidth || !newContainerHeight || !newImageHeight) {
return;
}
@@ -44,10 +44,7 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
setZoomScale(newZoomScale);
};
- /**
- * @param {SyntheticEvent} e
- */
- const onContainerLayoutChanged = (e) => {
+ const onContainerLayoutChanged = (e: LayoutChangeEvent) => {
const {width, height} = e.nativeEvent.layout;
setScale(width, height, imgWidth, imgHeight);
@@ -57,10 +54,8 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
/**
* When open image, set image width, height.
- * @param {Number} imageWidth
- * @param {Number} imageHeight
*/
- const setImageRegion = (imageWidth, imageHeight) => {
+ const setImageRegion = (imageWidth: number, imageHeight: number) => {
if (imageHeight <= 0) {
return;
}
@@ -78,32 +73,29 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
setIsZoomed(false);
};
- const imageLoad = ({nativeEvent}) => {
+ const imageLoad = ({nativeEvent}: NativeSyntheticEvent) => {
setImageRegion(nativeEvent.width, nativeEvent.height);
setIsLoading(false);
};
- /**
- * @param {SyntheticEvent} e
- */
- const onContainerPressIn = (e) => {
+ const onContainerPressIn = (e: GestureResponderEvent) => {
const {pageX, pageY} = e.nativeEvent;
setIsMouseDown(true);
setInitialX(pageX);
setInitialY(pageY);
- setInitialScrollLeft(scrollableRef.current.scrollLeft);
- setInitialScrollTop(scrollableRef.current.scrollTop);
+ setInitialScrollLeft(scrollableRef.current?.scrollLeft ?? 0);
+ setInitialScrollTop(scrollableRef.current?.scrollTop ?? 0);
};
/**
* Convert touch point to zoomed point
- * @param {Boolean} x x point when click zoom
- * @param {Boolean} y y point when click zoom
- * @returns {Object} converted touch point
+ * @param x point when click zoom
+ * @param y point when click zoom
+ * @returns converted touch point
*/
- const getScrollOffset = (x, y) => {
- let offsetX;
- let offsetY;
+ const getScrollOffset = (x: number, y: number) => {
+ let offsetX = 0;
+ let offsetY = 0;
// Container size bigger than clicked position offset
if (x <= containerWidth / 2) {
@@ -121,12 +113,9 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
return {offsetX, offsetY};
};
- /**
- * @param {SyntheticEvent} e
- */
- const onContainerPress = (e) => {
+ const onContainerPress = (e?: GestureResponderEvent | KeyboardEvent | SyntheticEvent) => {
if (!isZoomed && !isDragging) {
- if (e.nativeEvent) {
+ if (e && 'nativeEvent' in e && e.nativeEvent instanceof PointerEvent) {
const {offsetX, offsetY} = e.nativeEvent;
// Dividing clicked positions by the zoom scale to get coordinates
@@ -148,13 +137,10 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
}
};
- /**
- * @param {SyntheticEvent} e
- */
const trackPointerPosition = useCallback(
- (e) => {
+ (event: MouseEvent) => {
// Whether the pointer is released inside the ImageView
- const isInsideImageView = scrollableRef.current.contains(e.nativeEvent.target);
+ const isInsideImageView = scrollableRef.current?.contains(event.target as Node);
if (!isInsideImageView && isZoomed && isDragging && isMouseDown) {
setIsDragging(false);
@@ -165,14 +151,14 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
);
const trackMovement = useCallback(
- (e) => {
+ (event: MouseEvent) => {
if (!isZoomed) {
return;
}
- if (isDragging && isMouseDown) {
- const x = e.nativeEvent.x;
- const y = e.nativeEvent.y;
+ if (isDragging && isMouseDown && scrollableRef.current) {
+ const x = event.x;
+ const y = event.y;
const moveX = initialX - x;
const moveY = initialY - y;
scrollableRef.current.scrollLeft = initialScrollLeft + moveX;
@@ -218,7 +204,7 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
style={isLoading || zoomScale === 0 ? undefined : [styles.w100, styles.h100]}
// When Image dimensions are lower than the container boundary(zoomscale <= 1), use `contain` to render the image with natural dimensions.
// Both `center` and `contain` keeps the image centered on both x and y axis.
- resizeMode={zoomScale > 1 ? Image.resizeMode.center : Image.resizeMode.contain}
+ resizeMode={zoomScale > 1 ? RESIZE_MODES.center : RESIZE_MODES.contain}
onLoadStart={imageLoadingStart}
onLoad={imageLoad}
onError={onError}
@@ -229,7 +215,7 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
}
return (
@@ -249,7 +235,7 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
source={{uri: url}}
isAuthTokenRequired={isAuthTokenRequired}
style={[styles.h100, styles.w100]}
- resizeMode={Image.resizeMode.contain}
+ resizeMode={RESIZE_MODES.contain}
onLoadStart={imageLoadingStart}
onLoad={imageLoad}
onError={onError}
@@ -261,8 +247,6 @@ function ImageView({isAuthTokenRequired, url, fileName, onError}) {
);
}
-ImageView.propTypes = imageViewPropTypes;
-ImageView.defaultProps = imageViewDefaultProps;
ImageView.displayName = 'ImageView';
export default ImageView;
diff --git a/src/components/ImageView/propTypes.js b/src/components/ImageView/propTypes.js
deleted file mode 100644
index 3809d9aed043..000000000000
--- a/src/components/ImageView/propTypes.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import PropTypes from 'prop-types';
-
-const imageViewPropTypes = {
- /** Whether source url requires authentication */
- isAuthTokenRequired: PropTypes.bool,
-
- /** Handles scale changed event in image zoom component. Used on native only */
- // eslint-disable-next-line react/no-unused-prop-types
- onScaleChanged: PropTypes.func.isRequired,
-
- /** URL to full-sized image */
- url: PropTypes.string.isRequired,
-
- /** image file name */
- fileName: PropTypes.string.isRequired,
-
- /** Handles errors while displaying the image */
- onError: PropTypes.func,
-
- /** Whether this view is the active screen */
- isFocused: PropTypes.bool,
-
- /** Whether this AttachmentView is shown as part of a AttachmentCarousel */
- isUsedInCarousel: PropTypes.bool,
-
- /** When "isUsedInCarousel" is set to true, determines whether there is only one item in the carousel */
- isSingleCarouselItem: PropTypes.bool,
-
- /** The index of the carousel item */
- carouselItemIndex: PropTypes.number,
-
- /** The index of the currently active carousel item */
- carouselActiveItemIndex: PropTypes.number,
-};
-
-const imageViewDefaultProps = {
- isAuthTokenRequired: false,
- onError: () => {},
- isFocused: true,
- isUsedInCarousel: false,
- isSingleCarouselItem: false,
- carouselItemIndex: 0,
- carouselActiveItemIndex: 0,
-};
-
-export {imageViewPropTypes, imageViewDefaultProps};
diff --git a/src/components/ImageView/types.ts b/src/components/ImageView/types.ts
new file mode 100644
index 000000000000..bf83bc44d47b
--- /dev/null
+++ b/src/components/ImageView/types.ts
@@ -0,0 +1,47 @@
+import type {StyleProp, ViewStyle} from 'react-native';
+import type ZoomRange from '@components/MultiGestureCanvas/types';
+
+type ImageViewProps = {
+ /** Whether source url requires authentication */
+ isAuthTokenRequired?: boolean;
+
+ /** Handles scale changed event in image zoom component. Used on native only */
+ onScaleChanged: (scale: number) => void;
+
+ /** URL to full-sized image */
+ url: string;
+
+ /** image file name */
+ fileName: string;
+
+ /** Handles errors while displaying the image */
+ onError?: () => void;
+
+ /** Whether this AttachmentView is shown as part of a AttachmentCarousel */
+ isUsedInCarousel?: boolean;
+
+ /** When "isUsedInCarousel" is set to true, determines whether there is only one item in the carousel */
+ isSingleCarouselItem?: boolean;
+
+ /** The index of the carousel item */
+ carouselItemIndex?: number;
+
+ /** The index of the currently active carousel item */
+ carouselActiveItemIndex?: number;
+
+ /** Function for handle on press */
+ onPress?: () => void;
+
+ /** Additional styles to add to the component */
+ style?: StyleProp;
+
+ /** Range of zoom that can be applied to the content by pinching or double tapping. */
+ zoomRange?: ZoomRange;
+};
+
+type ImageLoadNativeEventData = {
+ width: number;
+ height: number;
+};
+
+export type {ImageViewProps, ImageLoadNativeEventData};
diff --git a/src/components/Lightbox.js b/src/components/Lightbox.js
index 45326edb4610..8b7d68befafd 100644
--- a/src/components/Lightbox.js
+++ b/src/components/Lightbox.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native';
import useStyleUtils from '@hooks/useStyleUtils';
+import stylePropTypes from '@styles/stylePropTypes';
import * as AttachmentsPropTypes from './Attachments/propTypes';
import Image from './Image';
import MultiGestureCanvas from './MultiGestureCanvas';
@@ -44,7 +45,7 @@ const propTypes = {
activeIndex: PropTypes.number,
/** Additional styles to add to the component */
- style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
+ style: stylePropTypes,
};
const defaultProps = {
diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js
index 590154b48bca..d967d04ab94b 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -758,7 +758,7 @@ function MoneyRequestConfirmationList(props) {
{shouldShowTags && (
{
diff --git a/src/components/MultiGestureCanvas/types.ts b/src/components/MultiGestureCanvas/types.ts
new file mode 100644
index 000000000000..0242f045feef
--- /dev/null
+++ b/src/components/MultiGestureCanvas/types.ts
@@ -0,0 +1,6 @@
+type ZoomRange = {
+ min: number;
+ max: number;
+};
+
+export default ZoomRange;
diff --git a/src/styles/stylePropTypes.js b/src/styles/stylePropTypes.js
index f9ecdb98ff13..b82db94140ee 100644
--- a/src/styles/stylePropTypes.js
+++ b/src/styles/stylePropTypes.js
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
-const stylePropTypes = PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object), PropTypes.func]);
+const stylePropTypes = PropTypes.oneOfType([PropTypes.object, PropTypes.bool, PropTypes.arrayOf(PropTypes.object), PropTypes.func]);
export default stylePropTypes;