├── .babelrc ├── .buckconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .prettierrc.js ├── .watchmanconfig ├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json ├── src ├── Cropper │ ├── Cropper.page.tsx │ ├── Cropper.style.ts │ └── Cropper.tsx ├── Main.tsx ├── common │ ├── DefaultFooter.tsx │ └── index.ts ├── constants │ └── index.ts └── utils │ ├── cropper.ts │ └── index.ts ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["module:metro-react-native-babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore the 'dist' folder 2 | dist/* 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native-community', 4 | parser: '@typescript-eslint/parser', 5 | plugins: ['@typescript-eslint'], 6 | rules: { 7 | "prefer-template": 0, 8 | "max-len": 0, 9 | "quote-props": 0, 10 | "no-return-assign": 0, 11 | "global-require": 0, 12 | "no-underscore-dangle": 0, 13 | "react/sort-comp": 0, 14 | "no-useless-concat": 0, 15 | "arrow-body-style": 0, 16 | "object-shorthand": 0, 17 | "no-mixed-operators": 0, 18 | "no-undef-init": 0, 19 | "camelcase": 0, 20 | "jsx-quotes": 0, 21 | "react-native/no-inline-styles": 0 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | */fastlane/report.xml 52 | */fastlane/Preview.html 53 | */fastlane/screenshots 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # Distribution folder 59 | dist/ 60 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: true, 3 | jsxBracketSameLine: false, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | jsxSingleQuote: true, 7 | printWidth: 150, 8 | }; 9 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ggunti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-amazing-cropper 2 | 3 | Image cropper for react native made with Animated API (with rotation possibility) - **for iOS & android** 4 | 5 |

It is written in typescript!

6 | 7 |      8 |
9 |      10 |
11 | 12 |
13 | 14 |      15 |
16 |      17 | 18 | This component depends on `@bam.tech/react-native-image-resizer` and `@react-native-community/image-editor` libraries. They need to be installed and linked to your project before. 19 | 20 | **STEPS TO INSTALL:**
1.\* `npm install --save @bam.tech/react-native-image-resizer @react-native-community/image-editor`
2. `react-native link @bam.tech/react-native-image-resizer @react-native-community/image-editor`
3.\* `npm install --save react-native-amazing-cropper`
21 | 22 | Step 2 is not needed for react-native >= 0.60 because of autolinking. Instead just run `pod install` inside `ios` directory. 23 | 24 | #### Properties 25 | 26 | --- 27 | 28 | | Prop | Type | Description | 29 | | :------------------------ | :---------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 30 | | onDone | `function` | A function which accepts 2 arguments: `croppedImageUri` and `garbageUris`. `garbageUris` is an array of string (it has 1 or 2 uri) and should be ignored. It is returned only to give you the possibility to clear the garbage uris from cache (ex. using `unlink` method from [react-native-fs](https://github.com/itinance/react-native-fs); see [this issue](https://github.com/ggunti/react-native-amazing-cropper/issues/36)). Called when user press the 'DONE' button | 31 | | onError | `function` | A function which accepts 1 argument `err` of type `Error`. Called when rotation or cropping fails | 32 | | onCancel | `function` | A function without arguments. Called when user press the 'CANCEL' button | 33 | | imageUri | `string` | The uri of the image you want to crop or rotate | 34 | | imageWidth | `number` | The width (in pixels) of the image you passed in `imageUri` | 35 | | imageHeight | `number` | The height (in pixels) of the image you passed in `imageUri` | 36 | | initialRotation | `number` | Number which set the default rotation of the image when cropper is initialized.
Default is `0` | 37 | | footerComponent | `component` | Custom component for footer. Default is ` ` | 38 | | NOT_SELECTED_AREA_OPACITY | `number` | The opacity of the area which is not selected by the cropper. Should be a value between `0` and `1`. Default is `0.5` | 39 | | BORDER_WIDTH | `number` | The border width [(see image)](https://i.imgur.com/CMSS953.png). Default is `50` | 40 | | COMPONENT_WIDTH | `number` | The width of the entire component. Default is `Dimensions.get('window').width`, not recommended to change. | 41 | | COMPONENT_HEIGHT | `number` | The height of the entire component. Default is `Dimensions.get('window').height`, you should change it to fix [hidden footer issue](https://github.com/ggunti/react-native-amazing-cropper/issues/30). | 42 | 43 | #### Usage example 1 (using the default footer) 44 | 45 | --- 46 | 47 | ```javascript 48 | import React, { Component } from 'react'; 49 | import AmazingCropper from 'react-native-amazing-cropper'; 50 | 51 | class AmazingCropperPage extends Component { 52 | onDone = (croppedImageUri, garbageUris) => { 53 | console.log('croppedImageUri = ', croppedImageUri); 54 | // clear the garbage uris from cache 55 | // send image to server for example 56 | }; 57 | 58 | onError = (err) => { 59 | console.log(err); 60 | }; 61 | 62 | onCancel = () => { 63 | console.log('Cancel button was pressed'); 64 | // navigate back 65 | }; 66 | 67 | render() { 68 | return ( 69 | 79 | ); 80 | } 81 | } 82 | ``` 83 | 84 | #### Usage example 2 (using the default footer with custom text) 85 | 86 | --- 87 | 88 | ```javascript 89 | import React, { Component } from 'react'; 90 | import AmazingCropper, { DefaultFooter } from 'react-native-amazing-cropper'; 91 | 92 | class AmazingCropperPage extends Component { 93 | onDone = (croppedImageUri, garbageUris) => { 94 | console.log('croppedImageUri = ', croppedImageUri); 95 | // clear the garbage uris from cache 96 | // send image to server for example 97 | }; 98 | 99 | onError = (err) => { 100 | console.log(err); 101 | }; 102 | 103 | onCancel = () => { 104 | console.log('Cancel button was pressed'); 105 | // navigate back 106 | }; 107 | 108 | render() { 109 | return ( 110 | } 113 | onDone={this.onDone} 114 | onError={this.onError} 115 | onCancel={this.onCancel} 116 | imageUri='https://www.lifeofpix.com/wp-content/uploads/2018/09/manhattan_-11-1600x2396.jpg' 117 | imageWidth={1600} 118 | imageHeight={2396} 119 | /> 120 | ); 121 | } 122 | } 123 | ``` 124 | 125 | #### Usage example 3 (using own fully customized footer) 126 | 127 | --- 128 | 129 | Write your custom footer component.
130 | Don't forget to call the **props.onDone**, **props.onRotate** and **props.onCancel** methods inside it (the Cropper will pass them automatically to your footer component). Example of custom footer component: 131 | 132 | ```javascript 133 | import React from 'react'; 134 | import { View, TouchableOpacity, Text, Platform, StyleSheet } from 'react-native'; 135 | import PropTypes from 'prop-types'; 136 | import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons'; 137 | 138 | const CustomCropperFooter = (props) => ( 139 | 140 | 141 | CANCEL 142 | 143 | 144 | 145 | 146 | 147 | DONE 148 | 149 | 150 | ); 151 | 152 | export default CustomCropperFooter; 153 | 154 | CustomCropperFooter.propTypes = { 155 | onDone: PropTypes.func, 156 | onRotate: PropTypes.func, 157 | onCancel: PropTypes.func, 158 | }; 159 | 160 | const styles = StyleSheet.create({ 161 | buttonsContainer: { 162 | flexDirection: 'row', 163 | alignItems: 'center', // 'flex-start' 164 | justifyContent: 'space-between', 165 | height: '100%', 166 | }, 167 | text: { 168 | color: 'white', 169 | fontSize: 16, 170 | }, 171 | touchable: { 172 | padding: 10, 173 | }, 174 | rotateIcon: { 175 | color: 'white', 176 | fontSize: 26, 177 | ...Platform.select({ 178 | android: { 179 | textShadowOffset: { width: 1, height: 1 }, 180 | textShadowColor: '#000000', 181 | textShadowRadius: 3, 182 | shadowOpacity: 0.9, 183 | elevation: 1, 184 | }, 185 | ios: { 186 | shadowOffset: { width: 1, height: 1 }, 187 | shadowColor: '#000000', 188 | shadowRadius: 3, 189 | shadowOpacity: 0.9, 190 | }, 191 | }), 192 | }, 193 | }); 194 | ``` 195 | 196 | Now just pass your footer component to the Cropper like here: 197 | 198 | ```javascript 199 | import React, { Component } from 'react'; 200 | import AmazingCropper from 'react-native-amazing-cropper'; 201 | import CustomCropperFooter from './src/components/CustomCropperFooter.component'; 202 | 203 | class AmazingCropperPage extends Component { 204 | onDone = (croppedImageUri, garbageUris) => { 205 | console.log('croppedImageUri = ', croppedImageUri); 206 | // clear the garbage uris from cache 207 | // send image to server for example 208 | }; 209 | 210 | onError = (err) => { 211 | console.log(err); 212 | }; 213 | 214 | onCancel = () => { 215 | console.log('Cancel button was pressed'); 216 | // navigate back 217 | }; 218 | 219 | render() { 220 | return ( 221 | } 225 | onDone={this.onDone} 226 | onError={this.onError} 227 | onCancel={this.onCancel} 228 | imageUri='https://www.lifeofpix.com/wp-content/uploads/2018/09/manhattan_-11-1600x2396.jpg' 229 | imageWidth={1600} 230 | imageHeight={2396} 231 | /> 232 | ); 233 | } 234 | } 235 | ``` 236 | 237 | ### Do you need a mobile app for android & iOS? [Hire me](https://order-software.com) 238 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import Main from './src/Main'; 2 | import { DefaultFooter } from './src/common'; 3 | 4 | export default Main; 5 | export { DefaultFooter }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-amazing-cropper", 3 | "version": "0.2.8", 4 | "description": "Custom react native cropper with rotation", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "lint": "eslint . --ext .js,.jsx,.ts,.tsx", 10 | "build": "tsc" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/ggunti/react-native-amazing-cropper.git" 15 | }, 16 | "author": { 17 | "name": "Gotha Guntter", 18 | "email": "ggunty96@gmail.com" 19 | }, 20 | "license": "MIT", 21 | "peerDependencies": { 22 | "@bam.tech/react-native-image-resizer": "*", 23 | "@react-native-community/image-editor": "*", 24 | "react": "*", 25 | "react-native": "*" 26 | }, 27 | "devDependencies": { 28 | "@react-native-community/eslint-config": "^0.0.5", 29 | "@types/jest": "^25.1.2", 30 | "@types/react": "^16.9.19", 31 | "@types/react-native": "^0.61.15", 32 | "@types/react-test-renderer": "^16.9.2", 33 | "@typescript-eslint/eslint-plugin": "^2.12.0", 34 | "@typescript-eslint/parser": "^2.12.0", 35 | "babel-jest": "^24.9.0", 36 | "eslint": "^6.5.1", 37 | "jest": "^24.9.0", 38 | "metro-react-native-babel-preset": "^0.56.0", 39 | "react-test-renderer": "16.9.0", 40 | "typescript": "^3.9.10" 41 | }, 42 | "jest": { 43 | "preset": "react-native", 44 | "moduleFileExtensions": [ 45 | "ts", 46 | "tsx", 47 | "js", 48 | "jsx", 49 | "json", 50 | "node" 51 | ] 52 | }, 53 | "keywords": [ 54 | "react-native", 55 | "react", 56 | "crop", 57 | "rotate", 58 | "image", 59 | "cropper" 60 | ], 61 | "bugs": { 62 | "url": "https://github.com/ggunti/react-native-amazing-cropper/issues" 63 | }, 64 | "homepage": "https://github.com/ggunti/react-native-amazing-cropper" 65 | } 66 | -------------------------------------------------------------------------------- /src/Cropper/Cropper.page.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Animated, PanResponder, Platform, PanResponderInstance, PanResponderGestureState } from 'react-native'; 3 | import ImageResizer from '@bam.tech/react-native-image-resizer'; 4 | import ImageEditor from '@react-native-community/image-editor'; 5 | import { Q } from '../constants'; 6 | import Cropper from './Cropper'; 7 | import { getCropperLimits } from '../utils'; 8 | 9 | type CropperPageProps = { 10 | footerComponent: JSX.Element; 11 | onDone: (croppedImageUri: string, garbageUris: string[]) => void; 12 | onError: (err: Error) => void; 13 | onCancel: () => void; 14 | imageUri: string; 15 | imageWidth: number; 16 | imageHeight: number; 17 | TOP_VALUE: number; 18 | LEFT_VALUE: number; 19 | BOTTOM_VALUE: number; 20 | RIGHT_VALUE: number; 21 | initialRotation: number; 22 | NOT_SELECTED_AREA_OPACITY: number; 23 | BORDER_WIDTH: number; 24 | COMPONENT_WIDTH: number; 25 | COMPONENT_HEIGHT: number; 26 | }; 27 | 28 | interface ExtendedAnimatedValue extends Animated.Value { 29 | _value: number; 30 | _offset: number; 31 | } 32 | 33 | interface ExtendedAnimatedValueXY extends Animated.AnimatedValueXY { 34 | x: ExtendedAnimatedValue; 35 | y: ExtendedAnimatedValue; 36 | } 37 | 38 | type Position = 'topPosition' | 'leftPosition' | 'bottomPosition' | 'rightPosition'; 39 | 40 | interface State { 41 | topOuterPosition: ExtendedAnimatedValueXY; 42 | topOuterPanResponder: PanResponderInstance; 43 | leftOuterPosition: ExtendedAnimatedValueXY; 44 | leftOuterPanResponder: PanResponderInstance; 45 | bottomOuterPosition: ExtendedAnimatedValueXY; 46 | bottomOuterPanResponder: PanResponderInstance; 47 | rightOuterPosition: ExtendedAnimatedValueXY; 48 | rightOuterPanResponder: PanResponderInstance; 49 | 50 | topPosition: ExtendedAnimatedValueXY; 51 | topPanResponder: PanResponderInstance; 52 | leftPosition: ExtendedAnimatedValueXY; 53 | leftPanResponder: PanResponderInstance; 54 | bottomPosition: ExtendedAnimatedValueXY; 55 | bottomPanResponder: PanResponderInstance; 56 | rightPosition: ExtendedAnimatedValueXY; 57 | rightPanResponder: PanResponderInstance; 58 | 59 | topLeftPosition: ExtendedAnimatedValueXY; 60 | topLeftPanResponder: PanResponderInstance; 61 | bottomLeftPosition: ExtendedAnimatedValueXY; 62 | bottomLeftPanResponder: PanResponderInstance; 63 | bottomRightPosition: ExtendedAnimatedValueXY; 64 | bottomRightPanResponder: PanResponderInstance; 65 | topRightPosition: ExtendedAnimatedValueXY; 66 | topRightPanResponder: PanResponderInstance; 67 | 68 | rectanglePosition: ExtendedAnimatedValueXY; 69 | rectanglePanResponder: PanResponderInstance; 70 | 71 | TOP_LIMIT: number; 72 | LEFT_LIMIT: number; 73 | BOTTOM_LIMIT: number; 74 | RIGHT_LIMIT: number; 75 | 76 | TOP_VALUE: number; 77 | LEFT_VALUE: number; 78 | BOTTOM_VALUE: number; 79 | RIGHT_VALUE: number; 80 | rotation: number; 81 | } 82 | 83 | class CropperPage extends Component { 84 | constructor(props: CropperPageProps) { 85 | super(props); 86 | const { imageWidth, imageHeight, BORDER_WIDTH, COMPONENT_WIDTH, COMPONENT_HEIGHT } = props; 87 | const W_INT = this.W - 2 * BORDER_WIDTH; 88 | const H_INT = this.H - 2 * BORDER_WIDTH; 89 | const { TOP_LIMIT, LEFT_LIMIT, BOTTOM_LIMIT, RIGHT_LIMIT, DIFF } = getCropperLimits( 90 | imageWidth, 91 | imageHeight, 92 | props.initialRotation, 93 | W_INT, 94 | H_INT, 95 | this.W, 96 | this.H, 97 | BORDER_WIDTH, 98 | Q, 99 | ); 100 | 101 | const TOP_VALUE = props.TOP_VALUE !== 0 ? props.TOP_VALUE : TOP_LIMIT; 102 | const LEFT_VALUE = props.LEFT_VALUE !== 0 ? props.LEFT_VALUE : LEFT_LIMIT; 103 | const BOTTOM_VALUE = props.BOTTOM_VALUE !== 0 ? props.BOTTOM_VALUE : BOTTOM_LIMIT; 104 | const RIGHT_VALUE = props.RIGHT_VALUE !== 0 ? props.RIGHT_VALUE : RIGHT_LIMIT; 105 | 106 | const topOuterPosition = new Animated.ValueXY({ x: LEFT_VALUE - BORDER_WIDTH, y: TOP_VALUE - BORDER_WIDTH }) as ExtendedAnimatedValueXY; 107 | const topOuterPanResponder = PanResponder.create({ onStartShouldSetPanResponder: () => false }); 108 | const leftOuterPosition = new Animated.ValueXY({ x: LEFT_VALUE - BORDER_WIDTH, y: TOP_VALUE - BORDER_WIDTH }) as ExtendedAnimatedValueXY; 109 | const leftOuterPanResponder = PanResponder.create({ onStartShouldSetPanResponder: () => false }); 110 | const bottomOuterPosition = new Animated.ValueXY({ x: LEFT_VALUE - BORDER_WIDTH, y: COMPONENT_HEIGHT - BOTTOM_VALUE }) as ExtendedAnimatedValueXY; 111 | const bottomOuterPanResponder = PanResponder.create({ onStartShouldSetPanResponder: () => false }); 112 | const rightOuterPosition = new Animated.ValueXY({ x: COMPONENT_WIDTH - RIGHT_VALUE, y: TOP_VALUE - BORDER_WIDTH }) as ExtendedAnimatedValueXY; 113 | const rightOuterPanResponder = PanResponder.create({ onStartShouldSetPanResponder: () => false }); 114 | 115 | const topPosition = new Animated.ValueXY({ x: LEFT_VALUE - BORDER_WIDTH, y: TOP_VALUE - BORDER_WIDTH }) as ExtendedAnimatedValueXY; 116 | const topPanResponder = this.initSidePanResponder('topPosition'); 117 | const leftPosition = new Animated.ValueXY({ x: LEFT_VALUE - BORDER_WIDTH, y: TOP_VALUE - BORDER_WIDTH }) as ExtendedAnimatedValueXY; 118 | const leftPanResponder = this.initSidePanResponder('leftPosition'); 119 | const bottomPosition = new Animated.ValueXY({ x: LEFT_VALUE - BORDER_WIDTH, y: COMPONENT_HEIGHT - BOTTOM_VALUE }) as ExtendedAnimatedValueXY; 120 | const bottomPanResponder = this.initSidePanResponder('bottomPosition'); 121 | const rightPosition = new Animated.ValueXY({ 122 | x: COMPONENT_WIDTH - RIGHT_VALUE, 123 | y: TOP_VALUE - BORDER_WIDTH - DIFF / 2, 124 | }) as ExtendedAnimatedValueXY; 125 | const rightPanResponder = this.initSidePanResponder('rightPosition'); 126 | 127 | const topLeftPosition = new Animated.ValueXY({ x: LEFT_VALUE - BORDER_WIDTH, y: TOP_VALUE - BORDER_WIDTH }) as ExtendedAnimatedValueXY; 128 | const topLeftPanResponder = this.initCornerPanResponder('topPosition', 'leftPosition'); 129 | const bottomLeftPosition = new Animated.ValueXY({ x: LEFT_VALUE - BORDER_WIDTH, y: COMPONENT_HEIGHT - BOTTOM_VALUE }) as ExtendedAnimatedValueXY; 130 | const bottomLeftPanResponder = this.initCornerPanResponder('bottomPosition', 'leftPosition'); 131 | const bottomRightPosition = new Animated.ValueXY({ 132 | x: COMPONENT_WIDTH - RIGHT_VALUE, 133 | y: COMPONENT_HEIGHT - BOTTOM_VALUE, 134 | }) as ExtendedAnimatedValueXY; 135 | const bottomRightPanResponder = this.initCornerPanResponder('bottomPosition', 'rightPosition'); 136 | const topRightPosition = new Animated.ValueXY({ x: COMPONENT_WIDTH - RIGHT_VALUE, y: TOP_VALUE - BORDER_WIDTH }) as ExtendedAnimatedValueXY; 137 | const topRightPanResponder = this.initCornerPanResponder('topPosition', 'rightPosition'); 138 | 139 | const rectanglePosition = new Animated.ValueXY({ x: LEFT_VALUE, y: TOP_VALUE }) as ExtendedAnimatedValueXY; 140 | const rectanglePanResponder = this.initRectanglePanResponder(); 141 | 142 | this.state = { 143 | topOuterPosition, 144 | topOuterPanResponder, 145 | leftOuterPosition, 146 | leftOuterPanResponder, 147 | bottomOuterPosition, 148 | bottomOuterPanResponder, 149 | rightOuterPosition, 150 | rightOuterPanResponder, 151 | 152 | topPosition, 153 | topPanResponder, 154 | leftPosition, 155 | leftPanResponder, 156 | bottomPosition, 157 | bottomPanResponder, 158 | rightPosition, 159 | rightPanResponder, 160 | 161 | topLeftPosition, 162 | topLeftPanResponder, 163 | bottomLeftPosition, 164 | bottomLeftPanResponder, 165 | bottomRightPosition, 166 | bottomRightPanResponder, 167 | topRightPosition, 168 | topRightPanResponder, 169 | 170 | rectanglePosition, 171 | rectanglePanResponder, 172 | 173 | TOP_LIMIT, 174 | LEFT_LIMIT, 175 | BOTTOM_LIMIT, 176 | RIGHT_LIMIT, 177 | 178 | TOP_VALUE, 179 | LEFT_VALUE, 180 | BOTTOM_VALUE, 181 | RIGHT_VALUE, 182 | rotation: props.initialRotation, 183 | }; 184 | } 185 | 186 | isRectangleMoving = false; 187 | topOuter = undefined; 188 | leftOuter = undefined; 189 | bottomOuter = undefined; 190 | rightOuter = undefined; 191 | W = this.props.COMPONENT_WIDTH; 192 | H = this.props.COMPONENT_HEIGHT - Q; 193 | 194 | onCancel = () => { 195 | this.props.onCancel(); 196 | }; 197 | 198 | getTopOuterStyle = () => { 199 | return { 200 | ...this.state.topOuterPosition.getLayout(), 201 | top: this.state.TOP_LIMIT, 202 | left: this.state.LEFT_LIMIT, 203 | height: Animated.add(this.props.BORDER_WIDTH - this.state.TOP_LIMIT, this.state.topPosition.y), 204 | width: this.W, 205 | backgroundColor: `rgba(0, 0, 0, ${this.props.NOT_SELECTED_AREA_OPACITY})`, 206 | }; 207 | }; 208 | 209 | getLeftOuterStyle = () => { 210 | return { 211 | ...this.state.leftOuterPosition.getLayout(), 212 | top: Animated.add(this.props.BORDER_WIDTH, this.state.topPosition.y), 213 | left: this.state.LEFT_LIMIT, 214 | height: Animated.add(-this.props.BORDER_WIDTH, Animated.add(this.state.bottomPosition.y, Animated.multiply(-1, this.state.topPosition.y))), 215 | width: Animated.add(this.props.BORDER_WIDTH - this.state.LEFT_LIMIT, this.state.leftPosition.x), 216 | backgroundColor: `rgba(0, 0, 0, ${this.props.NOT_SELECTED_AREA_OPACITY})`, 217 | }; 218 | }; 219 | 220 | getBottomOuterStyle = () => { 221 | return { 222 | ...this.state.bottomOuterPosition.getLayout(), 223 | top: this.state.bottomPosition.y, 224 | left: this.state.LEFT_LIMIT, 225 | height: Animated.add(this.props.COMPONENT_HEIGHT - this.state.BOTTOM_LIMIT, Animated.multiply(-1, this.state.bottomPosition.y)), 226 | width: this.W, 227 | backgroundColor: `rgba(0, 0, 0, ${this.props.NOT_SELECTED_AREA_OPACITY})`, 228 | }; 229 | }; 230 | 231 | getRightOuterStyle = () => { 232 | return { 233 | ...this.state.rightOuterPosition.getLayout(), 234 | top: Animated.add(this.props.BORDER_WIDTH, this.state.topPosition.y), 235 | left: this.state.rightPosition.x, 236 | height: Animated.add(-this.props.BORDER_WIDTH, Animated.add(this.state.bottomPosition.y, Animated.multiply(-1, this.state.topPosition.y))), 237 | right: this.state.RIGHT_LIMIT, 238 | backgroundColor: `rgba(0, 0, 0, ${this.props.NOT_SELECTED_AREA_OPACITY})`, 239 | }; 240 | }; 241 | 242 | getTopLeftStyle = () => { 243 | return { 244 | ...this.state.topLeftPosition.getLayout(), 245 | top: this.state.topPosition.y, 246 | left: this.state.leftPosition.x, 247 | width: this.props.BORDER_WIDTH, 248 | paddingBottom: this.props.BORDER_WIDTH, 249 | }; 250 | }; 251 | 252 | getBottomLeftStyle = () => { 253 | return { 254 | ...this.state.bottomLeftPosition.getLayout(), 255 | top: this.state.bottomPosition.y, 256 | left: this.state.leftPosition.x, 257 | width: this.props.BORDER_WIDTH, 258 | paddingTop: this.props.BORDER_WIDTH, 259 | }; 260 | }; 261 | 262 | getBottomRightStyle = () => { 263 | return { 264 | ...this.state.bottomRightPosition.getLayout(), 265 | top: this.state.bottomPosition.y, 266 | left: this.state.rightPosition.x, 267 | height: this.props.BORDER_WIDTH, 268 | paddingLeft: this.props.BORDER_WIDTH, 269 | }; 270 | }; 271 | 272 | getTopRightStyle = () => { 273 | return { 274 | ...this.state.topRightPosition.getLayout(), 275 | top: this.state.topPosition.y, 276 | left: this.state.rightPosition.x, 277 | height: this.props.BORDER_WIDTH, 278 | paddingLeft: this.props.BORDER_WIDTH, 279 | }; 280 | }; 281 | 282 | getTopSideStyle = () => { 283 | return { 284 | ...this.state.topPosition.getLayout(), 285 | left: Animated.add(this.props.BORDER_WIDTH, this.state.leftPosition.x), 286 | width: Animated.add(-this.props.BORDER_WIDTH, Animated.add(this.state.rightPosition.x, Animated.multiply(-1, this.state.leftPosition.x))), 287 | paddingBottom: this.props.BORDER_WIDTH, 288 | }; 289 | }; 290 | 291 | getLeftSideStyle = () => { 292 | return { 293 | ...this.state.leftPosition.getLayout(), 294 | top: Animated.add(this.props.BORDER_WIDTH, this.state.topPosition.y), 295 | height: Animated.add(-this.props.BORDER_WIDTH, Animated.add(this.state.bottomPosition.y, Animated.multiply(-1, this.state.topPosition.y))), 296 | paddingLeft: this.props.BORDER_WIDTH, 297 | }; 298 | }; 299 | 300 | getBottomSideStyle = () => { 301 | return { 302 | ...this.state.bottomPosition.getLayout(), 303 | left: Animated.add(this.props.BORDER_WIDTH, this.state.leftPosition.x), 304 | width: Animated.add(-this.props.BORDER_WIDTH, Animated.add(this.state.rightPosition.x, Animated.multiply(-1, this.state.leftPosition.x))), 305 | paddingTop: this.props.BORDER_WIDTH, 306 | }; 307 | }; 308 | 309 | getRightSideStyle = () => { 310 | return { 311 | ...this.state.rightPosition.getLayout(), 312 | top: Animated.add(this.props.BORDER_WIDTH, this.state.topPosition.y), 313 | height: Animated.add(-this.props.BORDER_WIDTH, Animated.add(this.state.bottomPosition.y, Animated.multiply(-1, this.state.topPosition.y))), 314 | paddingLeft: this.props.BORDER_WIDTH, 315 | }; 316 | }; 317 | 318 | getRectangleStyle = () => { 319 | return { 320 | ...this.state.rectanglePosition.getLayout(), 321 | top: Animated.add(this.props.BORDER_WIDTH, this.state.topPosition.y), 322 | left: Animated.add(this.props.BORDER_WIDTH, this.state.leftPosition.x), 323 | width: Animated.add(-this.props.BORDER_WIDTH, Animated.add(this.state.rightPosition.x, Animated.multiply(-1, this.state.leftPosition.x))), 324 | height: Animated.add(-this.props.BORDER_WIDTH, Animated.add(this.state.bottomPosition.y, Animated.multiply(-1, this.state.topPosition.y))), 325 | zIndex: 3, 326 | }; 327 | }; 328 | 329 | getImageStyle = () => { 330 | const DIFF = this.state.topPosition.y._value - this.state.rightPosition.y._value; 331 | return { 332 | position: 'absolute', 333 | top: this.state.TOP_LIMIT - DIFF, 334 | left: this.state.LEFT_LIMIT + DIFF, 335 | bottom: this.state.BOTTOM_LIMIT - DIFF, 336 | right: this.state.RIGHT_LIMIT + DIFF, 337 | resizeMode: 'stretch', 338 | transform: [{ rotate: `${this.state.rotation.toString()}deg` }], 339 | }; 340 | }; 341 | 342 | isAllowedToMoveTopSide = (gesture: PanResponderGestureState) => { 343 | return ( 344 | this.state.topPosition.y._offset + gesture.dy >= this.state.TOP_LIMIT - this.props.BORDER_WIDTH && 345 | this.state.topPosition.y._offset + gesture.dy + this.props.BORDER_WIDTH + 1 < this.state.bottomPosition.y._offset 346 | ); 347 | }; 348 | isAllowedToMoveLeftSide = (gesture: PanResponderGestureState) => { 349 | return ( 350 | this.state.leftPosition.x._offset + gesture.dx >= this.state.LEFT_LIMIT - this.props.BORDER_WIDTH && 351 | this.state.leftPosition.x._offset + gesture.dx + this.props.BORDER_WIDTH + 1 < this.state.rightPosition.x._offset 352 | ); 353 | }; 354 | isAllowedToMoveBottomSide = (gesture: PanResponderGestureState) => { 355 | return ( 356 | this.state.bottomPosition.y._offset + gesture.dy <= this.props.COMPONENT_HEIGHT - this.state.BOTTOM_LIMIT && 357 | this.state.topPosition.y._offset + this.props.BORDER_WIDTH + 1 < this.state.bottomPosition.y._offset + gesture.dy 358 | ); 359 | }; 360 | isAllowedToMoveRightSide = (gesture: PanResponderGestureState) => { 361 | return ( 362 | this.state.rightPosition.x._offset + gesture.dx <= this.props.COMPONENT_WIDTH - this.state.RIGHT_LIMIT && 363 | this.state.leftPosition.x._offset + this.props.BORDER_WIDTH + 1 < this.state.rightPosition.x._offset + gesture.dx 364 | ); 365 | }; 366 | 367 | isAllowedToMove = (position: Position, gesture: PanResponderGestureState) => { 368 | if (position === 'topPosition') { 369 | return this.isAllowedToMoveTopSide(gesture); 370 | } 371 | if (position === 'leftPosition') { 372 | return this.isAllowedToMoveLeftSide(gesture); 373 | } 374 | if (position === 'bottomPosition') { 375 | return this.isAllowedToMoveBottomSide(gesture); 376 | } 377 | if (position === 'rightPosition') { 378 | return this.isAllowedToMoveRightSide(gesture); 379 | } 380 | }; 381 | 382 | initSidePanResponder = (position: Position) => { 383 | return PanResponder.create({ 384 | onStartShouldSetPanResponder: () => !this.isRectangleMoving, 385 | onPanResponderMove: (_event, gesture) => { 386 | if (this.isAllowedToMove(position, gesture)) { 387 | this.state[position].setValue({ x: gesture.dx, y: gesture.dy }); 388 | } 389 | }, 390 | onPanResponderRelease: () => { 391 | // make to not reset position 392 | this.state.topPosition.flattenOffset(); 393 | this.state.leftPosition.flattenOffset(); 394 | this.state.bottomPosition.flattenOffset(); 395 | this.state.rightPosition.flattenOffset(); 396 | }, 397 | onPanResponderGrant: () => { 398 | this.state.topPosition.setOffset({ 399 | x: this.state.topPosition.x._value, 400 | y: this.state.topPosition.y._value, 401 | }); 402 | this.state.leftPosition.setOffset({ 403 | x: this.state.leftPosition.x._value, 404 | y: this.state.leftPosition.y._value, 405 | }); 406 | this.state.bottomPosition.setOffset({ 407 | x: this.state.bottomPosition.x._value, 408 | y: this.state.bottomPosition.y._value, 409 | }); 410 | this.state.rightPosition.setOffset({ 411 | x: this.state.rightPosition.x._value, 412 | y: this.state.rightPosition.y._value, 413 | }); 414 | 415 | this.state.topPosition.setValue({ x: 0, y: 0 }); 416 | this.state.leftPosition.setValue({ x: 0, y: 0 }); 417 | this.state.bottomPosition.setValue({ x: 0, y: 0 }); 418 | this.state.rightPosition.setValue({ x: 0, y: 0 }); 419 | }, 420 | }); 421 | }; 422 | 423 | initRectanglePanResponder = () => { 424 | return PanResponder.create({ 425 | onStartShouldSetPanResponder: () => !this.isRectangleMoving, 426 | onPanResponderMove: (_event, gesture) => { 427 | this.state.topPosition.setValue({ x: gesture.dx, y: gesture.dy }); 428 | this.state.leftPosition.setValue({ x: gesture.dx, y: gesture.dy }); 429 | this.state.bottomPosition.setValue({ x: gesture.dx, y: gesture.dy }); 430 | this.state.rightPosition.setValue({ x: gesture.dx, y: gesture.dy }); 431 | }, 432 | onPanResponderRelease: () => { 433 | this.isRectangleMoving = true; 434 | // make to not reset position 435 | this.state.topPosition.flattenOffset(); 436 | this.state.leftPosition.flattenOffset(); 437 | this.state.bottomPosition.flattenOffset(); 438 | this.state.rightPosition.flattenOffset(); 439 | 440 | const width = this.state.rightPosition.x._value - this.state.leftPosition.x._value - this.props.BORDER_WIDTH; 441 | const height = this.state.bottomPosition.y._value - this.state.topPosition.y._value - this.props.BORDER_WIDTH; 442 | let isOutside = false; 443 | 444 | if (this.state.leftPosition.x._value < this.state.LEFT_LIMIT - this.props.BORDER_WIDTH) { 445 | isOutside = true; 446 | Animated.parallel([ 447 | Animated.spring(this.state.leftPosition.x, { toValue: this.state.LEFT_LIMIT - this.props.BORDER_WIDTH, useNativeDriver: false }), 448 | Animated.spring(this.state.rightPosition.x, { toValue: this.state.LEFT_LIMIT + width, useNativeDriver: false }), 449 | ]).start(() => { 450 | this.isRectangleMoving = false; 451 | }); 452 | } 453 | if (this.state.topPosition.y._value < this.state.TOP_LIMIT - this.props.BORDER_WIDTH) { 454 | isOutside = true; 455 | Animated.parallel([ 456 | Animated.spring(this.state.topPosition.y, { toValue: this.state.TOP_LIMIT - this.props.BORDER_WIDTH, useNativeDriver: false }), 457 | Animated.spring(this.state.bottomPosition.y, { toValue: this.state.TOP_LIMIT + height, useNativeDriver: false }), 458 | ]).start(() => { 459 | this.isRectangleMoving = false; 460 | }); 461 | } 462 | if (width + this.state.leftPosition.x._value + this.props.BORDER_WIDTH > this.props.COMPONENT_WIDTH - this.state.RIGHT_LIMIT) { 463 | isOutside = true; 464 | Animated.parallel([ 465 | Animated.spring(this.state.leftPosition.x, { 466 | toValue: this.props.COMPONENT_WIDTH - this.state.RIGHT_LIMIT - width - this.props.BORDER_WIDTH, 467 | useNativeDriver: false, 468 | }), 469 | Animated.spring(this.state.rightPosition.x, { toValue: this.props.COMPONENT_WIDTH - this.state.RIGHT_LIMIT, useNativeDriver: false }), 470 | ]).start(() => { 471 | this.isRectangleMoving = false; 472 | }); 473 | } 474 | if (height + this.state.topPosition.y._value + this.props.BORDER_WIDTH > this.props.COMPONENT_HEIGHT - this.state.BOTTOM_LIMIT) { 475 | isOutside = true; 476 | Animated.parallel([ 477 | Animated.spring(this.state.topPosition.y, { 478 | toValue: this.props.COMPONENT_HEIGHT - this.state.BOTTOM_LIMIT - height - this.props.BORDER_WIDTH, 479 | useNativeDriver: false, 480 | }), 481 | Animated.spring(this.state.bottomPosition.y, { toValue: this.props.COMPONENT_HEIGHT - this.state.BOTTOM_LIMIT, useNativeDriver: false }), 482 | ]).start(() => { 483 | this.isRectangleMoving = false; 484 | }); 485 | } 486 | if (!isOutside) { 487 | this.isRectangleMoving = false; 488 | } 489 | }, 490 | onPanResponderGrant: () => { 491 | this.state.topPosition.setOffset({ 492 | x: this.state.topPosition.x._value, 493 | y: this.state.topPosition.y._value, 494 | }); 495 | this.state.leftPosition.setOffset({ 496 | x: this.state.leftPosition.x._value, 497 | y: this.state.leftPosition.y._value, 498 | }); 499 | this.state.bottomPosition.setOffset({ 500 | x: this.state.bottomPosition.x._value, 501 | y: this.state.bottomPosition.y._value, 502 | }); 503 | this.state.rightPosition.setOffset({ 504 | x: this.state.rightPosition.x._value, 505 | y: this.state.rightPosition.y._value, 506 | }); 507 | this.state.topPosition.setValue({ x: 0, y: 0 }); 508 | this.state.leftPosition.setValue({ x: 0, y: 0 }); 509 | this.state.bottomPosition.setValue({ x: 0, y: 0 }); 510 | this.state.rightPosition.setValue({ x: 0, y: 0 }); 511 | }, 512 | }); 513 | }; 514 | 515 | initCornerPanResponder = (pos1: Position, pos2: Position) => { 516 | return PanResponder.create({ 517 | onStartShouldSetPanResponder: () => !this.isRectangleMoving, 518 | onPanResponderMove: (_event, gesture) => { 519 | if (this.isAllowedToMove(pos1, gesture)) { 520 | this.state[pos1].setValue({ x: gesture.dx, y: gesture.dy }); 521 | } 522 | if (this.isAllowedToMove(pos2, gesture)) { 523 | this.state[pos2].setValue({ x: gesture.dx, y: gesture.dy }); 524 | } 525 | }, 526 | onPanResponderRelease: () => { 527 | this.state.topPosition.flattenOffset(); 528 | this.state.leftPosition.flattenOffset(); 529 | this.state.bottomPosition.flattenOffset(); 530 | this.state.rightPosition.flattenOffset(); 531 | }, 532 | onPanResponderGrant: () => { 533 | this.state.topPosition.setOffset({ x: this.state.topPosition.x._value, y: this.state.topPosition.y._value }); 534 | this.state.leftPosition.setOffset({ x: this.state.leftPosition.x._value, y: this.state.leftPosition.y._value }); 535 | this.state.bottomPosition.setOffset({ x: this.state.bottomPosition.x._value, y: this.state.bottomPosition.y._value }); 536 | this.state.rightPosition.setOffset({ x: this.state.rightPosition.x._value, y: this.state.rightPosition.y._value }); 537 | 538 | this.state.topPosition.setValue({ x: 0, y: 0 }); 539 | this.state.leftPosition.setValue({ x: 0, y: 0 }); 540 | this.state.bottomPosition.setValue({ x: 0, y: 0 }); 541 | this.state.rightPosition.setValue({ x: 0, y: 0 }); 542 | }, 543 | }); 544 | }; 545 | 546 | setCropBoxLimits = ({ 547 | TOP_LIMIT, 548 | LEFT_LIMIT, 549 | BOTTOM_LIMIT, 550 | RIGHT_LIMIT, 551 | }: { 552 | TOP_LIMIT: number; 553 | LEFT_LIMIT: number; 554 | BOTTOM_LIMIT: number; 555 | RIGHT_LIMIT: number; 556 | }) => { 557 | this.setState({ 558 | TOP_LIMIT, 559 | LEFT_LIMIT, 560 | BOTTOM_LIMIT, 561 | RIGHT_LIMIT, 562 | }); 563 | }; 564 | 565 | setCropBoxValues = ({ 566 | TOP_VALUE, 567 | LEFT_VALUE, 568 | BOTTOM_VALUE, 569 | RIGHT_VALUE, 570 | }: { 571 | TOP_VALUE: number; 572 | LEFT_VALUE: number; 573 | BOTTOM_VALUE: number; 574 | RIGHT_VALUE: number; 575 | }) => { 576 | this.setState({ 577 | TOP_VALUE, 578 | LEFT_VALUE, 579 | BOTTOM_VALUE, 580 | RIGHT_VALUE, 581 | }); 582 | }; 583 | 584 | setCropBoxRotation = (rotation: number) => { 585 | this.setState({ rotation }); 586 | }; 587 | 588 | rotate90 = () => { 589 | this.setCropBoxRotation((360 + (this.state.rotation - 90)) % 360); 590 | }; 591 | 592 | onRotate = () => { 593 | const W_INT = this.W - 2 * this.props.BORDER_WIDTH; 594 | const H_INT = this.H - 2 * this.props.BORDER_WIDTH; 595 | let imageWidth = 0; 596 | let imageHeight = 0; 597 | let rotation = 0; 598 | if (this.state.rotation % 180 === 90) { 599 | imageWidth = this.props.imageWidth > 0 ? this.props.imageWidth : 1280; // 340 600 | imageHeight = this.props.imageHeight > 0 ? this.props.imageHeight : 747; // 500 601 | rotation = 0; 602 | } else { 603 | imageWidth = this.props.COMPONENT_WIDTH - this.state.LEFT_LIMIT - this.state.RIGHT_LIMIT; 604 | imageHeight = this.props.COMPONENT_HEIGHT - this.state.TOP_LIMIT - this.state.BOTTOM_LIMIT; 605 | rotation = 90; 606 | } 607 | const { TOP_LIMIT, LEFT_LIMIT, BOTTOM_LIMIT, RIGHT_LIMIT, DIFF } = getCropperLimits( 608 | imageWidth, 609 | imageHeight, 610 | rotation, 611 | W_INT, 612 | H_INT, 613 | this.W, 614 | this.H, 615 | this.props.BORDER_WIDTH, 616 | Q, 617 | ); 618 | this.rotate90(); 619 | this.setCropBoxLimits({ TOP_LIMIT, LEFT_LIMIT, BOTTOM_LIMIT, RIGHT_LIMIT }); 620 | const startPositionBeforeRotationX = this.state.leftPosition.x._value - this.state.LEFT_LIMIT + this.props.BORDER_WIDTH; 621 | const startPositionBeforeRotationY = this.state.topPosition.y._value - this.state.TOP_LIMIT + this.props.BORDER_WIDTH; 622 | const imageWidthBeforeRotation = this.props.COMPONENT_WIDTH - this.state.RIGHT_LIMIT - this.state.LEFT_LIMIT; 623 | const imageHeightBeforeRotation = this.props.COMPONENT_HEIGHT - this.state.BOTTOM_LIMIT - this.state.TOP_LIMIT; 624 | const rectangleWidthBeforeRotation = this.state.rightPosition.x._value - this.state.leftPosition.x._value - this.props.BORDER_WIDTH; 625 | const rectangleHeightBeforeRotation = this.state.bottomPosition.y._value - this.state.topPosition.y._value - this.props.BORDER_WIDTH; 626 | const imageWidthAfterRotation = this.props.COMPONENT_WIDTH - RIGHT_LIMIT - LEFT_LIMIT; 627 | const imageHeightAfterRotation = this.props.COMPONENT_HEIGHT - BOTTOM_LIMIT - TOP_LIMIT; 628 | const rectangleWidthAfterRotation = (imageWidthAfterRotation * rectangleHeightBeforeRotation) / imageHeightBeforeRotation; 629 | const rectangleHeightAfterRotation = (imageHeightAfterRotation * rectangleWidthBeforeRotation) / imageWidthBeforeRotation; 630 | const startPositionAfterRotationX = (startPositionBeforeRotationY * imageWidthAfterRotation) / imageHeightBeforeRotation; 631 | const startPositionAfterRotationY = 632 | ((imageWidthBeforeRotation - startPositionBeforeRotationX - rectangleWidthBeforeRotation) * imageHeightAfterRotation) / 633 | imageWidthBeforeRotation; 634 | 635 | this.state.topPosition.setValue({ 636 | x: LEFT_LIMIT + startPositionAfterRotationX - this.props.BORDER_WIDTH, 637 | y: TOP_LIMIT + startPositionAfterRotationY - this.props.BORDER_WIDTH, 638 | }); 639 | this.state.leftPosition.setValue({ 640 | x: LEFT_LIMIT + startPositionAfterRotationX - this.props.BORDER_WIDTH, 641 | y: TOP_LIMIT + startPositionAfterRotationY - this.props.BORDER_WIDTH, 642 | }); 643 | this.state.bottomPosition.setValue({ 644 | x: LEFT_LIMIT + startPositionAfterRotationX - this.props.BORDER_WIDTH, 645 | y: TOP_LIMIT + startPositionAfterRotationY + rectangleHeightAfterRotation, 646 | }); 647 | this.state.rightPosition.setValue({ 648 | x: LEFT_LIMIT + startPositionAfterRotationX + rectangleWidthAfterRotation, 649 | y: TOP_LIMIT + startPositionAfterRotationY - this.props.BORDER_WIDTH - DIFF / 2, 650 | }); 651 | // @ts-ignore 652 | this.topOuter.setNativeProps({ style: { top: TOP_LIMIT, height: 0 } }); 653 | // @ts-ignore 654 | this.leftOuter.setNativeProps({ style: { left: LEFT_LIMIT, width: 0 } }); 655 | // @ts-ignore 656 | this.bottomOuter.setNativeProps({ style: { top: BOTTOM_LIMIT, height: 0 } }); 657 | // @ts-ignore 658 | this.rightOuter.setNativeProps({ style: { top: TOP_LIMIT, height: 0 } }); 659 | }; 660 | 661 | onDone = () => { 662 | if (this.isRectangleMoving) { 663 | return null; 664 | } 665 | 666 | const IMAGE_W = this.props.COMPONENT_WIDTH - this.state.RIGHT_LIMIT - this.state.LEFT_LIMIT; 667 | const IMAGE_H = this.props.COMPONENT_HEIGHT - this.state.BOTTOM_LIMIT - this.state.TOP_LIMIT; 668 | let x = this.state.leftPosition.x._value - this.state.LEFT_LIMIT + this.props.BORDER_WIDTH; 669 | let y = this.state.topPosition.y._value - this.state.TOP_LIMIT + this.props.BORDER_WIDTH; 670 | let width = this.state.rightPosition.x._value - this.state.leftPosition.x._value - this.props.BORDER_WIDTH; 671 | let height = this.state.bottomPosition.y._value - this.state.topPosition.y._value - this.props.BORDER_WIDTH; 672 | let imageWidth = this.props.imageWidth > 0 ? this.props.imageWidth : 1280; // 340 673 | let imageHeight = this.props.imageHeight > 0 ? this.props.imageHeight : 747; // 500 674 | if (this.state.rotation % 180 === 90) { 675 | const pivot = imageWidth; 676 | imageWidth = imageHeight; 677 | imageHeight = pivot; 678 | } 679 | width = (width * imageWidth) / IMAGE_W; 680 | height = (height * imageHeight) / IMAGE_H; 681 | x = (x * imageWidth) / IMAGE_W; 682 | y = (y * imageHeight) / IMAGE_H; 683 | const cropData = { 684 | offset: { x, y }, 685 | size: { width, height }, 686 | resizeMode: 'stretch' as 'stretch', 687 | }; 688 | const garbageUris: string[] = []; 689 | ImageResizer.createResizedImage( 690 | this.props.imageUri, 691 | this.state.rotation % 180 === 0 ? imageWidth : imageHeight, 692 | this.state.rotation % 180 === 0 ? imageHeight : imageWidth, 693 | 'JPEG', 694 | 100, 695 | this.state.rotation, 696 | undefined, 697 | false, 698 | { 699 | mode: 'cover', 700 | onlyScaleDown: true, 701 | }, 702 | ) 703 | .then((res) => { 704 | garbageUris.push(res.uri); 705 | return ImageEditor.cropImage(res.uri, cropData); 706 | }) 707 | .then((cropResult) => { 708 | this.props.onDone(cropResult.uri, garbageUris); 709 | }) 710 | .catch((err: Error) => { 711 | this.props.onError(err); 712 | }); 713 | }; 714 | 715 | render() { 716 | return ( 717 | (this.topOuter = ref)} 751 | leftOuterRef={(ref) => (this.leftOuter = ref)} 752 | bottomOuterRef={(ref) => (this.bottomOuter = ref)} 753 | rightOuterRef={(ref) => (this.rightOuter = ref)} 754 | COMPONENT_WIDTH={this.props.COMPONENT_WIDTH} 755 | COMPONENT_HEIGHT={this.props.COMPONENT_HEIGHT} 756 | W={this.W} 757 | H={this.H} 758 | /> 759 | ); 760 | } 761 | } 762 | 763 | export default CropperPage; 764 | -------------------------------------------------------------------------------- /src/Cropper/Cropper.style.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { Q } from '../constants'; 3 | 4 | export default function getStyles(COMPONENT_WIDTH: number, COMPONENT_HEIGHT: number, W: number) { 5 | return StyleSheet.create({ 6 | container: { 7 | flex: 1, 8 | flexDirection: 'column', 9 | alignItems: 'center', 10 | justifyContent: 'center', 11 | backgroundColor: 'black', 12 | }, 13 | secondContainer: { 14 | position: 'absolute', 15 | top: 0, 16 | left: 0, 17 | width: COMPONENT_WIDTH, 18 | height: COMPONENT_HEIGHT, 19 | }, 20 | footerContainer: { 21 | position: 'absolute', 22 | top: COMPONENT_HEIGHT - Q, 23 | height: Q, 24 | width: W, 25 | }, 26 | gridRow: { 27 | flex: 1, 28 | flexDirection: 'row', 29 | }, 30 | gridColumn: { 31 | flex: 1, 32 | borderWidth: 1, 33 | borderColor: 'rgba(255, 255, 255, 0.5)', 34 | }, 35 | animation: { 36 | position: 'absolute', 37 | backgroundColor: 'transparent', 38 | }, 39 | topSideAnimation: { 40 | borderBottomWidth: 20, 41 | borderColor: 'transparent', 42 | zIndex: 4, 43 | }, 44 | leftSideAnimation: { 45 | borderRightWidth: 20, 46 | borderColor: 'transparent', 47 | zIndex: 4, 48 | }, 49 | bottomSideAnimation: { 50 | borderTopWidth: 20, 51 | borderColor: 'transparent', 52 | zIndex: 4, 53 | transform: [{ translateY: -20 }], 54 | }, 55 | rightSideAnimation: { 56 | borderLeftWidth: 20, 57 | borderColor: 'transparent', 58 | zIndex: 4, 59 | transform: [{ translateX: -20 }], 60 | }, 61 | topLeftAnimation: { 62 | borderLeftWidth: 56, 63 | borderRightWidth: 25, 64 | borderTopWidth: 31, 65 | borderColor: 'transparent', 66 | zIndex: 5, 67 | }, 68 | bottomLeftAnimation: { 69 | borderLeftWidth: 56, 70 | borderRightWidth: 25, 71 | borderBottomWidth: 31, 72 | borderColor: 'transparent', 73 | zIndex: 5, 74 | transform: [{ translateY: -31 }], 75 | }, 76 | bottomRightAnimation: { 77 | borderTopWidth: 25, 78 | borderRightWidth: 31, 79 | borderBottomWidth: 56, 80 | borderColor: 'transparent', 81 | zIndex: 5, 82 | transform: [{ translateX: -31 }, { translateY: -31 }], 83 | }, 84 | topRightAnimation: { 85 | borderColor: 'transparent', 86 | borderTopWidth: 56, 87 | borderRightWidth: 31, 88 | borderBottomWidth: 25, 89 | zIndex: 5, 90 | transform: [{ translateX: -31 }], 91 | }, 92 | borderDesign: { 93 | width: 30, 94 | height: 30, 95 | borderColor: 'white', 96 | }, 97 | icon: { 98 | paddingRight: 10, 99 | flexDirection: 'row', 100 | }, 101 | zoomNavBar: { 102 | width: '100%', 103 | height: 50, 104 | backgroundColor: '#5a2480', 105 | alignItems: 'center', 106 | position: 'absolute', 107 | flexDirection: 'row', 108 | justifyContent: 'space-between', 109 | bottom: 20, 110 | borderTopLeftRadius: 20, 111 | borderTopRightRadius: 20, 112 | paddingHorizontal: 20, 113 | }, 114 | rightNav: { 115 | flexDirection: 'row', 116 | }, 117 | }); 118 | } 119 | -------------------------------------------------------------------------------- /src/Cropper/Cropper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, Animated, Image, PanResponderInstance } from 'react-native'; 3 | import getStyles from './Cropper.style'; 4 | 5 | interface CropperProps { 6 | imageUri: string; 7 | footerComponent: JSX.Element; 8 | getTopOuterStyle: () => object; 9 | getLeftOuterStyle: () => object; 10 | getBottomOuterStyle: () => object; 11 | getRightOuterStyle: () => object; 12 | getTopLeftStyle: () => object; 13 | getBottomLeftStyle: () => object; 14 | getBottomRightStyle: () => object; 15 | getTopRightStyle: () => object; 16 | getTopSideStyle: () => object; 17 | getLeftSideStyle: () => object; 18 | getBottomSideStyle: () => object; 19 | getRightSideStyle: () => object; 20 | getRectangleStyle: () => object; 21 | getImageStyle: () => object; 22 | onDone: () => void; 23 | onRotate: () => void; 24 | onCancel: () => void; 25 | topOuterPanResponder: PanResponderInstance; 26 | leftOuterPanResponder: PanResponderInstance; 27 | bottomOuterPanResponder: PanResponderInstance; 28 | rightOuterPanResponder: PanResponderInstance; 29 | topPanResponder: PanResponderInstance; 30 | leftPanResponder: PanResponderInstance; 31 | bottomPanResponder: PanResponderInstance; 32 | rightPanResponder: PanResponderInstance; 33 | topLeftPanResponder: PanResponderInstance; 34 | bottomLeftPanResponder: PanResponderInstance; 35 | bottomRightPanResponder: PanResponderInstance; 36 | topRightPanResponder: PanResponderInstance; 37 | rectanglePanResponder: PanResponderInstance; 38 | topOuterRef: (ref: any) => any; 39 | leftOuterRef: (ref: any) => any; 40 | bottomOuterRef: (ref: any) => any; 41 | rightOuterRef: (ref: any) => any; 42 | COMPONENT_WIDTH: number; 43 | COMPONENT_HEIGHT: number; 44 | W: number; 45 | H: number; 46 | } 47 | 48 | const Cropper: React.FC = props => { 49 | const styles = getStyles(props.COMPONENT_WIDTH, props.COMPONENT_HEIGHT, props.W); 50 | return ( 51 | 52 | 53 | 54 | 55 | 56 | 57 | {React.cloneElement(props.footerComponent, { 58 | onDone: props.onDone, 59 | onRotate: props.onRotate, 60 | onCancel: props.onCancel, 61 | })} 62 | 63 | {/* 64 | // @ts-ignore */} 65 | 66 | {/* 67 | // @ts-ignore */} 68 | 69 | {/* 70 | // @ts-ignore */ /* eslint-disable-line */ /* eslint-disable-next-line prettier/prettier */} 71 | 72 | {/* 73 | // @ts-ignore */} 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | {/* eslint-disable-next-line prettier/prettier */} 83 | 84 | {/* eslint-disable-next-line prettier/prettier */} 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 129 | 130 | 131 | 132 | 133 | ); 134 | }; 135 | 136 | export default Cropper; 137 | -------------------------------------------------------------------------------- /src/Main.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import CropperPage from './Cropper/Cropper.page'; 3 | import { DefaultFooter } from './common'; 4 | import { SCREEN_WIDTH, SCREEN_HEIGHT } from './constants'; 5 | 6 | export type AmazingCropperProps = { 7 | footerComponent?: JSX.Element; 8 | onDone: (croppedImageUri: string, garbageUris: string[]) => void; 9 | onError: (err: Error) => void; 10 | onCancel: () => void; 11 | imageUri: string; 12 | imageWidth: number; 13 | imageHeight: number; 14 | TOP_VALUE?: number; 15 | LEFT_VALUE?: number; 16 | BOTTOM_VALUE?: number; 17 | RIGHT_VALUE?: number; 18 | initialRotation?: number; 19 | NOT_SELECTED_AREA_OPACITY?: number; 20 | BORDER_WIDTH?: number; 21 | COMPONENT_WIDTH?: number; 22 | COMPONENT_HEIGHT?: number; 23 | } & typeof defaultProps; 24 | 25 | const defaultProps = { 26 | footerComponent: , 27 | onDone: (_croppedImageUri: string, _garbageUris: string[]) => {}, 28 | onError: (_err: Error) => {}, 29 | onCancel: () => {}, 30 | imageUri: '', 31 | imageWidth: 1280, 32 | imageHeight: 747, 33 | TOP_VALUE: 0, 34 | LEFT_VALUE: 0, 35 | BOTTOM_VALUE: 0, 36 | RIGHT_VALUE: 0, 37 | initialRotation: 0, 38 | NOT_SELECTED_AREA_OPACITY: 0.5, 39 | BORDER_WIDTH: 50, 40 | COMPONENT_WIDTH: SCREEN_WIDTH, 41 | COMPONENT_HEIGHT: SCREEN_HEIGHT, 42 | }; 43 | 44 | class Main extends Component { 45 | static defaultProps = defaultProps; 46 | 47 | render() { 48 | return ( 49 | 67 | ); 68 | } 69 | } 70 | 71 | export default Main; 72 | -------------------------------------------------------------------------------- /src/common/DefaultFooter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; 3 | 4 | export type DefaultFooterProps = { 5 | onDone?: () => any; 6 | onRotate?: () => any; 7 | onCancel?: () => any; 8 | doneText: string; 9 | rotateText: string; 10 | cancelText: string; 11 | }; 12 | 13 | const DefaultFooter: React.FC = props => ( 14 | 15 | 16 | {props.cancelText} 17 | 18 | 19 | {props.rotateText} 20 | 21 | 22 | {props.doneText} 23 | 24 | 25 | ); 26 | 27 | export { DefaultFooter }; 28 | 29 | const styles = StyleSheet.create({ 30 | buttonsContainer: { 31 | flexDirection: 'row', 32 | alignItems: 'center', 33 | justifyContent: 'space-between', 34 | height: '100%', 35 | }, 36 | text: { 37 | color: 'white', 38 | fontSize: 16, 39 | }, 40 | touchable: { 41 | padding: 10, 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /src/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DefaultFooter'; 2 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | import { Dimensions } from 'react-native'; 2 | 3 | export const SCREEN_WIDTH = Dimensions.get('window').width; 4 | export const SCREEN_HEIGHT = Dimensions.get('window').height; 5 | export const Q = 100; // buttons container height 6 | -------------------------------------------------------------------------------- /src/utils/cropper.ts: -------------------------------------------------------------------------------- 1 | function getCropperLimitsIfHorizontally( 2 | imageWidth: number, 3 | imageHeight: number, 4 | W_INT: number, 5 | H_INT: number, 6 | _W: number, 7 | _H: number, 8 | BW: number, 9 | Q: number, 10 | ) { 11 | let TOP_LIMIT = 0; 12 | let LEFT_LIMIT = 0; 13 | let BOTTOM_LIMIT = 0; 14 | let RIGHT_LIMIT = 0; 15 | const DIFF = 0; 16 | 17 | let w = 0; 18 | let h = 0; 19 | let DIST = 0; 20 | let TOTAL_DIST = 0; 21 | 22 | const w1 = W_INT; 23 | const h1 = (W_INT * imageHeight) / imageWidth; 24 | const w2 = (H_INT * imageWidth) / imageHeight; 25 | const h2 = H_INT; 26 | if (h1 <= H_INT) { 27 | h = h1; 28 | w = w1; 29 | DIST = (H_INT - h) / 2; 30 | TOTAL_DIST = DIST + BW; 31 | TOP_LIMIT = TOTAL_DIST; 32 | BOTTOM_LIMIT = TOTAL_DIST + Q; 33 | LEFT_LIMIT = BW; 34 | RIGHT_LIMIT = BW; 35 | } else { 36 | h = h2; 37 | w = w2; 38 | DIST = (W_INT - w) / 2; 39 | TOTAL_DIST = DIST + BW; 40 | TOP_LIMIT = BW; 41 | BOTTOM_LIMIT = BW + Q; 42 | LEFT_LIMIT = TOTAL_DIST; 43 | RIGHT_LIMIT = TOTAL_DIST; 44 | } 45 | 46 | return { TOP_LIMIT, LEFT_LIMIT, BOTTOM_LIMIT, RIGHT_LIMIT, DIFF }; 47 | } 48 | 49 | function getCropperLimitsIfVertically( 50 | imageWidth: number, 51 | imageHeight: number, 52 | W_INT: number, 53 | H_INT: number, 54 | W: number, 55 | H: number, 56 | _BW: number, 57 | Q: number, 58 | ) { 59 | let TOP_LIMIT = 0; 60 | let LEFT_LIMIT = 0; 61 | let BOTTOM_LIMIT = 0; 62 | let RIGHT_LIMIT = 0; 63 | let DIFF = 0; 64 | 65 | let IMAGE_W = 0; 66 | let IMAGE_H = 0; 67 | const IMAGE_W_1 = W_INT; 68 | const IMAGE_H_1 = (W_INT * imageHeight) / imageWidth; 69 | const IMAGE_W_2 = (H_INT * imageWidth) / imageHeight; 70 | const IMAGE_H_2 = H_INT; 71 | if (IMAGE_H_1 <= H_INT) { 72 | IMAGE_H = IMAGE_H_1; 73 | IMAGE_W = IMAGE_W_1; 74 | } else { 75 | IMAGE_H = IMAGE_H_2; 76 | IMAGE_W = IMAGE_W_2; 77 | } 78 | 79 | let w = 0; 80 | let h = 0; 81 | const h1 = W_INT; 82 | const w1 = (IMAGE_W * h1) / IMAGE_H; 83 | const w2 = H_INT; 84 | const h2 = (IMAGE_H * w2) / IMAGE_W; 85 | if (w1 <= H_INT) { 86 | w = w1; 87 | h = h1; 88 | } else { 89 | w = w2; 90 | h = h2; 91 | } 92 | const Tnew = (H - h) / 2; 93 | const Bnew = Tnew + Q; 94 | const Lnew = (W - w) / 2; 95 | const Rnew = Lnew; 96 | DIFF = h - w; 97 | TOP_LIMIT = Tnew + DIFF / 2; 98 | LEFT_LIMIT = Lnew - DIFF / 2; 99 | BOTTOM_LIMIT = Bnew + DIFF / 2; 100 | RIGHT_LIMIT = Rnew - DIFF / 2; 101 | 102 | return { TOP_LIMIT, LEFT_LIMIT, BOTTOM_LIMIT, RIGHT_LIMIT, DIFF }; 103 | } 104 | 105 | function getCropperLimits( 106 | imageWidth: number, 107 | imageHeight: number, 108 | rotation: number, 109 | W_INT: number, 110 | H_INT: number, 111 | W: number, 112 | H: number, 113 | BW: number, 114 | Q: number, 115 | ) { 116 | if (rotation % 180 === 0) { 117 | return getCropperLimitsIfHorizontally(imageWidth, imageHeight, W_INT, H_INT, W, H, BW, Q); 118 | } 119 | return getCropperLimitsIfVertically(imageWidth, imageHeight, W_INT, H_INT, W, H, BW, Q); 120 | } 121 | 122 | export { getCropperLimits }; 123 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cropper'; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "compilerOptions": { 4 | /* Basic Options */ 5 | "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 6 | "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | "lib": ["es6"], /* Specify library files to be included in the compilation. */ 8 | "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | "jsx": "react-native", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "dist", /* Redirect output structure to the directory. */ 15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "removeComments": true, /* Do not emit comments to output. */ 17 | // "noEmit": true, /* Do not emit outputs. */ 18 | "incremental": true, /* Enable incremental compilation */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true, /* Enable all strict type-checking options. */ 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | 32 | /* Additional Checks */ 33 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 37 | 38 | /* Module Resolution Options */ 39 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | // "types": [], /* Type declaration files to be included in compilation. */ 45 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 46 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | 49 | /* Source Map Options */ 50 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | 55 | /* Experimental Options */ 56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 58 | }, 59 | "exclude": [ 60 | "node_modules", "dist", "babel.config.js", "metro.config.js", "jest.config.js" 61 | ] 62 | } 63 | --------------------------------------------------------------------------------