├── .circleci
└── config.yml
├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ └── lint.yml
├── .gitignore
├── .prettierrc
├── .yarnrc
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── babel.config.js
├── example
├── .expo-shared
│ └── assets.json
├── .gitignore
├── App.tsx
├── app.json
├── assets
│ ├── adaptive-icon.png
│ ├── favicon.png
│ ├── icon.png
│ └── splash.png
├── babel.config.js
├── metro.config.js
├── package-lock.json
├── package.json
├── style.ts
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
├── lib
├── commonjs
│ ├── ReactNativeZoomableView.js
│ ├── ReactNativeZoomableView.js.map
│ ├── animations
│ │ ├── index.js
│ │ └── index.js.map
│ ├── assets
│ │ └── pin.png
│ ├── components
│ │ ├── AnimatedTouchFeedback.js
│ │ ├── AnimatedTouchFeedback.js.map
│ │ ├── StaticPin.js
│ │ ├── StaticPin.js.map
│ │ ├── index.js
│ │ └── index.js.map
│ ├── debugHelper
│ │ ├── index.js
│ │ └── index.js.map
│ ├── helper
│ │ ├── applyPanBoundariesToOffset.js
│ │ ├── applyPanBoundariesToOffset.js.map
│ │ ├── calcNewScaledOffsetForZoomCentering.js
│ │ ├── calcNewScaledOffsetForZoomCentering.js.map
│ │ ├── coordinateConversion.js
│ │ ├── coordinateConversion.js.map
│ │ ├── index.js
│ │ └── index.js.map
│ ├── index.js
│ ├── index.js.map
│ └── typings
│ │ ├── index.js
│ │ └── index.js.map
├── module
│ ├── ReactNativeZoomableView.js
│ ├── ReactNativeZoomableView.js.map
│ ├── animations
│ │ ├── index.js
│ │ └── index.js.map
│ ├── assets
│ │ └── pin.png
│ ├── components
│ │ ├── AnimatedTouchFeedback.js
│ │ ├── AnimatedTouchFeedback.js.map
│ │ ├── StaticPin.js
│ │ ├── StaticPin.js.map
│ │ ├── index.js
│ │ └── index.js.map
│ ├── debugHelper
│ │ ├── index.js
│ │ └── index.js.map
│ ├── helper
│ │ ├── applyPanBoundariesToOffset.js
│ │ ├── applyPanBoundariesToOffset.js.map
│ │ ├── calcNewScaledOffsetForZoomCentering.js
│ │ ├── calcNewScaledOffsetForZoomCentering.js.map
│ │ ├── coordinateConversion.js
│ │ ├── coordinateConversion.js.map
│ │ ├── index.js
│ │ └── index.js.map
│ ├── index.js
│ ├── index.js.map
│ └── typings
│ │ ├── index.js
│ │ └── index.js.map
└── typescript
│ ├── ReactNativeZoomableView.d.ts
│ ├── __tests__
│ └── index.test.d.ts
│ ├── animations
│ └── index.d.ts
│ ├── components
│ ├── AnimatedTouchFeedback.d.ts
│ ├── StaticPin.d.ts
│ └── index.d.ts
│ ├── debugHelper
│ └── index.d.ts
│ ├── helper
│ ├── applyPanBoundariesToOffset.d.ts
│ ├── calcNewScaledOffsetForZoomCentering.d.ts
│ ├── coordinateConversion.d.ts
│ └── index.d.ts
│ ├── index.d.ts
│ └── typings
│ └── index.d.ts
├── package.json
├── scripts
└── bootstrap.js
├── src
├── ReactNativeZoomableView.tsx
├── __tests__
│ └── index.test.tsx
├── animations
│ └── index.ts
├── assets
│ └── pin.png
├── components
│ ├── AnimatedTouchFeedback.tsx
│ ├── StaticPin.tsx
│ └── index.tsx
├── debugHelper
│ └── index.tsx
├── helper
│ ├── applyPanBoundariesToOffset.ts
│ ├── calcNewScaledOffsetForZoomCentering.ts
│ ├── coordinateConversion.ts
│ └── index.ts
├── index.tsx
└── typings
│ └── index.ts
├── tsconfig.build.json
├── tsconfig.json
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | executors:
4 | default:
5 | docker:
6 | - image: circleci/node:10
7 | working_directory: ~/project
8 |
9 | commands:
10 | attach_project:
11 | steps:
12 | - attach_workspace:
13 | at: ~/project
14 |
15 | jobs:
16 | install-dependencies:
17 | executor: default
18 | steps:
19 | - checkout
20 | - attach_project
21 | - restore_cache:
22 | keys:
23 | - dependencies-{{ checksum "package.json" }}
24 | - dependencies-
25 | - restore_cache:
26 | keys:
27 | - dependencies-example-{{ checksum "example/package.json" }}
28 | - dependencies-example-
29 | - run:
30 | name: Install dependencies
31 | command: |
32 | yarn install --cwd example --frozen-lockfile
33 | yarn install --frozen-lockfile
34 | - save_cache:
35 | key: dependencies-{{ checksum "package.json" }}
36 | paths: node_modules
37 | - save_cache:
38 | key: dependencies-example-{{ checksum "example/package.json" }}
39 | paths: example/node_modules
40 | - persist_to_workspace:
41 | root: .
42 | paths: .
43 |
44 | lint:
45 | executor: default
46 | steps:
47 | - attach_project
48 | - run:
49 | name: Lint files
50 | command: |
51 | yarn lint
52 |
53 | typescript:
54 | executor: default
55 | steps:
56 | - attach_project
57 | - run:
58 | name: Typecheck files
59 | command: |
60 | yarn typescript
61 |
62 | unit-tests:
63 | executor: default
64 | steps:
65 | - attach_project
66 | - run:
67 | name: Run unit tests
68 | command: |
69 | yarn test --coverage
70 | - store_artifacts:
71 | path: coverage
72 | destination: coverage
73 |
74 | build-package:
75 | executor: default
76 | steps:
77 | - attach_project
78 | - run:
79 | name: Build package
80 | command: |
81 | yarn prepare
82 |
83 | workflows:
84 | build-and-test:
85 | jobs:
86 | - install-dependencies
87 | - lint:
88 | requires:
89 | - install-dependencies
90 | - typescript:
91 | requires:
92 | - install-dependencies
93 | - unit-tests:
94 | requires:
95 | - install-dependencies
96 | - build-package:
97 | requires:
98 | - install-dependencies
99 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | indent_style = space
10 | indent_size = 2
11 |
12 | end_of_line = lf
13 | charset = utf-8
14 | trim_trailing_whitespace = true
15 | insert_final_newline = true
16 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 | # specific for windows script files
3 | *.bat text eol=crlf
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Checks
2 | on: push
3 | jobs:
4 | build:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/checkout@v2
8 | - name: Install modules
9 | run: yarn
10 | - name: Run tsc
11 | run: yarn run typescript
12 | - name: Run ESLint
13 | run: yarn run lint
14 | - name: Run tests
15 | run: yarn run test
16 |
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # XDE
6 | .expo/
7 |
8 | # VSCode
9 | .vscode/
10 | jsconfig.json
11 |
12 | # Xcode
13 | #
14 | build/
15 | *.pbxuser
16 | !default.pbxuser
17 | *.mode1v3
18 | !default.mode1v3
19 | *.mode2v3
20 | !default.mode2v3
21 | *.perspectivev3
22 | !default.perspectivev3
23 | xcuserdata
24 | *.xccheckout
25 | *.moved-aside
26 | DerivedData
27 | *.hmap
28 | *.ipa
29 | *.xcuserstate
30 | project.xcworkspace
31 |
32 | # Android/IJ
33 | #
34 | .idea
35 | .gradle
36 | local.properties
37 | android.iml
38 |
39 | # Cocoapods
40 | #
41 | example/ios/Pods
42 |
43 | # node.js
44 | #
45 | node_modules/
46 | npm-debug.log
47 | yarn-debug.log
48 | yarn-error.log
49 |
50 | # BUCK
51 | buck-out/
52 | \.buckd/
53 | android/app/libs
54 | android/keystores/debug.keystore
55 |
56 | # Expo
57 | .expo/*
58 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "endOfLine": "crlf",
4 | "quoteProps": "consistent",
5 | "singleQuote": true,
6 | "trailingComma": "all"
7 | }
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | # Override Yarn command so we can automatically setup the repo on running `yarn`
2 |
3 | yarn-path "scripts/bootstrap.js"
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project.
4 |
5 | ## Development workflow
6 |
7 | To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
8 |
9 | ```sh
10 | yarn
11 | ```
12 |
13 | > While it's possible to use [`npm`](https://github.com/npm/cli), the tooling is built around [`yarn`](https://classic.yarnpkg.com/), so you'll have an easier time if you use `yarn` for development.
14 |
15 | While developing, you can run the [example app](/example/) to test your changes. Any changes you make in your library's JavaScript code will be reflected in the example app without a rebuild. If you change any native code, then you'll need to rebuild the example app.
16 |
17 | To start the packager:
18 |
19 | ```sh
20 | yarn example start
21 | ```
22 |
23 | To run the example app on Android:
24 |
25 | ```sh
26 | yarn example android
27 | ```
28 |
29 | To run the example app on iOS:
30 |
31 | ```sh
32 | yarn example ios
33 | ```
34 |
35 | To run the example app on Web:
36 |
37 | ```sh
38 | yarn example web
39 | ```
40 |
41 | Make sure your code passes TypeScript and ESLint. Run the following to verify:
42 |
43 | ```sh
44 | yarn typescript
45 | yarn lint
46 | ```
47 |
48 | To fix formatting errors, run the following:
49 |
50 | ```sh
51 | yarn lint --fix
52 | ```
53 |
54 | Remember to add tests for your change if possible. Run the unit tests by:
55 |
56 | ```sh
57 | yarn test
58 | ```
59 |
60 | ### Commit message convention
61 |
62 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
63 |
64 | - `fix`: bug fixes, e.g. fix crash due to deprecated method.
65 | - `feat`: new features, e.g. add new method to the module.
66 | - `refactor`: code refactor, e.g. migrate from class components to hooks.
67 | - `docs`: changes into documentation, e.g. add usage example for the module..
68 | - `test`: adding or updating tests, e.g. add integration tests using detox.
69 | - `chore`: tooling changes, e.g. change CI config.
70 |
71 | Our pre-commit hooks verify that your commit message matches this format when committing.
72 |
73 | ### Linting and tests
74 |
75 | [ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
76 |
77 | We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
78 |
79 | Our pre-commit hooks verify that the linter and tests pass when committing.
80 |
81 | ### Publishing to npm
82 |
83 | We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc.
84 |
85 | To publish new versions, run the following:
86 |
87 | ```sh
88 | yarn release
89 | ```
90 |
91 | ### Scripts
92 |
93 | The `package.json` file contains various scripts for common tasks:
94 |
95 | - `yarn bootstrap`: setup project by installing all dependencies and pods.
96 | - `yarn typescript`: type-check files with TypeScript.
97 | - `yarn lint`: lint files with ESLint.
98 | - `yarn test`: run unit tests with Jest.
99 | - `yarn example start`: start the Metro server for the example app.
100 | - `yarn example android`: run the example app on Android.
101 | - `yarn example ios`: run the example app on iOS.
102 |
103 | ### Sending a pull request
104 |
105 | > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
106 |
107 | When you're sending a pull request:
108 |
109 | - Prefer small pull requests focused on one change.
110 | - Verify that linters and tests are passing.
111 | - Review the documentation to make sure it looks good.
112 | - Follow the pull request template when opening a pull request.
113 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
114 |
115 | ## Code of Conduct
116 |
117 | ### Our Pledge
118 |
119 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
120 |
121 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
122 |
123 | ### Our Standards
124 |
125 | Examples of behavior that contributes to a positive environment for our community include:
126 |
127 | - Demonstrating empathy and kindness toward other people
128 | - Being respectful of differing opinions, viewpoints, and experiences
129 | - Giving and gracefully accepting constructive feedback
130 | - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
131 | - Focusing on what is best not just for us as individuals, but for the overall community
132 |
133 | Examples of unacceptable behavior include:
134 |
135 | - The use of sexualized language or imagery, and sexual attention or
136 | advances of any kind
137 | - Trolling, insulting or derogatory comments, and personal or political attacks
138 | - Public or private harassment
139 | - Publishing others' private information, such as a physical or email
140 | address, without their explicit permission
141 | - Other conduct which could reasonably be considered inappropriate in a
142 | professional setting
143 |
144 | ### Enforcement Responsibilities
145 |
146 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
147 |
148 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
149 |
150 | ### Scope
151 |
152 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
153 |
154 | ### Enforcement
155 |
156 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly.
157 |
158 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
159 |
160 | ### Enforcement Guidelines
161 |
162 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
163 |
164 | #### 1. Correction
165 |
166 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
167 |
168 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
169 |
170 | #### 2. Warning
171 |
172 | **Community Impact**: A violation through a single incident or series of actions.
173 |
174 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
175 |
176 | #### 3. Temporary Ban
177 |
178 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
179 |
180 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
181 |
182 | #### 4. Permanent Ban
183 |
184 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
185 |
186 | **Consequence**: A permanent ban from any sort of public interaction within the community.
187 |
188 | ### Attribution
189 |
190 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
191 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
192 |
193 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
194 |
195 | [homepage]: https://www.contributor-covenant.org
196 |
197 | For answers to common questions about this code of conduct, see the FAQ at
198 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
199 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 DuAgentur
4 | Copyright (c) 2021 Open Space Labs, Inc.
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/example/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
4 | }
5 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | dist/
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 |
13 | # macOS
14 | .DS_Store
15 |
--------------------------------------------------------------------------------
/example/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useRef, useState } from 'react';
2 | import { View, Text, Image, Animated, Button } from 'react-native';
3 |
4 | import { ReactNativeZoomableView } from '@openspacelabs/react-native-zoomable-view';
5 | import { styles } from './style';
6 | import { debounce } from 'lodash';
7 | import { applyContainResizeMode } from '../src/helper/coordinateConversion';
8 |
9 | const kittenSize = 800;
10 | const uri = `https://placekitten.com/${kittenSize}/${kittenSize}`;
11 | const imageSize = { width: kittenSize, height: kittenSize };
12 |
13 | const stringifyPoint = (point?: { x: number; y: number }) =>
14 | point ? `${Math.round(point.x)}, ${Math.round(point.y)}` : 'Off map';
15 |
16 | export default function App() {
17 | const zoomAnimatedValue = useRef(new Animated.Value(1)).current;
18 | const scale = Animated.divide(1, zoomAnimatedValue);
19 | const [showMarkers, setShowMarkers] = useState(true);
20 | const [size, setSize] = useState<{ width: number; height: number }>({
21 | width: 0,
22 | height: 0,
23 | });
24 |
25 | // Use layout event to get centre point, to set the pin
26 | const [pin, setPin] = useState({ x: 0, y: 0 });
27 | const [movePin, setMovePin] = useState({ x: 0, y: 0 });
28 |
29 | // Debounce the change event to avoid layout event firing too often while dragging
30 | const debouncedUpdatePin = useCallback(() => debounce(setPin, 10), [])();
31 | const debouncedUpdateMovePin = useCallback(
32 | () => debounce(setMovePin, 10),
33 | []
34 | )();
35 |
36 | const staticPinPosition = { x: size.width / 2, y: size.height / 2 };
37 | const { size: contentSize } = applyContainResizeMode(imageSize, size);
38 |
39 | return (
40 |
41 | ReactNativeZoomableView
42 | {
45 | setSize(e.nativeEvent.layout);
46 | }}
47 | >
48 |
65 |
66 |
67 |
68 | {showMarkers &&
69 | ['20%', '40%', '60%', '80%'].map((left) =>
70 | ['20%', '40%', '60%', '80%'].map((top) => (
71 |
80 | ))
81 | )}
82 |
83 |
84 |
85 | onStaticPinPositionChange: {stringifyPoint(pin)}
86 | onStaticPinPositionMove: {stringifyPoint(movePin)}
87 |
94 | );
95 | }
96 |
--------------------------------------------------------------------------------
/example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "@openspacelabs/react-native-zoomable-view-example",
4 | "slug": "openspacelabs-react-native-zoomable-view-example",
5 | "version": "2.0.1",
6 | "orientation": "portrait",
7 | "userInterfaceStyle": "light",
8 | "splash": {
9 | "image": "./assets/splash.png",
10 | "resizeMode": "contain",
11 | "backgroundColor": "#ffffff"
12 | },
13 | "updates": {
14 | "fallbackToCacheTimeout": 0
15 | },
16 | "assetBundlePatterns": [
17 | "**/*"
18 | ],
19 | "ios": {
20 | "supportsTablet": true
21 | },
22 | "android": {
23 | "adaptiveIcon": {
24 | "foregroundImage": "./assets/adaptive-icon.png",
25 | "backgroundColor": "#FFFFFF"
26 | }
27 | },
28 | "web": {
29 | "favicon": "./assets/favicon.png"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/example/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openspacelabs/react-native-zoomable-view/9a4219bfd5250bea2995309ac847b57de0e3b3ae/example/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/example/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openspacelabs/react-native-zoomable-view/9a4219bfd5250bea2995309ac847b57de0e3b3ae/example/assets/favicon.png
--------------------------------------------------------------------------------
/example/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openspacelabs/react-native-zoomable-view/9a4219bfd5250bea2995309ac847b57de0e3b3ae/example/assets/icon.png
--------------------------------------------------------------------------------
/example/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openspacelabs/react-native-zoomable-view/9a4219bfd5250bea2995309ac847b57de0e3b3ae/example/assets/splash.png
--------------------------------------------------------------------------------
/example/babel.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const pak = require('../package.json');
3 |
4 | module.exports = function (api) {
5 | api.cache(true);
6 |
7 | return {
8 | presets: ['babel-preset-expo'],
9 | plugins: [
10 | [
11 | 'module-resolver',
12 | {
13 | extensions: ['.tsx', '.ts', '.js', '.json'],
14 | alias: {
15 | // For development, we want to alias the library to the source
16 | [pak.name]: path.join(__dirname, '..', pak.source),
17 | },
18 | },
19 | ],
20 | ],
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/example/metro.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const blacklist = require('metro-config/src/defaults/exclusionList');
3 | const escape = require('escape-string-regexp');
4 | const pak = require('../package.json');
5 |
6 | const root = path.resolve(__dirname, '..');
7 |
8 | const modules = Object.keys({
9 | ...pak.peerDependencies,
10 | });
11 |
12 | module.exports = {
13 | projectRoot: __dirname,
14 | watchFolders: [root],
15 |
16 | // We need to make sure that only one version is loaded for peerDependencies
17 | // So we blacklist them at the root, and alias them to the versions in example's node_modules
18 | resolver: {
19 | blacklistRE: blacklist(
20 | modules.map(
21 | (m) =>
22 | new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`)
23 | )
24 | ),
25 |
26 | extraNodeModules: modules.reduce((acc, name) => {
27 | acc[name] = path.join(__dirname, 'node_modules', name);
28 | return acc;
29 | }, {}),
30 | },
31 |
32 | transformer: {
33 | getTransformOptions: async () => ({
34 | transform: {
35 | experimentalImportSupport: false,
36 | inlineRequires: true,
37 | },
38 | }),
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@openspacelabs/react-native-zoomable-view-example",
3 | "description": "Example app for @openspacelabs/react-native-zoomable-view",
4 | "version": "0.0.1",
5 | "private": true,
6 | "main": "node_modules/expo/AppEntry.js",
7 | "scripts": {
8 | "start": "expo start",
9 | "android": "expo start --android",
10 | "ios": "expo start --ios",
11 | "web": "expo start --web"
12 | },
13 | "dependencies": {
14 | "expo": "^48.0.0",
15 | "expo-status-bar": "~1.4.4",
16 | "lodash": "^4.17.21",
17 | "react": "18.2.0",
18 | "react-dom": "18.2.0",
19 | "react-native": "0.71.3",
20 | "react-native-web": "~0.18.11"
21 | },
22 | "devDependencies": {
23 | "@babel/core": "^7.20.0",
24 | "babel-plugin-module-resolver": "^4.0.0",
25 | "typescript": "^4.9.4"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/example/style.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export const styles = StyleSheet.create({
4 | box: {
5 | borderWidth: 5,
6 | flexShrink: 1,
7 | height: 600,
8 | width: 480,
9 | },
10 | container: {
11 | alignItems: 'center',
12 | flex: 1,
13 | justifyContent: 'center',
14 | padding: 20,
15 | },
16 | contents: {
17 | alignSelf: 'stretch',
18 | flex: 1,
19 | },
20 | img: {
21 | height: '100%',
22 | resizeMode: 'contain',
23 | width: '100%',
24 | },
25 | marker: {
26 | backgroundColor: 'white',
27 | borderRadius: 10,
28 | borderWidth: 2,
29 | height: 20,
30 | left: '50%',
31 | marginLeft: -10,
32 | marginTop: -10,
33 | position: 'absolute',
34 | top: '50%',
35 | width: 20,
36 | },
37 | });
38 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react-native",
4 | "target": "esnext",
5 | "lib": [
6 | "esnext"
7 | ],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "noEmit": true,
11 | "allowSyntheticDefaultImports": true,
12 | "resolveJsonModule": true,
13 | "esModuleInterop": true,
14 | "moduleResolution": "node"
15 | },
16 | "extends": "expo/tsconfig.base"
17 | }
18 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const createExpoWebpackConfigAsync = require('@expo/webpack-config');
3 | const { resolver } = require('./metro.config');
4 |
5 | const root = path.resolve(__dirname, '..');
6 | const node_modules = path.join(__dirname, 'node_modules');
7 |
8 | module.exports = async function (env, argv) {
9 | const config = await createExpoWebpackConfigAsync(env, argv);
10 |
11 | config.module.rules.push({
12 | test: /\.(js|jsx|ts|tsx)$/,
13 | include: path.resolve(root, 'src'),
14 | use: 'babel-loader',
15 | });
16 |
17 | // We need to make sure that only one version is loaded for peerDependencies
18 | // So we alias them to the versions in example's node_modules
19 | Object.assign(config.resolve.alias, {
20 | ...resolver.extraNodeModules,
21 | 'react-native-web': path.join(node_modules, 'react-native-web'),
22 | });
23 |
24 | return config;
25 | };
26 |
--------------------------------------------------------------------------------
/lib/commonjs/animations/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.getBoundaryCrossedAnim = getBoundaryCrossedAnim;
7 | exports.getPanMomentumDecayAnim = getPanMomentumDecayAnim;
8 | exports.getZoomToAnimation = getZoomToAnimation;
9 |
10 | var _reactNative = require("react-native");
11 |
12 | function getBoundaryCrossedAnim(animValue, toValue) {
13 | return _reactNative.Animated.spring(animValue, {
14 | overshootClamping: true,
15 | toValue,
16 | useNativeDriver: true
17 | });
18 | }
19 |
20 | function getPanMomentumDecayAnim(animValue, velocity) {
21 | return _reactNative.Animated.decay(animValue, {
22 | velocity,
23 | deceleration: 0.994,
24 | useNativeDriver: true
25 | });
26 | }
27 |
28 | function getZoomToAnimation(animValue, toValue) {
29 | return _reactNative.Animated.timing(animValue, {
30 | easing: _reactNative.Easing.out(_reactNative.Easing.ease),
31 | toValue,
32 | useNativeDriver: true
33 | });
34 | }
35 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/lib/commonjs/animations/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.ts"],"names":["getBoundaryCrossedAnim","animValue","toValue","Animated","spring","overshootClamping","useNativeDriver","getPanMomentumDecayAnim","velocity","decay","deceleration","getZoomToAnimation","timing","easing","Easing","out","ease"],"mappings":";;;;;;;;;AAAA;;AAGO,SAASA,sBAAT,CACLC,SADK,EAELC,OAFK,EAGL;AACA,SAAOC,sBAASC,MAAT,CAAgBH,SAAhB,EAA2B;AAChCI,IAAAA,iBAAiB,EAAE,IADa;AAEhCH,IAAAA,OAFgC;AAGhCI,IAAAA,eAAe,EAAE;AAHe,GAA3B,CAAP;AAKD;;AAEM,SAASC,uBAAT,CACLN,SADK,EAELO,QAFK,EAGL;AACA,SAAOL,sBAASM,KAAT,CAAeR,SAAf,EAA0B;AAC/BO,IAAAA,QAD+B;AAE/BE,IAAAA,YAAY,EAAE,KAFiB;AAG/BJ,IAAAA,eAAe,EAAE;AAHc,GAA1B,CAAP;AAKD;;AAEM,SAASK,kBAAT,CAA4BV,SAA5B,EAAuDC,OAAvD,EAAwE;AAC7E,SAAOC,sBAASS,MAAT,CAAgBX,SAAhB,EAA2B;AAChCY,IAAAA,MAAM,EAAEC,oBAAOC,GAAP,CAAWD,oBAAOE,IAAlB,CADwB;AAEhCd,IAAAA,OAFgC;AAGhCI,IAAAA,eAAe,EAAE;AAHe,GAA3B,CAAP;AAKD","sourcesContent":["import { Animated, Easing } from 'react-native';\nimport { Vec2D } from '../typings';\n\nexport function getBoundaryCrossedAnim(\n animValue: Animated.Value,\n toValue: number\n) {\n return Animated.spring(animValue, {\n overshootClamping: true,\n toValue,\n useNativeDriver: true,\n });\n}\n\nexport function getPanMomentumDecayAnim(\n animValue: Animated.Value | Animated.ValueXY,\n velocity: number | Vec2D\n) {\n return Animated.decay(animValue, {\n velocity,\n deceleration: 0.994,\n useNativeDriver: true,\n });\n}\n\nexport function getZoomToAnimation(animValue: Animated.Value, toValue: number) {\n return Animated.timing(animValue, {\n easing: Easing.out(Easing.ease),\n toValue,\n useNativeDriver: true,\n });\n}\n"]}
--------------------------------------------------------------------------------
/lib/commonjs/assets/pin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openspacelabs/react-native-zoomable-view/9a4219bfd5250bea2995309ac847b57de0e3b3ae/lib/commonjs/assets/pin.png
--------------------------------------------------------------------------------
/lib/commonjs/components/AnimatedTouchFeedback.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.AnimatedTouchFeedback = void 0;
7 |
8 | var _react = _interopRequireWildcard(require("react"));
9 |
10 | var _reactNative = require("react-native");
11 |
12 | function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
13 |
14 | function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
15 |
16 | const AnimatedTouchFeedback = ({
17 | x,
18 | y,
19 | animationDelay,
20 | animationDuration,
21 | onAnimationDone
22 | }) => {
23 | const appearDisappearAnimRef = (0, _react.useRef)(new _reactNative.Animated.Value(0));
24 | const onAnimationDoneRef = (0, _react.useRef)(onAnimationDone);
25 | onAnimationDoneRef.current = onAnimationDone;
26 | (0, _react.useEffect)(() => {
27 | appearDisappearAnimRef.current.setValue(0);
28 | const inDuration = animationDuration * 0.8;
29 | const outDuration = animationDuration - inDuration;
30 |
31 | _reactNative.Animated.sequence([_reactNative.Animated.timing(appearDisappearAnimRef.current, {
32 | delay: animationDelay || 0,
33 | toValue: 1,
34 | duration: inDuration,
35 | easing: _reactNative.Easing.linear,
36 | useNativeDriver: true
37 | }), _reactNative.Animated.timing(appearDisappearAnimRef.current, {
38 | toValue: 0,
39 | duration: outDuration,
40 | easing: _reactNative.Easing.out(_reactNative.Easing.ease),
41 | useNativeDriver: true
42 | })]).start(() => {
43 | var _onAnimationDoneRef$c;
44 |
45 | return (_onAnimationDoneRef$c = onAnimationDoneRef.current) === null || _onAnimationDoneRef$c === void 0 ? void 0 : _onAnimationDoneRef$c.call(onAnimationDoneRef);
46 | });
47 | }, [animationDelay, animationDuration]);
48 | return /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, {
49 | pointerEvents: "none",
50 | style: [styles.animatedTouchFeedback, {
51 | opacity: appearDisappearAnimRef.current.interpolate({
52 | inputRange: [0, 1],
53 | outputRange: [0, 0.3]
54 | }),
55 | left: x - 20,
56 | top: y - 20,
57 | transform: [{
58 | scale: appearDisappearAnimRef.current.interpolate({
59 | inputRange: [0, 1],
60 | outputRange: [0.5, 1]
61 | })
62 | }]
63 | }]
64 | });
65 | };
66 |
67 | exports.AnimatedTouchFeedback = AnimatedTouchFeedback;
68 |
69 | const styles = _reactNative.StyleSheet.create({
70 | animatedTouchFeedback: {
71 | backgroundColor: 'lightgray',
72 | borderRadius: 40,
73 | height: 40,
74 | position: 'absolute',
75 | width: 40
76 | }
77 | });
78 | //# sourceMappingURL=AnimatedTouchFeedback.js.map
--------------------------------------------------------------------------------
/lib/commonjs/components/AnimatedTouchFeedback.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["AnimatedTouchFeedback.tsx"],"names":["AnimatedTouchFeedback","x","y","animationDelay","animationDuration","onAnimationDone","appearDisappearAnimRef","Animated","Value","onAnimationDoneRef","current","setValue","inDuration","outDuration","sequence","timing","delay","toValue","duration","easing","Easing","linear","useNativeDriver","out","ease","start","styles","animatedTouchFeedback","opacity","interpolate","inputRange","outputRange","left","top","transform","scale","StyleSheet","create","backgroundColor","borderRadius","height","position","width"],"mappings":";;;;;;;AAAA;;AACA;;;;;;AAEO,MAAMA,qBAAqB,GAAG,CAAC;AACpCC,EAAAA,CADoC;AAEpCC,EAAAA,CAFoC;AAGpCC,EAAAA,cAHoC;AAIpCC,EAAAA,iBAJoC;AAKpCC,EAAAA;AALoC,CAAD,KAY/B;AACJ,QAAMC,sBAAsB,GAAG,mBAAuB,IAAIC,sBAASC,KAAb,CAAmB,CAAnB,CAAvB,CAA/B;AACA,QAAMC,kBAAkB,GAAG,mBAAOJ,eAAP,CAA3B;AACAI,EAAAA,kBAAkB,CAACC,OAAnB,GAA6BL,eAA7B;AAEA,wBAAU,MAAM;AACdC,IAAAA,sBAAsB,CAACI,OAAvB,CAA+BC,QAA/B,CAAwC,CAAxC;AACA,UAAMC,UAAU,GAAGR,iBAAiB,GAAG,GAAvC;AACA,UAAMS,WAAW,GAAGT,iBAAiB,GAAGQ,UAAxC;;AACAL,0BAASO,QAAT,CAAkB,CAChBP,sBAASQ,MAAT,CAAgBT,sBAAsB,CAACI,OAAvC,EAAgD;AAC9CM,MAAAA,KAAK,EAAEb,cAAc,IAAI,CADqB;AAE9Cc,MAAAA,OAAO,EAAE,CAFqC;AAG9CC,MAAAA,QAAQ,EAAEN,UAHoC;AAI9CO,MAAAA,MAAM,EAAEC,oBAAOC,MAJ+B;AAK9CC,MAAAA,eAAe,EAAE;AAL6B,KAAhD,CADgB,EAQhBf,sBAASQ,MAAT,CAAgBT,sBAAsB,CAACI,OAAvC,EAAgD;AAC9CO,MAAAA,OAAO,EAAE,CADqC;AAE9CC,MAAAA,QAAQ,EAAEL,WAFoC;AAG9CM,MAAAA,MAAM,EAAEC,oBAAOG,GAAP,CAAWH,oBAAOI,IAAlB,CAHsC;AAI9CF,MAAAA,eAAe,EAAE;AAJ6B,KAAhD,CARgB,CAAlB,EAcGG,KAdH,CAcS;AAAA;;AAAA,sCAAMhB,kBAAkB,CAACC,OAAzB,0DAAM,2BAAAD,kBAAkB,CAAxB;AAAA,KAdT;AAeD,GAnBD,EAmBG,CAACN,cAAD,EAAiBC,iBAAjB,CAnBH;AAqBA,sBACE,6BAAC,qBAAD,CAAU,IAAV;AACE,IAAA,aAAa,EAAC,MADhB;AAEE,IAAA,KAAK,EAAE,CACLsB,MAAM,CAACC,qBADF,EAEL;AACEC,MAAAA,OAAO,EAAEtB,sBAAsB,CAACI,OAAvB,CAA+BmB,WAA/B,CAA2C;AAClDC,QAAAA,UAAU,EAAE,CAAC,CAAD,EAAI,CAAJ,CADsC;AAElDC,QAAAA,WAAW,EAAE,CAAC,CAAD,EAAI,GAAJ;AAFqC,OAA3C,CADX;AAKEC,MAAAA,IAAI,EAAE/B,CAAC,GAAG,EALZ;AAMEgC,MAAAA,GAAG,EAAE/B,CAAC,GAAG,EANX;AAOEgC,MAAAA,SAAS,EAAE,CACT;AACEC,QAAAA,KAAK,EAAE7B,sBAAsB,CAACI,OAAvB,CAA+BmB,WAA/B,CAA2C;AAChDC,UAAAA,UAAU,EAAE,CAAC,CAAD,EAAI,CAAJ,CADoC;AAEhDC,UAAAA,WAAW,EAAE,CAAC,GAAD,EAAM,CAAN;AAFmC,SAA3C;AADT,OADS;AAPb,KAFK;AAFT,IADF;AAwBD,CA9DM;;;;AAgEP,MAAML,MAAM,GAAGU,wBAAWC,MAAX,CAAkB;AAC/BV,EAAAA,qBAAqB,EAAE;AACrBW,IAAAA,eAAe,EAAE,WADI;AAErBC,IAAAA,YAAY,EAAE,EAFO;AAGrBC,IAAAA,MAAM,EAAE,EAHa;AAIrBC,IAAAA,QAAQ,EAAE,UAJW;AAKrBC,IAAAA,KAAK,EAAE;AALc;AADQ,CAAlB,CAAf","sourcesContent":["import React, { useEffect, useRef } from 'react';\nimport { Animated, Easing, StyleSheet } from 'react-native';\n\nexport const AnimatedTouchFeedback = ({\n x,\n y,\n animationDelay,\n animationDuration,\n onAnimationDone,\n}: {\n x: number;\n y: number;\n animationDuration: number;\n animationDelay?: number;\n onAnimationDone?: () => void;\n}) => {\n const appearDisappearAnimRef = useRef(new Animated.Value(0));\n const onAnimationDoneRef = useRef(onAnimationDone);\n onAnimationDoneRef.current = onAnimationDone;\n\n useEffect(() => {\n appearDisappearAnimRef.current.setValue(0);\n const inDuration = animationDuration * 0.8;\n const outDuration = animationDuration - inDuration;\n Animated.sequence([\n Animated.timing(appearDisappearAnimRef.current, {\n delay: animationDelay || 0,\n toValue: 1,\n duration: inDuration,\n easing: Easing.linear,\n useNativeDriver: true,\n }),\n Animated.timing(appearDisappearAnimRef.current, {\n toValue: 0,\n duration: outDuration,\n easing: Easing.out(Easing.ease),\n useNativeDriver: true,\n }),\n ]).start(() => onAnimationDoneRef.current?.());\n }, [animationDelay, animationDuration]);\n\n return (\n \n );\n};\n\nconst styles = StyleSheet.create({\n animatedTouchFeedback: {\n backgroundColor: 'lightgray',\n borderRadius: 40,\n height: 40,\n position: 'absolute',\n width: 40,\n },\n});\n"]}
--------------------------------------------------------------------------------
/lib/commonjs/components/StaticPin.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.StaticPin = void 0;
7 |
8 | var _react = _interopRequireDefault(require("react"));
9 |
10 | var _reactNative = require("react-native");
11 |
12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13 |
14 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
15 |
16 | const StaticPin = ({
17 | staticPinPosition,
18 | pinAnim,
19 | staticPinIcon,
20 | pinSize,
21 | onParentMove,
22 | onPress,
23 | onLongPress,
24 | setPinSize,
25 | pinProps = {}
26 | }) => {
27 | const tapTime = _react.default.useRef(0);
28 |
29 | const transform = [{
30 | translateY: -pinSize.height
31 | }, {
32 | translateX: -pinSize.width / 2
33 | }, ...pinAnim.getTranslateTransform()];
34 | const opacity = pinSize.width && pinSize.height ? 1 : 0;
35 |
36 | const panResponder = _react.default.useRef(_reactNative.PanResponder.create({
37 | onStartShouldSetPanResponder: () => {
38 | tapTime.current = Date.now(); // We want to handle tap on this so set true
39 |
40 | return true;
41 | },
42 | onPanResponderMove: (evt, gestureState) => {
43 | // However if the user moves finger we want to pass this evt to parent
44 | // to handle panning (tap not recognized)
45 | if (Math.abs(gestureState.dx) > 5 && Math.abs(gestureState.dy) > 5) onParentMove(evt, gestureState);
46 | },
47 | onPanResponderRelease: (evt, gestureState) => {
48 | if (Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5) return;
49 | const dt = Date.now() - tapTime.current;
50 |
51 | if (onPress && dt < 500) {
52 | onPress(evt);
53 | }
54 |
55 | if (onLongPress && dt > 500) {
56 | // RN long press is 500ms
57 | onLongPress(evt);
58 | }
59 | }
60 | })).current;
61 |
62 | return /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, _extends({
63 | style: [{
64 | left: staticPinPosition.x,
65 | top: staticPinPosition.y
66 | }, styles.pinWrapper, {
67 | opacity,
68 | transform
69 | }]
70 | }, pinProps), /*#__PURE__*/_react.default.createElement(_reactNative.View, _extends({
71 | onLayout: ({
72 | nativeEvent: {
73 | layout
74 | }
75 | }) => {
76 | setPinSize(layout);
77 | }
78 | }, panResponder.panHandlers), staticPinIcon ||
79 | /*#__PURE__*/
80 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
81 | _react.default.createElement(_reactNative.Image, {
82 | source: require('../assets/pin.png'),
83 | style: styles.pin
84 | })));
85 | };
86 |
87 | exports.StaticPin = StaticPin;
88 |
89 | const styles = _reactNative.StyleSheet.create({
90 | pin: {
91 | height: 64,
92 | width: 48
93 | },
94 | pinWrapper: {
95 | position: 'absolute'
96 | }
97 | });
98 | //# sourceMappingURL=StaticPin.js.map
--------------------------------------------------------------------------------
/lib/commonjs/components/StaticPin.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["StaticPin.tsx"],"names":["StaticPin","staticPinPosition","pinAnim","staticPinIcon","pinSize","onParentMove","onPress","onLongPress","setPinSize","pinProps","tapTime","React","useRef","transform","translateY","height","translateX","width","getTranslateTransform","opacity","panResponder","PanResponder","create","onStartShouldSetPanResponder","current","Date","now","onPanResponderMove","evt","gestureState","Math","abs","dx","dy","onPanResponderRelease","dt","left","x","top","y","styles","pinWrapper","nativeEvent","layout","panHandlers","require","pin","StyleSheet","position"],"mappings":"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","sourcesContent":["import React from 'react';\nimport {\n Animated,\n View,\n Image,\n StyleSheet,\n GestureResponderEvent,\n PanResponderGestureState,\n PanResponder,\n ViewProps,\n} from 'react-native';\nimport { Size2D } from 'src/typings';\n\nexport const StaticPin = ({\n staticPinPosition,\n pinAnim,\n staticPinIcon,\n pinSize,\n onParentMove,\n onPress,\n onLongPress,\n setPinSize,\n pinProps = {},\n}: {\n staticPinPosition: { x: number; y: number };\n pinAnim: Animated.ValueXY;\n staticPinIcon: React.ReactNode;\n pinSize: Size2D;\n /** Internal handler for passing move event to parent */\n onParentMove: (\n evt: GestureResponderEvent,\n gestureState: PanResponderGestureState\n ) => boolean | undefined;\n onPress?: (evt: GestureResponderEvent) => void;\n onLongPress?: (evt: GestureResponderEvent) => void;\n setPinSize: (size: Size2D) => void;\n pinProps?: ViewProps;\n}) => {\n const tapTime = React.useRef(0);\n const transform = [\n { translateY: -pinSize.height },\n { translateX: -pinSize.width / 2 },\n ...pinAnim.getTranslateTransform(),\n ];\n\n const opacity = pinSize.width && pinSize.height ? 1 : 0;\n\n const panResponder = React.useRef(\n PanResponder.create({\n onStartShouldSetPanResponder: () => {\n tapTime.current = Date.now();\n\n // We want to handle tap on this so set true\n return true;\n },\n onPanResponderMove: (evt, gestureState) => {\n // However if the user moves finger we want to pass this evt to parent\n // to handle panning (tap not recognized)\n if (Math.abs(gestureState.dx) > 5 && Math.abs(gestureState.dy) > 5)\n onParentMove(evt, gestureState);\n },\n onPanResponderRelease: (evt, gestureState) => {\n if (Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5)\n return;\n const dt = Date.now() - tapTime.current;\n if (onPress && dt < 500) {\n onPress(evt);\n }\n if (onLongPress && dt > 500) {\n // RN long press is 500ms\n onLongPress(evt);\n }\n },\n })\n ).current;\n\n return (\n \n {\n setPinSize(layout);\n }}\n {...panResponder.panHandlers}\n >\n {staticPinIcon || (\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n \n )}\n \n \n );\n};\n\nconst styles = StyleSheet.create({\n pin: {\n height: 64,\n width: 48,\n },\n pinWrapper: {\n position: 'absolute',\n },\n});\n"]}
--------------------------------------------------------------------------------
/lib/commonjs/components/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | Object.defineProperty(exports, "AnimatedTouchFeedback", {
7 | enumerable: true,
8 | get: function () {
9 | return _AnimatedTouchFeedback.AnimatedTouchFeedback;
10 | }
11 | });
12 |
13 | var _AnimatedTouchFeedback = require("./AnimatedTouchFeedback");
14 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/lib/commonjs/components/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.tsx"],"names":[],"mappings":";;;;;;;;;;;;AAAA","sourcesContent":["export { AnimatedTouchFeedback } from './AnimatedTouchFeedback';\n"]}
--------------------------------------------------------------------------------
/lib/commonjs/debugHelper/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.DebugTouchPoint = exports.DebugRect = void 0;
7 |
8 | var _reactNative = require("react-native");
9 |
10 | var _react = _interopRequireDefault(require("react"));
11 |
12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13 |
14 | const DebugTouchPoint = ({
15 | diameter = 20,
16 | x = 0,
17 | y = 0,
18 | color = 'yellow'
19 | }) => {
20 | const radius = diameter / 2;
21 | return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
22 | style: [styles.debugPoint, {
23 | width: diameter,
24 | height: diameter,
25 | borderRadius: diameter,
26 | backgroundColor: color,
27 | left: x - radius,
28 | top: y - radius
29 | }],
30 | pointerEvents: "none"
31 | });
32 | };
33 |
34 | exports.DebugTouchPoint = DebugTouchPoint;
35 |
36 | const DebugRect = ({
37 | height,
38 | x = 0,
39 | y = 0,
40 | color = 'yellow'
41 | }) => {
42 | const width = 5;
43 | return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
44 | style: [styles.debugRect, {
45 | width,
46 | height,
47 | backgroundColor: color,
48 | left: x - width / 2,
49 | top: y
50 | }],
51 | pointerEvents: "none"
52 | });
53 | };
54 |
55 | exports.DebugRect = DebugRect;
56 |
57 | const styles = _reactNative.StyleSheet.create({
58 | debugPoint: {
59 | opacity: 0.7,
60 | position: 'absolute'
61 | },
62 | debugRect: {
63 | opacity: 0.5,
64 | position: 'absolute'
65 | }
66 | });
67 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/lib/commonjs/debugHelper/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.tsx"],"names":["DebugTouchPoint","diameter","x","y","color","radius","styles","debugPoint","width","height","borderRadius","backgroundColor","left","top","DebugRect","debugRect","StyleSheet","create","opacity","position"],"mappings":";;;;;;;AAAA;;AACA;;;;AAEO,MAAMA,eAAe,GAAG,CAAC;AAC9BC,EAAAA,QAAQ,GAAG,EADmB;AAE9BC,EAAAA,CAAC,GAAG,CAF0B;AAG9BC,EAAAA,CAAC,GAAG,CAH0B;AAI9BC,EAAAA,KAAK,GAAG;AAJsB,CAAD,KAKzB;AACJ,QAAMC,MAAM,GAAGJ,QAAQ,GAAG,CAA1B;AACA,sBACE,6BAAC,iBAAD;AACE,IAAA,KAAK,EAAE,CACLK,MAAM,CAACC,UADF,EAEL;AACEC,MAAAA,KAAK,EAAEP,QADT;AAEEQ,MAAAA,MAAM,EAAER,QAFV;AAGES,MAAAA,YAAY,EAAET,QAHhB;AAIEU,MAAAA,eAAe,EAAEP,KAJnB;AAKEQ,MAAAA,IAAI,EAAEV,CAAC,GAAGG,MALZ;AAMEQ,MAAAA,GAAG,EAAEV,CAAC,GAAGE;AANX,KAFK,CADT;AAYE,IAAA,aAAa,EAAC;AAZhB,IADF;AAgBD,CAvBM;;;;AAwBA,MAAMS,SAAS,GAAG,CAAC;AACxBL,EAAAA,MADwB;AAExBP,EAAAA,CAAC,GAAG,CAFoB;AAGxBC,EAAAA,CAAC,GAAG,CAHoB;AAIxBC,EAAAA,KAAK,GAAG;AAJgB,CAAD,KAUnB;AACJ,QAAMI,KAAK,GAAG,CAAd;AACA,sBACE,6BAAC,iBAAD;AACE,IAAA,KAAK,EAAE,CACLF,MAAM,CAACS,SADF,EAEL;AACEP,MAAAA,KADF;AAEEC,MAAAA,MAFF;AAGEE,MAAAA,eAAe,EAAEP,KAHnB;AAIEQ,MAAAA,IAAI,EAAEV,CAAC,GAAGM,KAAK,GAAG,CAJpB;AAKEK,MAAAA,GAAG,EAAEV;AALP,KAFK,CADT;AAWE,IAAA,aAAa,EAAC;AAXhB,IADF;AAeD,CA3BM;;;;AA6BP,MAAMG,MAAM,GAAGU,wBAAWC,MAAX,CAAkB;AAC/BV,EAAAA,UAAU,EAAE;AACVW,IAAAA,OAAO,EAAE,GADC;AAEVC,IAAAA,QAAQ,EAAE;AAFA,GADmB;AAK/BJ,EAAAA,SAAS,EAAE;AACTG,IAAAA,OAAO,EAAE,GADA;AAETC,IAAAA,QAAQ,EAAE;AAFD;AALoB,CAAlB,CAAf","sourcesContent":["import { View, StyleSheet } from 'react-native';\nimport React from 'react';\n\nexport const DebugTouchPoint = ({\n diameter = 20,\n x = 0,\n y = 0,\n color = 'yellow',\n}) => {\n const radius = diameter / 2;\n return (\n \n );\n};\nexport const DebugRect = ({\n height,\n x = 0,\n y = 0,\n color = 'yellow',\n}: {\n height: number;\n x: number;\n y: number;\n color: string;\n}) => {\n const width = 5;\n return (\n \n );\n};\n\nconst styles = StyleSheet.create({\n debugPoint: {\n opacity: 0.7,\n position: 'absolute',\n },\n debugRect: {\n opacity: 0.5,\n position: 'absolute',\n },\n});\n"]}
--------------------------------------------------------------------------------
/lib/commonjs/helper/applyPanBoundariesToOffset.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.applyPanBoundariesToOffset = applyPanBoundariesToOffset;
7 |
8 | /**
9 | * Takes a single offset value and calculates the correct offset value
10 | * to make sure it's within the pan boundaries
11 | *
12 | *
13 | * @param offsetScaled
14 | * @param containerSize
15 | * @param contentSize
16 | * @param scale
17 | * @param boundaryPadding - see README
18 | *
19 | * @returns {number}
20 | */
21 | function applyPanBoundariesToOffset(offsetScaled, containerSize, contentSize, scale, boundaryPadding) {
22 | const contentSizeUnscaled = contentSize * scale;
23 | const offsetUnscaled = offsetScaled * scale;
24 | const contentStartBorderUnscaled = containerSize / 2 + offsetUnscaled - contentSizeUnscaled / 2;
25 | const contentEndBorderUnscaled = contentStartBorderUnscaled + contentSizeUnscaled;
26 | const containerStartBorder = 0;
27 | const containerEndBorder = containerStartBorder + containerSize; // do not let boundary padding be greater than the container size or less than 0
28 |
29 | if (!boundaryPadding || boundaryPadding < 0) boundaryPadding = 0;
30 | if (boundaryPadding > containerSize) boundaryPadding = containerSize; // Calculate container's measurements with boundary padding applied.
31 | // this should shrink the container's size by the amount of the boundary padding,
32 | // so that the content inside can be panned a bit further away from the original container's boundaries.
33 |
34 | const paddedContainerSize = containerSize - boundaryPadding * 2;
35 | const paddedContainerStartBorder = containerStartBorder + boundaryPadding;
36 | const paddedContainerEndBorder = containerEndBorder - boundaryPadding; // if content is smaller than the padded container,
37 | // don't let the content move
38 |
39 | if (contentSizeUnscaled < paddedContainerSize) {
40 | return 0;
41 | } // if content is larger than the padded container,
42 | // don't let the padded container go outside of content
43 | // maximum distance the content's center can move from its original position.
44 | // assuming the content original center is the container's center.
45 |
46 |
47 | const contentMaxOffsetScaled = (paddedContainerSize / 2 - contentSizeUnscaled / 2) / scale;
48 |
49 | if ( // content reaching the end boundary
50 | contentEndBorderUnscaled < paddedContainerEndBorder) {
51 | return contentMaxOffsetScaled;
52 | }
53 |
54 | if ( // content reaching the start boundary
55 | contentStartBorderUnscaled > paddedContainerStartBorder) {
56 | return -contentMaxOffsetScaled;
57 | }
58 |
59 | return offsetScaled;
60 | }
61 | //# sourceMappingURL=applyPanBoundariesToOffset.js.map
--------------------------------------------------------------------------------
/lib/commonjs/helper/applyPanBoundariesToOffset.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["applyPanBoundariesToOffset.ts"],"names":["applyPanBoundariesToOffset","offsetScaled","containerSize","contentSize","scale","boundaryPadding","contentSizeUnscaled","offsetUnscaled","contentStartBorderUnscaled","contentEndBorderUnscaled","containerStartBorder","containerEndBorder","paddedContainerSize","paddedContainerStartBorder","paddedContainerEndBorder","contentMaxOffsetScaled"],"mappings":";;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASA,0BAAT,CACLC,YADK,EAELC,aAFK,EAGLC,WAHK,EAILC,KAJK,EAKLC,eALK,EAML;AACA,QAAMC,mBAAmB,GAAGH,WAAW,GAAGC,KAA1C;AACA,QAAMG,cAAc,GAAGN,YAAY,GAAGG,KAAtC;AAEA,QAAMI,0BAA0B,GAC9BN,aAAa,GAAG,CAAhB,GAAoBK,cAApB,GAAqCD,mBAAmB,GAAG,CAD7D;AAEA,QAAMG,wBAAwB,GAC5BD,0BAA0B,GAAGF,mBAD/B;AAGA,QAAMI,oBAAoB,GAAG,CAA7B;AACA,QAAMC,kBAAkB,GAAGD,oBAAoB,GAAGR,aAAlD,CAVA,CAYA;;AACA,MAAI,CAACG,eAAD,IAAoBA,eAAe,GAAG,CAA1C,EAA6CA,eAAe,GAAG,CAAlB;AAC7C,MAAIA,eAAe,GAAGH,aAAtB,EAAqCG,eAAe,GAAGH,aAAlB,CAdrC,CAgBA;AACA;AACA;;AACA,QAAMU,mBAAmB,GAAGV,aAAa,GAAGG,eAAe,GAAG,CAA9D;AACA,QAAMQ,0BAA0B,GAAGH,oBAAoB,GAAGL,eAA1D;AACA,QAAMS,wBAAwB,GAAGH,kBAAkB,GAAGN,eAAtD,CArBA,CAuBA;AACA;;AACA,MAAIC,mBAAmB,GAAGM,mBAA1B,EAA+C;AAC7C,WAAO,CAAP;AACD,GA3BD,CA6BA;AACA;AAEA;AACA;;;AACA,QAAMG,sBAAsB,GAC1B,CAACH,mBAAmB,GAAG,CAAtB,GAA0BN,mBAAmB,GAAG,CAAjD,IAAsDF,KADxD;;AAGA,OACE;AACAK,EAAAA,wBAAwB,GAAGK,wBAF7B,EAGE;AACA,WAAOC,sBAAP;AACD;;AACD,OACE;AACAP,EAAAA,0BAA0B,GAAGK,0BAF/B,EAGE;AACA,WAAO,CAACE,sBAAR;AACD;;AAED,SAAOd,YAAP;AACD","sourcesContent":["/**\n * Takes a single offset value and calculates the correct offset value\n * to make sure it's within the pan boundaries\n *\n *\n * @param offsetScaled\n * @param containerSize\n * @param contentSize\n * @param scale\n * @param boundaryPadding - see README\n *\n * @returns {number}\n */\nexport function applyPanBoundariesToOffset(\n offsetScaled: number,\n containerSize: number,\n contentSize: number,\n scale: number,\n boundaryPadding: number\n) {\n const contentSizeUnscaled = contentSize * scale;\n const offsetUnscaled = offsetScaled * scale;\n\n const contentStartBorderUnscaled =\n containerSize / 2 + offsetUnscaled - contentSizeUnscaled / 2;\n const contentEndBorderUnscaled =\n contentStartBorderUnscaled + contentSizeUnscaled;\n\n const containerStartBorder = 0;\n const containerEndBorder = containerStartBorder + containerSize;\n\n // do not let boundary padding be greater than the container size or less than 0\n if (!boundaryPadding || boundaryPadding < 0) boundaryPadding = 0;\n if (boundaryPadding > containerSize) boundaryPadding = containerSize;\n\n // Calculate container's measurements with boundary padding applied.\n // this should shrink the container's size by the amount of the boundary padding,\n // so that the content inside can be panned a bit further away from the original container's boundaries.\n const paddedContainerSize = containerSize - boundaryPadding * 2;\n const paddedContainerStartBorder = containerStartBorder + boundaryPadding;\n const paddedContainerEndBorder = containerEndBorder - boundaryPadding;\n\n // if content is smaller than the padded container,\n // don't let the content move\n if (contentSizeUnscaled < paddedContainerSize) {\n return 0;\n }\n\n // if content is larger than the padded container,\n // don't let the padded container go outside of content\n\n // maximum distance the content's center can move from its original position.\n // assuming the content original center is the container's center.\n const contentMaxOffsetScaled =\n (paddedContainerSize / 2 - contentSizeUnscaled / 2) / scale;\n\n if (\n // content reaching the end boundary\n contentEndBorderUnscaled < paddedContainerEndBorder\n ) {\n return contentMaxOffsetScaled;\n }\n if (\n // content reaching the start boundary\n contentStartBorderUnscaled > paddedContainerStartBorder\n ) {\n return -contentMaxOffsetScaled;\n }\n\n return offsetScaled;\n}\n"]}
--------------------------------------------------------------------------------
/lib/commonjs/helper/calcNewScaledOffsetForZoomCentering.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.calcNewScaledOffsetForZoomCentering = calcNewScaledOffsetForZoomCentering;
7 |
8 | /**
9 | * Calculates the new offset for the zoomSubject to ensure zoom center position is retained after zooming.
10 | * Parameters should correspond to whether we need the offset for the X or Y axis
11 | *
12 | * ## Terms Used:
13 | *
14 | * - Zoom Subject: the view that's being zoomed and panned
15 | * - Zoom Center: the point whose relative position to the window is retained
16 | * - Unscaled: a measurement in pixels denoting the true size as observed by the users' eyes
17 | * - Scaled: a measurement in pixels scaled to the "scale transformation" of the zoom subject to match with its true size.
18 | * *For example:*
19 | * If the scale on the zoom subject is 0.5,
20 | * then to draw an actual 4px line on the zoom subject, we need to scale it to 4px / 0.5 = 8px
21 | * 8px is the scaled measurement
22 | *
23 | * ## Overall idea of this algorithm:
24 | *
25 | * When users perform zooming by pinching the screen,
26 | * we need to shift the zoom subject so that the position of the zoom center is always the same.
27 | * The offset amount to shift the layer is the returned value.
28 | *
29 | *
30 | * ## How we achieve our goal:
31 | *
32 | * To retain, the zoom center position, whenever a zoom action is performed,
33 | * we just need to make sure the distances from all the points in the zoom subject
34 | * to the zoom center increases or decreases by the growth rate of the scale.
35 | *
36 | * ```
37 | * newDistanceAnyPointToZoomCenter = oldDistanceAnyPointToZoomCenter * (newScale/oldScale)
38 | * ```
39 | *
40 | * We can't calculate that for all the points because there are unlimited points on a plain.
41 | * However, due to the way `transform` works in RN, every point is scaled from the zoom subject center.
42 | * Therefore, it's sufficient to base our calculation on the distance from the zoom subject center to the zoom center.
43 | *
44 | * ```
45 | * newDistanceZoomSubjectCenterToZoomCenter = oldDistanceZoomSubjectCenterToZoomCenter * (newScale/oldScale)
46 | * ```
47 | *
48 | * Once we have `newDistanceZoomSubjectCenterToZoomCenter`,
49 | * we can easily calculate the position of the new center, which leads us to the offset amount.
50 | * Refer to the code for more details
51 | *
52 | * @param oldOffsetXOrYScaled
53 | * @param zoomSubjectOriginalWidthOrHeight
54 | * @param oldScale
55 | * @param newScale
56 | * @param zoomCenterXOrY
57 | */
58 | function calcNewScaledOffsetForZoomCentering(oldOffsetXOrYScaled, zoomSubjectOriginalWidthOrHeight, oldScale, newScale, zoomCenterXOrY) {
59 | const oldOffSetUnscaled = oldOffsetXOrYScaled * oldScale;
60 | const growthRate = newScale / oldScale; // these act like namespaces just for the sake of readability
61 |
62 | const zoomSubjectOriginalCenter = {};
63 | const zoomSubjectCurrentCenter = {};
64 | const zoomSubjectNewCenter = {};
65 | zoomSubjectOriginalCenter.xOrY = zoomSubjectOriginalWidthOrHeight / 2;
66 | zoomSubjectCurrentCenter.xOrY = zoomSubjectOriginalCenter.xOrY + oldOffSetUnscaled;
67 | zoomSubjectCurrentCenter.distanceToZoomCenter = zoomSubjectCurrentCenter.xOrY - zoomCenterXOrY;
68 | zoomSubjectNewCenter.distanceToZoomCenter = zoomSubjectCurrentCenter.distanceToZoomCenter * growthRate;
69 | zoomSubjectNewCenter.xOrY = zoomSubjectNewCenter.distanceToZoomCenter + zoomCenterXOrY;
70 | const newOffsetUnscaled = zoomSubjectNewCenter.xOrY - zoomSubjectOriginalCenter.xOrY;
71 | return newOffsetUnscaled / newScale;
72 | }
73 | //# sourceMappingURL=calcNewScaledOffsetForZoomCentering.js.map
--------------------------------------------------------------------------------
/lib/commonjs/helper/calcNewScaledOffsetForZoomCentering.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["calcNewScaledOffsetForZoomCentering.ts"],"names":["calcNewScaledOffsetForZoomCentering","oldOffsetXOrYScaled","zoomSubjectOriginalWidthOrHeight","oldScale","newScale","zoomCenterXOrY","oldOffSetUnscaled","growthRate","zoomSubjectOriginalCenter","zoomSubjectCurrentCenter","zoomSubjectNewCenter","xOrY","distanceToZoomCenter","newOffsetUnscaled"],"mappings":";;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASA,mCAAT,CACLC,mBADK,EAELC,gCAFK,EAGLC,QAHK,EAILC,QAJK,EAKLC,cALK,EAML;AACA,QAAMC,iBAAiB,GAAGL,mBAAmB,GAAGE,QAAhD;AACA,QAAMI,UAAU,GAAGH,QAAQ,GAAGD,QAA9B,CAFA,CAIA;;AACA,QAAMK,yBAAyB,GAAG,EAAlC;AACA,QAAMC,wBAAwB,GAAG,EAAjC;AACA,QAAMC,oBAAoB,GAAG,EAA7B;AAEAF,EAAAA,yBAAyB,CAACG,IAA1B,GAAiCT,gCAAgC,GAAG,CAApE;AACAO,EAAAA,wBAAwB,CAACE,IAAzB,GACEH,yBAAyB,CAACG,IAA1B,GAAiCL,iBADnC;AAEAG,EAAAA,wBAAwB,CAACG,oBAAzB,GACEH,wBAAwB,CAACE,IAAzB,GAAgCN,cADlC;AAGAK,EAAAA,oBAAoB,CAACE,oBAArB,GACEH,wBAAwB,CAACG,oBAAzB,GAAgDL,UADlD;AAEAG,EAAAA,oBAAoB,CAACC,IAArB,GACED,oBAAoB,CAACE,oBAArB,GAA4CP,cAD9C;AAGA,QAAMQ,iBAAiB,GACrBH,oBAAoB,CAACC,IAArB,GAA4BH,yBAAyB,CAACG,IADxD;AAGA,SAAOE,iBAAiB,GAAGT,QAA3B;AACD","sourcesContent":["/**\n * Calculates the new offset for the zoomSubject to ensure zoom center position is retained after zooming.\n * Parameters should correspond to whether we need the offset for the X or Y axis\n *\n * ## Terms Used:\n *\n * - Zoom Subject: the view that's being zoomed and panned\n * - Zoom Center: the point whose relative position to the window is retained\n * - Unscaled: a measurement in pixels denoting the true size as observed by the users' eyes\n * - Scaled: a measurement in pixels scaled to the \"scale transformation\" of the zoom subject to match with its true size.\n * *For example:*\n * If the scale on the zoom subject is 0.5,\n * then to draw an actual 4px line on the zoom subject, we need to scale it to 4px / 0.5 = 8px\n * 8px is the scaled measurement\n *\n * ## Overall idea of this algorithm:\n *\n * When users perform zooming by pinching the screen,\n * we need to shift the zoom subject so that the position of the zoom center is always the same.\n * The offset amount to shift the layer is the returned value.\n *\n *\n * ## How we achieve our goal:\n *\n * To retain, the zoom center position, whenever a zoom action is performed,\n * we just need to make sure the distances from all the points in the zoom subject\n * to the zoom center increases or decreases by the growth rate of the scale.\n *\n * ```\n * newDistanceAnyPointToZoomCenter = oldDistanceAnyPointToZoomCenter * (newScale/oldScale)\n * ```\n *\n * We can't calculate that for all the points because there are unlimited points on a plain.\n * However, due to the way `transform` works in RN, every point is scaled from the zoom subject center.\n * Therefore, it's sufficient to base our calculation on the distance from the zoom subject center to the zoom center.\n *\n * ```\n * newDistanceZoomSubjectCenterToZoomCenter = oldDistanceZoomSubjectCenterToZoomCenter * (newScale/oldScale)\n * ```\n *\n * Once we have `newDistanceZoomSubjectCenterToZoomCenter`,\n * we can easily calculate the position of the new center, which leads us to the offset amount.\n * Refer to the code for more details\n *\n * @param oldOffsetXOrYScaled\n * @param zoomSubjectOriginalWidthOrHeight\n * @param oldScale\n * @param newScale\n * @param zoomCenterXOrY\n */\nexport function calcNewScaledOffsetForZoomCentering(\n oldOffsetXOrYScaled: number,\n zoomSubjectOriginalWidthOrHeight: number,\n oldScale: number,\n newScale: number,\n zoomCenterXOrY: number\n) {\n const oldOffSetUnscaled = oldOffsetXOrYScaled * oldScale;\n const growthRate = newScale / oldScale;\n\n // these act like namespaces just for the sake of readability\n const zoomSubjectOriginalCenter = {} as Center;\n const zoomSubjectCurrentCenter = {} as Center;\n const zoomSubjectNewCenter = {} as Center;\n\n zoomSubjectOriginalCenter.xOrY = zoomSubjectOriginalWidthOrHeight / 2;\n zoomSubjectCurrentCenter.xOrY =\n zoomSubjectOriginalCenter.xOrY + oldOffSetUnscaled;\n zoomSubjectCurrentCenter.distanceToZoomCenter =\n zoomSubjectCurrentCenter.xOrY - zoomCenterXOrY;\n\n zoomSubjectNewCenter.distanceToZoomCenter =\n zoomSubjectCurrentCenter.distanceToZoomCenter * growthRate;\n zoomSubjectNewCenter.xOrY =\n zoomSubjectNewCenter.distanceToZoomCenter + zoomCenterXOrY;\n\n const newOffsetUnscaled =\n zoomSubjectNewCenter.xOrY - zoomSubjectOriginalCenter.xOrY;\n\n return newOffsetUnscaled / newScale;\n}\n\ninterface Center {\n xOrY: number;\n distanceToZoomCenter: number;\n}\n"]}
--------------------------------------------------------------------------------
/lib/commonjs/helper/coordinateConversion.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.applyContainResizeMode = applyContainResizeMode;
7 | exports.defaultTransformSubjectData = void 0;
8 | exports.getImageOriginOnTransformSubject = getImageOriginOnTransformSubject;
9 | exports.viewportPositionToImagePosition = viewportPositionToImagePosition;
10 | const defaultTransformSubjectData = {
11 | offsetX: 0,
12 | offsetY: 0,
13 | zoomLevel: 0,
14 | originalWidth: 0,
15 | originalHeight: 0,
16 | originalPageX: 0,
17 | originalPageY: 0
18 | };
19 | /**
20 | * Assuming you have an image that's being resized to fit into a container
21 | * using the "contain" resize mode. You can use this function to calculate the
22 | * size of the image after fitting.
23 | *
24 | * Since our sheet is resized in this manner, we need this function
25 | * for things like pan boundaries and marker placement
26 | *
27 | * @param imgSize
28 | * @param containerSize
29 | */
30 |
31 | exports.defaultTransformSubjectData = defaultTransformSubjectData;
32 |
33 | function applyContainResizeMode(imgSize, containerSize) {
34 | const {
35 | width: imageWidth,
36 | height: imageHeight
37 | } = imgSize;
38 | const {
39 | width: areaWidth,
40 | height: areaHeight
41 | } = containerSize;
42 | const imageAspect = imageWidth / imageHeight;
43 | const areaAspect = areaWidth / areaHeight;
44 | let newSize;
45 |
46 | if (imageAspect >= areaAspect) {
47 | // longest edge is horizontal
48 | newSize = {
49 | width: areaWidth,
50 | height: areaWidth / imageAspect
51 | };
52 | } else {
53 | // longest edge is vertical
54 | newSize = {
55 | width: areaHeight * imageAspect,
56 | height: areaHeight
57 | };
58 | }
59 |
60 | if (isNaN(newSize.height)) newSize.height = areaHeight;
61 | if (isNaN(newSize.width)) newSize.width = areaWidth;
62 | const scale = imageWidth ? newSize.width / imageWidth : newSize.height / imageHeight;
63 | if (!isFinite(scale)) return {
64 | size: null,
65 | scale: null
66 | };
67 | return {
68 | size: newSize,
69 | scale
70 | };
71 | }
72 | /**
73 | * get the coord of image's origin relative to the transformSubject
74 | * @param resizedImageSize
75 | * @param transformSubject
76 | */
77 |
78 |
79 | function getImageOriginOnTransformSubject(resizedImageSize, transformSubject) {
80 | const {
81 | offsetX,
82 | offsetY,
83 | zoomLevel,
84 | originalWidth,
85 | originalHeight
86 | } = transformSubject;
87 | return {
88 | x: offsetX * zoomLevel + originalWidth / 2 - resizedImageSize.width / 2 * zoomLevel,
89 | y: offsetY * zoomLevel + originalHeight / 2 - resizedImageSize.height / 2 * zoomLevel
90 | };
91 | }
92 | /**
93 | * Translates the coord system of a point from the viewport's space to the image's space
94 | *
95 | * @param pointOnContainer
96 | * @param sheetImageSize
97 | * @param transformSubject
98 | *
99 | * @return {Vec2D} returns null if point is out of the sheet's bound
100 | */
101 |
102 |
103 | function viewportPositionToImagePosition({
104 | viewportPosition,
105 | imageSize,
106 | zoomableEvent
107 | }) {
108 | const {
109 | size: resizedImgSize,
110 | scale: resizedImgScale
111 | } = applyContainResizeMode(imageSize, {
112 | width: zoomableEvent.originalWidth,
113 | height: zoomableEvent.originalHeight
114 | });
115 | if (resizedImgScale == null) return null;
116 | const sheetOriginOnContainer = getImageOriginOnTransformSubject(resizedImgSize, zoomableEvent);
117 | const pointOnSheet = {
118 | x: (viewportPosition.x - sheetOriginOnContainer.x) / zoomableEvent.zoomLevel / resizedImgScale,
119 | y: (viewportPosition.y - sheetOriginOnContainer.y) / zoomableEvent.zoomLevel / resizedImgScale
120 | };
121 | return pointOnSheet;
122 | }
123 | //# sourceMappingURL=coordinateConversion.js.map
--------------------------------------------------------------------------------
/lib/commonjs/helper/coordinateConversion.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["coordinateConversion.ts"],"names":["defaultTransformSubjectData","offsetX","offsetY","zoomLevel","originalWidth","originalHeight","originalPageX","originalPageY","applyContainResizeMode","imgSize","containerSize","width","imageWidth","height","imageHeight","areaWidth","areaHeight","imageAspect","areaAspect","newSize","isNaN","scale","isFinite","size","getImageOriginOnTransformSubject","resizedImageSize","transformSubject","x","y","viewportPositionToImagePosition","viewportPosition","imageSize","zoomableEvent","resizedImgSize","resizedImgScale","sheetOriginOnContainer","pointOnSheet"],"mappings":"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","sourcesContent":["import { Size2D, Vec2D, ZoomableViewEvent } from 'src/typings';\n\nexport const defaultTransformSubjectData: ZoomableViewEvent = {\n offsetX: 0,\n offsetY: 0,\n zoomLevel: 0,\n originalWidth: 0,\n originalHeight: 0,\n originalPageX: 0,\n originalPageY: 0,\n};\n\n/**\n * Assuming you have an image that's being resized to fit into a container\n * using the \"contain\" resize mode. You can use this function to calculate the\n * size of the image after fitting.\n *\n * Since our sheet is resized in this manner, we need this function\n * for things like pan boundaries and marker placement\n *\n * @param imgSize\n * @param containerSize\n */\nexport function applyContainResizeMode(\n imgSize: Size2D,\n containerSize: Size2D\n): { size: Size2D; scale: number } | { size: null; scale: null } {\n const { width: imageWidth, height: imageHeight } = imgSize;\n const { width: areaWidth, height: areaHeight } = containerSize;\n const imageAspect = imageWidth / imageHeight;\n const areaAspect = areaWidth / areaHeight;\n\n let newSize;\n if (imageAspect >= areaAspect) {\n // longest edge is horizontal\n newSize = { width: areaWidth, height: areaWidth / imageAspect };\n } else {\n // longest edge is vertical\n newSize = { width: areaHeight * imageAspect, height: areaHeight };\n }\n\n if (isNaN(newSize.height)) newSize.height = areaHeight;\n if (isNaN(newSize.width)) newSize.width = areaWidth;\n\n const scale = imageWidth\n ? newSize.width / imageWidth\n : newSize.height / imageHeight;\n\n if (!isFinite(scale)) return { size: null, scale: null };\n\n return {\n size: newSize,\n scale,\n };\n}\n\n/**\n * get the coord of image's origin relative to the transformSubject\n * @param resizedImageSize\n * @param transformSubject\n */\nexport function getImageOriginOnTransformSubject(\n resizedImageSize: Size2D,\n transformSubject: ZoomableViewEvent\n) {\n const { offsetX, offsetY, zoomLevel, originalWidth, originalHeight } =\n transformSubject;\n return {\n x:\n offsetX * zoomLevel +\n originalWidth / 2 -\n (resizedImageSize.width / 2) * zoomLevel,\n y:\n offsetY * zoomLevel +\n originalHeight / 2 -\n (resizedImageSize.height / 2) * zoomLevel,\n };\n}\n\n/**\n * Translates the coord system of a point from the viewport's space to the image's space\n *\n * @param pointOnContainer\n * @param sheetImageSize\n * @param transformSubject\n *\n * @return {Vec2D} returns null if point is out of the sheet's bound\n */\nexport function viewportPositionToImagePosition({\n viewportPosition,\n imageSize,\n zoomableEvent,\n}: {\n viewportPosition: Vec2D;\n imageSize: Size2D;\n zoomableEvent: ZoomableViewEvent;\n}): Vec2D | null {\n const { size: resizedImgSize, scale: resizedImgScale } =\n applyContainResizeMode(imageSize, {\n width: zoomableEvent.originalWidth,\n height: zoomableEvent.originalHeight,\n });\n\n if (resizedImgScale == null) return null;\n\n const sheetOriginOnContainer = getImageOriginOnTransformSubject(\n resizedImgSize,\n zoomableEvent\n );\n\n const pointOnSheet = {\n x:\n (viewportPosition.x - sheetOriginOnContainer.x) /\n zoomableEvent.zoomLevel /\n resizedImgScale,\n y:\n (viewportPosition.y - sheetOriginOnContainer.y) /\n zoomableEvent.zoomLevel /\n resizedImgScale,\n };\n\n return pointOnSheet;\n}\n"]}
--------------------------------------------------------------------------------
/lib/commonjs/helper/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.calcGestureCenterPoint = calcGestureCenterPoint;
7 | exports.calcGestureTouchDistance = calcGestureTouchDistance;
8 | Object.defineProperty(exports, "calcNewScaledOffsetForZoomCentering", {
9 | enumerable: true,
10 | get: function () {
11 | return _calcNewScaledOffsetForZoomCentering.calcNewScaledOffsetForZoomCentering;
12 | }
13 | });
14 |
15 | var _calcNewScaledOffsetForZoomCentering = require("./calcNewScaledOffsetForZoomCentering");
16 |
17 | /**
18 | * Calculates the gesture center point relative to the page coordinate system
19 | *
20 | * We're unable to use touch.locationX/Y
21 | * because locationX uses the axis system of the leaf element that the touch occurs on,
22 | * which makes it even more complicated to translate into our container's axis system.
23 | *
24 | * We're also unable to use gestureState.moveX/Y
25 | * because gestureState.moveX/Y is messed up on real device
26 | * (Sometimes it's the center point, but sometimes it randomly takes the position of one of the touches)
27 | */
28 | function calcGestureCenterPoint(e, gestureState) {
29 | const touches = e.nativeEvent.touches;
30 | if (!touches[0]) return null;
31 |
32 | if (gestureState.numberActiveTouches === 2) {
33 | if (!touches[1]) return null;
34 | return {
35 | x: (touches[0].pageX + touches[1].pageX) / 2,
36 | y: (touches[0].pageY + touches[1].pageY) / 2
37 | };
38 | }
39 |
40 | if (gestureState.numberActiveTouches === 1) {
41 | return {
42 | x: touches[0].pageX,
43 | y: touches[0].pageY
44 | };
45 | }
46 |
47 | return null;
48 | }
49 |
50 | function calcGestureTouchDistance(e, gestureState) {
51 | const touches = e.nativeEvent.touches;
52 | if (gestureState.numberActiveTouches !== 2 || !touches[0] || !touches[1]) return null;
53 | const dx = Math.abs(touches[0].pageX - touches[1].pageX);
54 | const dy = Math.abs(touches[0].pageY - touches[1].pageY);
55 | return Math.sqrt(dx * dx + dy * dy);
56 | }
57 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/lib/commonjs/helper/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.ts"],"names":["calcGestureCenterPoint","e","gestureState","touches","nativeEvent","numberActiveTouches","x","pageX","y","pageY","calcGestureTouchDistance","dx","Math","abs","dy","sqrt"],"mappings":";;;;;;;;;;;;;;AAGA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASA,sBAAT,CACLC,CADK,EAELC,YAFK,EAGS;AACd,QAAMC,OAAO,GAAGF,CAAC,CAACG,WAAF,CAAcD,OAA9B;AACA,MAAI,CAACA,OAAO,CAAC,CAAD,CAAZ,EAAiB,OAAO,IAAP;;AAEjB,MAAID,YAAY,CAACG,mBAAb,KAAqC,CAAzC,EAA4C;AAC1C,QAAI,CAACF,OAAO,CAAC,CAAD,CAAZ,EAAiB,OAAO,IAAP;AACjB,WAAO;AACLG,MAAAA,CAAC,EAAE,CAACH,OAAO,CAAC,CAAD,CAAP,CAAWI,KAAX,GAAmBJ,OAAO,CAAC,CAAD,CAAP,CAAWI,KAA/B,IAAwC,CADtC;AAELC,MAAAA,CAAC,EAAE,CAACL,OAAO,CAAC,CAAD,CAAP,CAAWM,KAAX,GAAmBN,OAAO,CAAC,CAAD,CAAP,CAAWM,KAA/B,IAAwC;AAFtC,KAAP;AAID;;AACD,MAAIP,YAAY,CAACG,mBAAb,KAAqC,CAAzC,EAA4C;AAC1C,WAAO;AACLC,MAAAA,CAAC,EAAEH,OAAO,CAAC,CAAD,CAAP,CAAWI,KADT;AAELC,MAAAA,CAAC,EAAEL,OAAO,CAAC,CAAD,CAAP,CAAWM;AAFT,KAAP;AAID;;AAED,SAAO,IAAP;AACD;;AAEM,SAASC,wBAAT,CACLT,CADK,EAELC,YAFK,EAGU;AACf,QAAMC,OAAO,GAAGF,CAAC,CAACG,WAAF,CAAcD,OAA9B;AACA,MAAID,YAAY,CAACG,mBAAb,KAAqC,CAArC,IAA0C,CAACF,OAAO,CAAC,CAAD,CAAlD,IAAyD,CAACA,OAAO,CAAC,CAAD,CAArE,EACE,OAAO,IAAP;AAEF,QAAMQ,EAAE,GAAGC,IAAI,CAACC,GAAL,CAASV,OAAO,CAAC,CAAD,CAAP,CAAWI,KAAX,GAAmBJ,OAAO,CAAC,CAAD,CAAP,CAAWI,KAAvC,CAAX;AACA,QAAMO,EAAE,GAAGF,IAAI,CAACC,GAAL,CAASV,OAAO,CAAC,CAAD,CAAP,CAAWM,KAAX,GAAmBN,OAAO,CAAC,CAAD,CAAP,CAAWM,KAAvC,CAAX;AACA,SAAOG,IAAI,CAACG,IAAL,CAAUJ,EAAE,GAAGA,EAAL,GAAUG,EAAE,GAAGA,EAAzB,CAAP;AACD","sourcesContent":["import { GestureResponderEvent, PanResponderGestureState } from 'react-native';\nimport { Vec2D } from '../typings';\n\nexport { calcNewScaledOffsetForZoomCentering } from './calcNewScaledOffsetForZoomCentering';\n\n/**\n * Calculates the gesture center point relative to the page coordinate system\n *\n * We're unable to use touch.locationX/Y\n * because locationX uses the axis system of the leaf element that the touch occurs on,\n * which makes it even more complicated to translate into our container's axis system.\n *\n * We're also unable to use gestureState.moveX/Y\n * because gestureState.moveX/Y is messed up on real device\n * (Sometimes it's the center point, but sometimes it randomly takes the position of one of the touches)\n */\nexport function calcGestureCenterPoint(\n e: GestureResponderEvent,\n gestureState: PanResponderGestureState\n): Vec2D | null {\n const touches = e.nativeEvent.touches;\n if (!touches[0]) return null;\n\n if (gestureState.numberActiveTouches === 2) {\n if (!touches[1]) return null;\n return {\n x: (touches[0].pageX + touches[1].pageX) / 2,\n y: (touches[0].pageY + touches[1].pageY) / 2,\n };\n }\n if (gestureState.numberActiveTouches === 1) {\n return {\n x: touches[0].pageX,\n y: touches[0].pageY,\n };\n }\n\n return null;\n}\n\nexport function calcGestureTouchDistance(\n e: GestureResponderEvent,\n gestureState: PanResponderGestureState\n): number | null {\n const touches = e.nativeEvent.touches;\n if (gestureState.numberActiveTouches !== 2 || !touches[0] || !touches[1])\n return null;\n\n const dx = Math.abs(touches[0].pageX - touches[1].pageX);\n const dy = Math.abs(touches[0].pageY - touches[1].pageY);\n return Math.sqrt(dx * dx + dy * dy);\n}\n"]}
--------------------------------------------------------------------------------
/lib/commonjs/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | Object.defineProperty(exports, "ReactNativeZoomableView", {
7 | enumerable: true,
8 | get: function () {
9 | return _ReactNativeZoomableView.default;
10 | }
11 | });
12 | Object.defineProperty(exports, "applyContainResizeMode", {
13 | enumerable: true,
14 | get: function () {
15 | return _coordinateConversion.applyContainResizeMode;
16 | }
17 | });
18 | Object.defineProperty(exports, "getImageOriginOnTransformSubject", {
19 | enumerable: true,
20 | get: function () {
21 | return _coordinateConversion.getImageOriginOnTransformSubject;
22 | }
23 | });
24 | Object.defineProperty(exports, "viewportPositionToImagePosition", {
25 | enumerable: true,
26 | get: function () {
27 | return _coordinateConversion.viewportPositionToImagePosition;
28 | }
29 | });
30 |
31 | var _coordinateConversion = require("./helper/coordinateConversion");
32 |
33 | var _ReactNativeZoomableView = _interopRequireDefault(require("./ReactNativeZoomableView"));
34 |
35 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
36 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/lib/commonjs/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;AAKA","sourcesContent":["import {\n applyContainResizeMode,\n getImageOriginOnTransformSubject,\n viewportPositionToImagePosition,\n} from './helper/coordinateConversion';\nimport ReactNativeZoomableView from './ReactNativeZoomableView';\nimport type {\n ReactNativeZoomableViewProps,\n ZoomableViewEvent,\n} from './typings';\n\nexport {\n ReactNativeZoomableView,\n ReactNativeZoomableViewProps,\n ZoomableViewEvent,\n // Helper functions for coordinate conversion\n applyContainResizeMode,\n getImageOriginOnTransformSubject,\n viewportPositionToImagePosition,\n};\n"]}
--------------------------------------------------------------------------------
/lib/commonjs/typings/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.SwipeDirection = void 0;
7 | let SwipeDirection;
8 | exports.SwipeDirection = SwipeDirection;
9 |
10 | (function (SwipeDirection) {
11 | SwipeDirection["SWIPE_UP"] = "SWIPE_UP";
12 | SwipeDirection["SWIPE_DOWN"] = "SWIPE_DOWN";
13 | SwipeDirection["SWIPE_LEFT"] = "SWIPE_LEFT";
14 | SwipeDirection["SWIPE_RIGHT"] = "SWIPE_RIGHT";
15 | })(SwipeDirection || (exports.SwipeDirection = SwipeDirection = {}));
16 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/lib/commonjs/typings/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.ts"],"names":["SwipeDirection"],"mappings":";;;;;;IASYA,c;;;WAAAA,c;AAAAA,EAAAA,c;AAAAA,EAAAA,c;AAAAA,EAAAA,c;AAAAA,EAAAA,c;GAAAA,c,8BAAAA,c","sourcesContent":["import {\n Animated,\n GestureResponderEvent,\n LayoutChangeEvent,\n PanResponderGestureState,\n ViewProps,\n} from 'react-native';\nimport { ReactNode } from 'react';\n\nexport enum SwipeDirection {\n SWIPE_UP = 'SWIPE_UP',\n SWIPE_DOWN = 'SWIPE_DOWN',\n SWIPE_LEFT = 'SWIPE_LEFT',\n SWIPE_RIGHT = 'SWIPE_RIGHT',\n}\n\nexport interface ZoomableViewEvent {\n zoomLevel: number;\n offsetX: number;\n offsetY: number;\n originalHeight: number;\n originalWidth: number;\n originalPageX: number;\n originalPageY: number;\n}\n\nexport interface ReactNativeZoomableViewProps {\n // options\n style?: ViewProps['style'];\n children?: ReactNode;\n zoomEnabled?: boolean;\n panEnabled?: boolean;\n initialZoom?: number;\n initialOffsetX?: number;\n initialOffsetY?: number;\n contentWidth?: number;\n contentHeight?: number;\n panBoundaryPadding?: number;\n maxZoom?: number;\n minZoom?: number;\n doubleTapDelay?: number;\n doubleTapZoomToCenter?: boolean;\n bindToBorders?: boolean;\n zoomStep?: number;\n pinchToZoomInSensitivity?: number;\n pinchToZoomOutSensitivity?: number;\n movementSensibility?: number;\n longPressDuration?: number;\n visualTouchFeedbackEnabled?: boolean;\n disablePanOnInitialZoom?: boolean;\n\n // Zoom animated value ref\n zoomAnimatedValue?: Animated.Value;\n panAnimatedValueXY?: Animated.ValueXY;\n\n // debug\n debug?: boolean;\n\n // callbacks\n onLayout?: (event: Pick) => void;\n onTransform?: (zoomableViewEventObject: ZoomableViewEvent) => void;\n onSingleTap?: (\n event: GestureResponderEvent,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onDoubleTapBefore?: (\n event: GestureResponderEvent,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onDoubleTapAfter?: (\n event: GestureResponderEvent,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onShiftingBefore?: (\n event: GestureResponderEvent | null,\n gestureState: PanResponderGestureState | null,\n zoomableViewEventObject: ZoomableViewEvent\n ) => boolean;\n onShiftingAfter?: (\n event: GestureResponderEvent | null,\n gestureState: PanResponderGestureState | null,\n zoomableViewEventObject: ZoomableViewEvent\n ) => boolean;\n onShiftingEnd?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onZoomBefore?: (\n event: GestureResponderEvent | null,\n gestureState: PanResponderGestureState | null,\n zoomableViewEventObject: ZoomableViewEvent\n ) => boolean | undefined;\n onZoomAfter?: (\n event: GestureResponderEvent | null,\n gestureState: PanResponderGestureState | null,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onZoomEnd?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onLongPress?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onStartShouldSetPanResponder?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent,\n baseComponentResult: boolean\n ) => boolean;\n onPanResponderGrant?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onPanResponderEnd?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onPanResponderMove?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => boolean;\n onPanResponderTerminate?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onPanResponderTerminationRequest?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => boolean;\n onShouldBlockNativeResponder?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => boolean;\n onStartShouldSetPanResponderCapture?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState\n ) => boolean;\n onMoveShouldSetPanResponderCapture?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState\n ) => boolean;\n onStaticPinPress?: (event: GestureResponderEvent) => void;\n onStaticPinLongPress?: (event: GestureResponderEvent) => void;\n staticPinPosition?: Vec2D;\n staticPinIcon?: React.ReactElement;\n onStaticPinPositionChange?: (position: Vec2D) => void;\n onStaticPinPositionMove?: (position: Vec2D) => void;\n animatePin: boolean;\n pinProps?: ViewProps;\n disableMomentum?: boolean;\n}\n\nexport interface Vec2D {\n x: number;\n y: number;\n}\n\nexport interface Size2D {\n width: number;\n height: number;\n}\n\nexport interface TouchPoint extends Vec2D {\n id: string;\n isSecondTap?: boolean;\n}\n\nexport interface ReactNativeZoomableViewState {\n touches?: TouchPoint[];\n originalWidth: number;\n originalHeight: number;\n originalPageX: number;\n originalPageY: number;\n originalX: number;\n originalY: number;\n debugPoints?: undefined | Vec2D[];\n pinSize: Size2D;\n}\n\nexport interface ReactNativeZoomableViewWithGesturesProps\n extends ReactNativeZoomableViewProps {\n swipeLengthThreshold?: number;\n swipeVelocityThreshold?: number;\n swipeDirectionalThreshold?: number;\n swipeMinZoom?: number;\n swipeMaxZoom?: number;\n swipeDisabled?: boolean;\n onSwipe?: (\n swipeDirection: SwipeDirection,\n gestureState: PanResponderGestureState\n ) => void;\n onSwipeUp?: (gestureState: PanResponderGestureState) => void;\n onSwipeDown?: (gestureState: PanResponderGestureState) => void;\n onSwipeLeft?: (gestureState: PanResponderGestureState) => void;\n onSwipeRight?: (gestureState: PanResponderGestureState) => void;\n}\n"]}
--------------------------------------------------------------------------------
/lib/module/animations/index.js:
--------------------------------------------------------------------------------
1 | import { Animated, Easing } from 'react-native';
2 | export function getBoundaryCrossedAnim(animValue, toValue) {
3 | return Animated.spring(animValue, {
4 | overshootClamping: true,
5 | toValue,
6 | useNativeDriver: true
7 | });
8 | }
9 | export function getPanMomentumDecayAnim(animValue, velocity) {
10 | return Animated.decay(animValue, {
11 | velocity,
12 | deceleration: 0.994,
13 | useNativeDriver: true
14 | });
15 | }
16 | export function getZoomToAnimation(animValue, toValue) {
17 | return Animated.timing(animValue, {
18 | easing: Easing.out(Easing.ease),
19 | toValue,
20 | useNativeDriver: true
21 | });
22 | }
23 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/lib/module/animations/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.ts"],"names":["Animated","Easing","getBoundaryCrossedAnim","animValue","toValue","spring","overshootClamping","useNativeDriver","getPanMomentumDecayAnim","velocity","decay","deceleration","getZoomToAnimation","timing","easing","out","ease"],"mappings":"AAAA,SAASA,QAAT,EAAmBC,MAAnB,QAAiC,cAAjC;AAGA,OAAO,SAASC,sBAAT,CACLC,SADK,EAELC,OAFK,EAGL;AACA,SAAOJ,QAAQ,CAACK,MAAT,CAAgBF,SAAhB,EAA2B;AAChCG,IAAAA,iBAAiB,EAAE,IADa;AAEhCF,IAAAA,OAFgC;AAGhCG,IAAAA,eAAe,EAAE;AAHe,GAA3B,CAAP;AAKD;AAED,OAAO,SAASC,uBAAT,CACLL,SADK,EAELM,QAFK,EAGL;AACA,SAAOT,QAAQ,CAACU,KAAT,CAAeP,SAAf,EAA0B;AAC/BM,IAAAA,QAD+B;AAE/BE,IAAAA,YAAY,EAAE,KAFiB;AAG/BJ,IAAAA,eAAe,EAAE;AAHc,GAA1B,CAAP;AAKD;AAED,OAAO,SAASK,kBAAT,CAA4BT,SAA5B,EAAuDC,OAAvD,EAAwE;AAC7E,SAAOJ,QAAQ,CAACa,MAAT,CAAgBV,SAAhB,EAA2B;AAChCW,IAAAA,MAAM,EAAEb,MAAM,CAACc,GAAP,CAAWd,MAAM,CAACe,IAAlB,CADwB;AAEhCZ,IAAAA,OAFgC;AAGhCG,IAAAA,eAAe,EAAE;AAHe,GAA3B,CAAP;AAKD","sourcesContent":["import { Animated, Easing } from 'react-native';\nimport { Vec2D } from '../typings';\n\nexport function getBoundaryCrossedAnim(\n animValue: Animated.Value,\n toValue: number\n) {\n return Animated.spring(animValue, {\n overshootClamping: true,\n toValue,\n useNativeDriver: true,\n });\n}\n\nexport function getPanMomentumDecayAnim(\n animValue: Animated.Value | Animated.ValueXY,\n velocity: number | Vec2D\n) {\n return Animated.decay(animValue, {\n velocity,\n deceleration: 0.994,\n useNativeDriver: true,\n });\n}\n\nexport function getZoomToAnimation(animValue: Animated.Value, toValue: number) {\n return Animated.timing(animValue, {\n easing: Easing.out(Easing.ease),\n toValue,\n useNativeDriver: true,\n });\n}\n"]}
--------------------------------------------------------------------------------
/lib/module/assets/pin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openspacelabs/react-native-zoomable-view/9a4219bfd5250bea2995309ac847b57de0e3b3ae/lib/module/assets/pin.png
--------------------------------------------------------------------------------
/lib/module/components/AnimatedTouchFeedback.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | import { Animated, Easing, StyleSheet } from 'react-native';
3 | export const AnimatedTouchFeedback = ({
4 | x,
5 | y,
6 | animationDelay,
7 | animationDuration,
8 | onAnimationDone
9 | }) => {
10 | const appearDisappearAnimRef = useRef(new Animated.Value(0));
11 | const onAnimationDoneRef = useRef(onAnimationDone);
12 | onAnimationDoneRef.current = onAnimationDone;
13 | useEffect(() => {
14 | appearDisappearAnimRef.current.setValue(0);
15 | const inDuration = animationDuration * 0.8;
16 | const outDuration = animationDuration - inDuration;
17 | Animated.sequence([Animated.timing(appearDisappearAnimRef.current, {
18 | delay: animationDelay || 0,
19 | toValue: 1,
20 | duration: inDuration,
21 | easing: Easing.linear,
22 | useNativeDriver: true
23 | }), Animated.timing(appearDisappearAnimRef.current, {
24 | toValue: 0,
25 | duration: outDuration,
26 | easing: Easing.out(Easing.ease),
27 | useNativeDriver: true
28 | })]).start(() => {
29 | var _onAnimationDoneRef$c;
30 |
31 | return (_onAnimationDoneRef$c = onAnimationDoneRef.current) === null || _onAnimationDoneRef$c === void 0 ? void 0 : _onAnimationDoneRef$c.call(onAnimationDoneRef);
32 | });
33 | }, [animationDelay, animationDuration]);
34 | return /*#__PURE__*/React.createElement(Animated.View, {
35 | pointerEvents: "none",
36 | style: [styles.animatedTouchFeedback, {
37 | opacity: appearDisappearAnimRef.current.interpolate({
38 | inputRange: [0, 1],
39 | outputRange: [0, 0.3]
40 | }),
41 | left: x - 20,
42 | top: y - 20,
43 | transform: [{
44 | scale: appearDisappearAnimRef.current.interpolate({
45 | inputRange: [0, 1],
46 | outputRange: [0.5, 1]
47 | })
48 | }]
49 | }]
50 | });
51 | };
52 | const styles = StyleSheet.create({
53 | animatedTouchFeedback: {
54 | backgroundColor: 'lightgray',
55 | borderRadius: 40,
56 | height: 40,
57 | position: 'absolute',
58 | width: 40
59 | }
60 | });
61 | //# sourceMappingURL=AnimatedTouchFeedback.js.map
--------------------------------------------------------------------------------
/lib/module/components/AnimatedTouchFeedback.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["AnimatedTouchFeedback.tsx"],"names":["React","useEffect","useRef","Animated","Easing","StyleSheet","AnimatedTouchFeedback","x","y","animationDelay","animationDuration","onAnimationDone","appearDisappearAnimRef","Value","onAnimationDoneRef","current","setValue","inDuration","outDuration","sequence","timing","delay","toValue","duration","easing","linear","useNativeDriver","out","ease","start","styles","animatedTouchFeedback","opacity","interpolate","inputRange","outputRange","left","top","transform","scale","create","backgroundColor","borderRadius","height","position","width"],"mappings":"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","sourcesContent":["import React, { useEffect, useRef } from 'react';\nimport { Animated, Easing, StyleSheet } from 'react-native';\n\nexport const AnimatedTouchFeedback = ({\n x,\n y,\n animationDelay,\n animationDuration,\n onAnimationDone,\n}: {\n x: number;\n y: number;\n animationDuration: number;\n animationDelay?: number;\n onAnimationDone?: () => void;\n}) => {\n const appearDisappearAnimRef = useRef(new Animated.Value(0));\n const onAnimationDoneRef = useRef(onAnimationDone);\n onAnimationDoneRef.current = onAnimationDone;\n\n useEffect(() => {\n appearDisappearAnimRef.current.setValue(0);\n const inDuration = animationDuration * 0.8;\n const outDuration = animationDuration - inDuration;\n Animated.sequence([\n Animated.timing(appearDisappearAnimRef.current, {\n delay: animationDelay || 0,\n toValue: 1,\n duration: inDuration,\n easing: Easing.linear,\n useNativeDriver: true,\n }),\n Animated.timing(appearDisappearAnimRef.current, {\n toValue: 0,\n duration: outDuration,\n easing: Easing.out(Easing.ease),\n useNativeDriver: true,\n }),\n ]).start(() => onAnimationDoneRef.current?.());\n }, [animationDelay, animationDuration]);\n\n return (\n \n );\n};\n\nconst styles = StyleSheet.create({\n animatedTouchFeedback: {\n backgroundColor: 'lightgray',\n borderRadius: 40,\n height: 40,\n position: 'absolute',\n width: 40,\n },\n});\n"]}
--------------------------------------------------------------------------------
/lib/module/components/StaticPin.js:
--------------------------------------------------------------------------------
1 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2 |
3 | import React from 'react';
4 | import { Animated, View, Image, StyleSheet, PanResponder } from 'react-native';
5 | export const StaticPin = ({
6 | staticPinPosition,
7 | pinAnim,
8 | staticPinIcon,
9 | pinSize,
10 | onParentMove,
11 | onPress,
12 | onLongPress,
13 | setPinSize,
14 | pinProps = {}
15 | }) => {
16 | const tapTime = React.useRef(0);
17 | const transform = [{
18 | translateY: -pinSize.height
19 | }, {
20 | translateX: -pinSize.width / 2
21 | }, ...pinAnim.getTranslateTransform()];
22 | const opacity = pinSize.width && pinSize.height ? 1 : 0;
23 | const panResponder = React.useRef(PanResponder.create({
24 | onStartShouldSetPanResponder: () => {
25 | tapTime.current = Date.now(); // We want to handle tap on this so set true
26 |
27 | return true;
28 | },
29 | onPanResponderMove: (evt, gestureState) => {
30 | // However if the user moves finger we want to pass this evt to parent
31 | // to handle panning (tap not recognized)
32 | if (Math.abs(gestureState.dx) > 5 && Math.abs(gestureState.dy) > 5) onParentMove(evt, gestureState);
33 | },
34 | onPanResponderRelease: (evt, gestureState) => {
35 | if (Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5) return;
36 | const dt = Date.now() - tapTime.current;
37 |
38 | if (onPress && dt < 500) {
39 | onPress(evt);
40 | }
41 |
42 | if (onLongPress && dt > 500) {
43 | // RN long press is 500ms
44 | onLongPress(evt);
45 | }
46 | }
47 | })).current;
48 | return /*#__PURE__*/React.createElement(Animated.View, _extends({
49 | style: [{
50 | left: staticPinPosition.x,
51 | top: staticPinPosition.y
52 | }, styles.pinWrapper, {
53 | opacity,
54 | transform
55 | }]
56 | }, pinProps), /*#__PURE__*/React.createElement(View, _extends({
57 | onLayout: ({
58 | nativeEvent: {
59 | layout
60 | }
61 | }) => {
62 | setPinSize(layout);
63 | }
64 | }, panResponder.panHandlers), staticPinIcon ||
65 | /*#__PURE__*/
66 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
67 | React.createElement(Image, {
68 | source: require('../assets/pin.png'),
69 | style: styles.pin
70 | })));
71 | };
72 | const styles = StyleSheet.create({
73 | pin: {
74 | height: 64,
75 | width: 48
76 | },
77 | pinWrapper: {
78 | position: 'absolute'
79 | }
80 | });
81 | //# sourceMappingURL=StaticPin.js.map
--------------------------------------------------------------------------------
/lib/module/components/StaticPin.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["StaticPin.tsx"],"names":["React","Animated","View","Image","StyleSheet","PanResponder","StaticPin","staticPinPosition","pinAnim","staticPinIcon","pinSize","onParentMove","onPress","onLongPress","setPinSize","pinProps","tapTime","useRef","transform","translateY","height","translateX","width","getTranslateTransform","opacity","panResponder","create","onStartShouldSetPanResponder","current","Date","now","onPanResponderMove","evt","gestureState","Math","abs","dx","dy","onPanResponderRelease","dt","left","x","top","y","styles","pinWrapper","nativeEvent","layout","panHandlers","require","pin","position"],"mappings":"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","sourcesContent":["import React from 'react';\nimport {\n Animated,\n View,\n Image,\n StyleSheet,\n GestureResponderEvent,\n PanResponderGestureState,\n PanResponder,\n ViewProps,\n} from 'react-native';\nimport { Size2D } from 'src/typings';\n\nexport const StaticPin = ({\n staticPinPosition,\n pinAnim,\n staticPinIcon,\n pinSize,\n onParentMove,\n onPress,\n onLongPress,\n setPinSize,\n pinProps = {},\n}: {\n staticPinPosition: { x: number; y: number };\n pinAnim: Animated.ValueXY;\n staticPinIcon: React.ReactNode;\n pinSize: Size2D;\n /** Internal handler for passing move event to parent */\n onParentMove: (\n evt: GestureResponderEvent,\n gestureState: PanResponderGestureState\n ) => boolean | undefined;\n onPress?: (evt: GestureResponderEvent) => void;\n onLongPress?: (evt: GestureResponderEvent) => void;\n setPinSize: (size: Size2D) => void;\n pinProps?: ViewProps;\n}) => {\n const tapTime = React.useRef(0);\n const transform = [\n { translateY: -pinSize.height },\n { translateX: -pinSize.width / 2 },\n ...pinAnim.getTranslateTransform(),\n ];\n\n const opacity = pinSize.width && pinSize.height ? 1 : 0;\n\n const panResponder = React.useRef(\n PanResponder.create({\n onStartShouldSetPanResponder: () => {\n tapTime.current = Date.now();\n\n // We want to handle tap on this so set true\n return true;\n },\n onPanResponderMove: (evt, gestureState) => {\n // However if the user moves finger we want to pass this evt to parent\n // to handle panning (tap not recognized)\n if (Math.abs(gestureState.dx) > 5 && Math.abs(gestureState.dy) > 5)\n onParentMove(evt, gestureState);\n },\n onPanResponderRelease: (evt, gestureState) => {\n if (Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5)\n return;\n const dt = Date.now() - tapTime.current;\n if (onPress && dt < 500) {\n onPress(evt);\n }\n if (onLongPress && dt > 500) {\n // RN long press is 500ms\n onLongPress(evt);\n }\n },\n })\n ).current;\n\n return (\n \n {\n setPinSize(layout);\n }}\n {...panResponder.panHandlers}\n >\n {staticPinIcon || (\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n \n )}\n \n \n );\n};\n\nconst styles = StyleSheet.create({\n pin: {\n height: 64,\n width: 48,\n },\n pinWrapper: {\n position: 'absolute',\n },\n});\n"]}
--------------------------------------------------------------------------------
/lib/module/components/index.js:
--------------------------------------------------------------------------------
1 | export { AnimatedTouchFeedback } from './AnimatedTouchFeedback';
2 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/lib/module/components/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.tsx"],"names":["AnimatedTouchFeedback"],"mappings":"AAAA,SAASA,qBAAT,QAAsC,yBAAtC","sourcesContent":["export { AnimatedTouchFeedback } from './AnimatedTouchFeedback';\n"]}
--------------------------------------------------------------------------------
/lib/module/debugHelper/index.js:
--------------------------------------------------------------------------------
1 | import { View, StyleSheet } from 'react-native';
2 | import React from 'react';
3 | export const DebugTouchPoint = ({
4 | diameter = 20,
5 | x = 0,
6 | y = 0,
7 | color = 'yellow'
8 | }) => {
9 | const radius = diameter / 2;
10 | return /*#__PURE__*/React.createElement(View, {
11 | style: [styles.debugPoint, {
12 | width: diameter,
13 | height: diameter,
14 | borderRadius: diameter,
15 | backgroundColor: color,
16 | left: x - radius,
17 | top: y - radius
18 | }],
19 | pointerEvents: "none"
20 | });
21 | };
22 | export const DebugRect = ({
23 | height,
24 | x = 0,
25 | y = 0,
26 | color = 'yellow'
27 | }) => {
28 | const width = 5;
29 | return /*#__PURE__*/React.createElement(View, {
30 | style: [styles.debugRect, {
31 | width,
32 | height,
33 | backgroundColor: color,
34 | left: x - width / 2,
35 | top: y
36 | }],
37 | pointerEvents: "none"
38 | });
39 | };
40 | const styles = StyleSheet.create({
41 | debugPoint: {
42 | opacity: 0.7,
43 | position: 'absolute'
44 | },
45 | debugRect: {
46 | opacity: 0.5,
47 | position: 'absolute'
48 | }
49 | });
50 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/lib/module/debugHelper/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.tsx"],"names":["View","StyleSheet","React","DebugTouchPoint","diameter","x","y","color","radius","styles","debugPoint","width","height","borderRadius","backgroundColor","left","top","DebugRect","debugRect","create","opacity","position"],"mappings":"AAAA,SAASA,IAAT,EAAeC,UAAf,QAAiC,cAAjC;AACA,OAAOC,KAAP,MAAkB,OAAlB;AAEA,OAAO,MAAMC,eAAe,GAAG,CAAC;AAC9BC,EAAAA,QAAQ,GAAG,EADmB;AAE9BC,EAAAA,CAAC,GAAG,CAF0B;AAG9BC,EAAAA,CAAC,GAAG,CAH0B;AAI9BC,EAAAA,KAAK,GAAG;AAJsB,CAAD,KAKzB;AACJ,QAAMC,MAAM,GAAGJ,QAAQ,GAAG,CAA1B;AACA,sBACE,oBAAC,IAAD;AACE,IAAA,KAAK,EAAE,CACLK,MAAM,CAACC,UADF,EAEL;AACEC,MAAAA,KAAK,EAAEP,QADT;AAEEQ,MAAAA,MAAM,EAAER,QAFV;AAGES,MAAAA,YAAY,EAAET,QAHhB;AAIEU,MAAAA,eAAe,EAAEP,KAJnB;AAKEQ,MAAAA,IAAI,EAAEV,CAAC,GAAGG,MALZ;AAMEQ,MAAAA,GAAG,EAAEV,CAAC,GAAGE;AANX,KAFK,CADT;AAYE,IAAA,aAAa,EAAC;AAZhB,IADF;AAgBD,CAvBM;AAwBP,OAAO,MAAMS,SAAS,GAAG,CAAC;AACxBL,EAAAA,MADwB;AAExBP,EAAAA,CAAC,GAAG,CAFoB;AAGxBC,EAAAA,CAAC,GAAG,CAHoB;AAIxBC,EAAAA,KAAK,GAAG;AAJgB,CAAD,KAUnB;AACJ,QAAMI,KAAK,GAAG,CAAd;AACA,sBACE,oBAAC,IAAD;AACE,IAAA,KAAK,EAAE,CACLF,MAAM,CAACS,SADF,EAEL;AACEP,MAAAA,KADF;AAEEC,MAAAA,MAFF;AAGEE,MAAAA,eAAe,EAAEP,KAHnB;AAIEQ,MAAAA,IAAI,EAAEV,CAAC,GAAGM,KAAK,GAAG,CAJpB;AAKEK,MAAAA,GAAG,EAAEV;AALP,KAFK,CADT;AAWE,IAAA,aAAa,EAAC;AAXhB,IADF;AAeD,CA3BM;AA6BP,MAAMG,MAAM,GAAGR,UAAU,CAACkB,MAAX,CAAkB;AAC/BT,EAAAA,UAAU,EAAE;AACVU,IAAAA,OAAO,EAAE,GADC;AAEVC,IAAAA,QAAQ,EAAE;AAFA,GADmB;AAK/BH,EAAAA,SAAS,EAAE;AACTE,IAAAA,OAAO,EAAE,GADA;AAETC,IAAAA,QAAQ,EAAE;AAFD;AALoB,CAAlB,CAAf","sourcesContent":["import { View, StyleSheet } from 'react-native';\nimport React from 'react';\n\nexport const DebugTouchPoint = ({\n diameter = 20,\n x = 0,\n y = 0,\n color = 'yellow',\n}) => {\n const radius = diameter / 2;\n return (\n \n );\n};\nexport const DebugRect = ({\n height,\n x = 0,\n y = 0,\n color = 'yellow',\n}: {\n height: number;\n x: number;\n y: number;\n color: string;\n}) => {\n const width = 5;\n return (\n \n );\n};\n\nconst styles = StyleSheet.create({\n debugPoint: {\n opacity: 0.7,\n position: 'absolute',\n },\n debugRect: {\n opacity: 0.5,\n position: 'absolute',\n },\n});\n"]}
--------------------------------------------------------------------------------
/lib/module/helper/applyPanBoundariesToOffset.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Takes a single offset value and calculates the correct offset value
3 | * to make sure it's within the pan boundaries
4 | *
5 | *
6 | * @param offsetScaled
7 | * @param containerSize
8 | * @param contentSize
9 | * @param scale
10 | * @param boundaryPadding - see README
11 | *
12 | * @returns {number}
13 | */
14 | export function applyPanBoundariesToOffset(offsetScaled, containerSize, contentSize, scale, boundaryPadding) {
15 | const contentSizeUnscaled = contentSize * scale;
16 | const offsetUnscaled = offsetScaled * scale;
17 | const contentStartBorderUnscaled = containerSize / 2 + offsetUnscaled - contentSizeUnscaled / 2;
18 | const contentEndBorderUnscaled = contentStartBorderUnscaled + contentSizeUnscaled;
19 | const containerStartBorder = 0;
20 | const containerEndBorder = containerStartBorder + containerSize; // do not let boundary padding be greater than the container size or less than 0
21 |
22 | if (!boundaryPadding || boundaryPadding < 0) boundaryPadding = 0;
23 | if (boundaryPadding > containerSize) boundaryPadding = containerSize; // Calculate container's measurements with boundary padding applied.
24 | // this should shrink the container's size by the amount of the boundary padding,
25 | // so that the content inside can be panned a bit further away from the original container's boundaries.
26 |
27 | const paddedContainerSize = containerSize - boundaryPadding * 2;
28 | const paddedContainerStartBorder = containerStartBorder + boundaryPadding;
29 | const paddedContainerEndBorder = containerEndBorder - boundaryPadding; // if content is smaller than the padded container,
30 | // don't let the content move
31 |
32 | if (contentSizeUnscaled < paddedContainerSize) {
33 | return 0;
34 | } // if content is larger than the padded container,
35 | // don't let the padded container go outside of content
36 | // maximum distance the content's center can move from its original position.
37 | // assuming the content original center is the container's center.
38 |
39 |
40 | const contentMaxOffsetScaled = (paddedContainerSize / 2 - contentSizeUnscaled / 2) / scale;
41 |
42 | if ( // content reaching the end boundary
43 | contentEndBorderUnscaled < paddedContainerEndBorder) {
44 | return contentMaxOffsetScaled;
45 | }
46 |
47 | if ( // content reaching the start boundary
48 | contentStartBorderUnscaled > paddedContainerStartBorder) {
49 | return -contentMaxOffsetScaled;
50 | }
51 |
52 | return offsetScaled;
53 | }
54 | //# sourceMappingURL=applyPanBoundariesToOffset.js.map
--------------------------------------------------------------------------------
/lib/module/helper/applyPanBoundariesToOffset.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["applyPanBoundariesToOffset.ts"],"names":["applyPanBoundariesToOffset","offsetScaled","containerSize","contentSize","scale","boundaryPadding","contentSizeUnscaled","offsetUnscaled","contentStartBorderUnscaled","contentEndBorderUnscaled","containerStartBorder","containerEndBorder","paddedContainerSize","paddedContainerStartBorder","paddedContainerEndBorder","contentMaxOffsetScaled"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASA,0BAAT,CACLC,YADK,EAELC,aAFK,EAGLC,WAHK,EAILC,KAJK,EAKLC,eALK,EAML;AACA,QAAMC,mBAAmB,GAAGH,WAAW,GAAGC,KAA1C;AACA,QAAMG,cAAc,GAAGN,YAAY,GAAGG,KAAtC;AAEA,QAAMI,0BAA0B,GAC9BN,aAAa,GAAG,CAAhB,GAAoBK,cAApB,GAAqCD,mBAAmB,GAAG,CAD7D;AAEA,QAAMG,wBAAwB,GAC5BD,0BAA0B,GAAGF,mBAD/B;AAGA,QAAMI,oBAAoB,GAAG,CAA7B;AACA,QAAMC,kBAAkB,GAAGD,oBAAoB,GAAGR,aAAlD,CAVA,CAYA;;AACA,MAAI,CAACG,eAAD,IAAoBA,eAAe,GAAG,CAA1C,EAA6CA,eAAe,GAAG,CAAlB;AAC7C,MAAIA,eAAe,GAAGH,aAAtB,EAAqCG,eAAe,GAAGH,aAAlB,CAdrC,CAgBA;AACA;AACA;;AACA,QAAMU,mBAAmB,GAAGV,aAAa,GAAGG,eAAe,GAAG,CAA9D;AACA,QAAMQ,0BAA0B,GAAGH,oBAAoB,GAAGL,eAA1D;AACA,QAAMS,wBAAwB,GAAGH,kBAAkB,GAAGN,eAAtD,CArBA,CAuBA;AACA;;AACA,MAAIC,mBAAmB,GAAGM,mBAA1B,EAA+C;AAC7C,WAAO,CAAP;AACD,GA3BD,CA6BA;AACA;AAEA;AACA;;;AACA,QAAMG,sBAAsB,GAC1B,CAACH,mBAAmB,GAAG,CAAtB,GAA0BN,mBAAmB,GAAG,CAAjD,IAAsDF,KADxD;;AAGA,OACE;AACAK,EAAAA,wBAAwB,GAAGK,wBAF7B,EAGE;AACA,WAAOC,sBAAP;AACD;;AACD,OACE;AACAP,EAAAA,0BAA0B,GAAGK,0BAF/B,EAGE;AACA,WAAO,CAACE,sBAAR;AACD;;AAED,SAAOd,YAAP;AACD","sourcesContent":["/**\n * Takes a single offset value and calculates the correct offset value\n * to make sure it's within the pan boundaries\n *\n *\n * @param offsetScaled\n * @param containerSize\n * @param contentSize\n * @param scale\n * @param boundaryPadding - see README\n *\n * @returns {number}\n */\nexport function applyPanBoundariesToOffset(\n offsetScaled: number,\n containerSize: number,\n contentSize: number,\n scale: number,\n boundaryPadding: number\n) {\n const contentSizeUnscaled = contentSize * scale;\n const offsetUnscaled = offsetScaled * scale;\n\n const contentStartBorderUnscaled =\n containerSize / 2 + offsetUnscaled - contentSizeUnscaled / 2;\n const contentEndBorderUnscaled =\n contentStartBorderUnscaled + contentSizeUnscaled;\n\n const containerStartBorder = 0;\n const containerEndBorder = containerStartBorder + containerSize;\n\n // do not let boundary padding be greater than the container size or less than 0\n if (!boundaryPadding || boundaryPadding < 0) boundaryPadding = 0;\n if (boundaryPadding > containerSize) boundaryPadding = containerSize;\n\n // Calculate container's measurements with boundary padding applied.\n // this should shrink the container's size by the amount of the boundary padding,\n // so that the content inside can be panned a bit further away from the original container's boundaries.\n const paddedContainerSize = containerSize - boundaryPadding * 2;\n const paddedContainerStartBorder = containerStartBorder + boundaryPadding;\n const paddedContainerEndBorder = containerEndBorder - boundaryPadding;\n\n // if content is smaller than the padded container,\n // don't let the content move\n if (contentSizeUnscaled < paddedContainerSize) {\n return 0;\n }\n\n // if content is larger than the padded container,\n // don't let the padded container go outside of content\n\n // maximum distance the content's center can move from its original position.\n // assuming the content original center is the container's center.\n const contentMaxOffsetScaled =\n (paddedContainerSize / 2 - contentSizeUnscaled / 2) / scale;\n\n if (\n // content reaching the end boundary\n contentEndBorderUnscaled < paddedContainerEndBorder\n ) {\n return contentMaxOffsetScaled;\n }\n if (\n // content reaching the start boundary\n contentStartBorderUnscaled > paddedContainerStartBorder\n ) {\n return -contentMaxOffsetScaled;\n }\n\n return offsetScaled;\n}\n"]}
--------------------------------------------------------------------------------
/lib/module/helper/calcNewScaledOffsetForZoomCentering.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Calculates the new offset for the zoomSubject to ensure zoom center position is retained after zooming.
3 | * Parameters should correspond to whether we need the offset for the X or Y axis
4 | *
5 | * ## Terms Used:
6 | *
7 | * - Zoom Subject: the view that's being zoomed and panned
8 | * - Zoom Center: the point whose relative position to the window is retained
9 | * - Unscaled: a measurement in pixels denoting the true size as observed by the users' eyes
10 | * - Scaled: a measurement in pixels scaled to the "scale transformation" of the zoom subject to match with its true size.
11 | * *For example:*
12 | * If the scale on the zoom subject is 0.5,
13 | * then to draw an actual 4px line on the zoom subject, we need to scale it to 4px / 0.5 = 8px
14 | * 8px is the scaled measurement
15 | *
16 | * ## Overall idea of this algorithm:
17 | *
18 | * When users perform zooming by pinching the screen,
19 | * we need to shift the zoom subject so that the position of the zoom center is always the same.
20 | * The offset amount to shift the layer is the returned value.
21 | *
22 | *
23 | * ## How we achieve our goal:
24 | *
25 | * To retain, the zoom center position, whenever a zoom action is performed,
26 | * we just need to make sure the distances from all the points in the zoom subject
27 | * to the zoom center increases or decreases by the growth rate of the scale.
28 | *
29 | * ```
30 | * newDistanceAnyPointToZoomCenter = oldDistanceAnyPointToZoomCenter * (newScale/oldScale)
31 | * ```
32 | *
33 | * We can't calculate that for all the points because there are unlimited points on a plain.
34 | * However, due to the way `transform` works in RN, every point is scaled from the zoom subject center.
35 | * Therefore, it's sufficient to base our calculation on the distance from the zoom subject center to the zoom center.
36 | *
37 | * ```
38 | * newDistanceZoomSubjectCenterToZoomCenter = oldDistanceZoomSubjectCenterToZoomCenter * (newScale/oldScale)
39 | * ```
40 | *
41 | * Once we have `newDistanceZoomSubjectCenterToZoomCenter`,
42 | * we can easily calculate the position of the new center, which leads us to the offset amount.
43 | * Refer to the code for more details
44 | *
45 | * @param oldOffsetXOrYScaled
46 | * @param zoomSubjectOriginalWidthOrHeight
47 | * @param oldScale
48 | * @param newScale
49 | * @param zoomCenterXOrY
50 | */
51 | export function calcNewScaledOffsetForZoomCentering(oldOffsetXOrYScaled, zoomSubjectOriginalWidthOrHeight, oldScale, newScale, zoomCenterXOrY) {
52 | const oldOffSetUnscaled = oldOffsetXOrYScaled * oldScale;
53 | const growthRate = newScale / oldScale; // these act like namespaces just for the sake of readability
54 |
55 | const zoomSubjectOriginalCenter = {};
56 | const zoomSubjectCurrentCenter = {};
57 | const zoomSubjectNewCenter = {};
58 | zoomSubjectOriginalCenter.xOrY = zoomSubjectOriginalWidthOrHeight / 2;
59 | zoomSubjectCurrentCenter.xOrY = zoomSubjectOriginalCenter.xOrY + oldOffSetUnscaled;
60 | zoomSubjectCurrentCenter.distanceToZoomCenter = zoomSubjectCurrentCenter.xOrY - zoomCenterXOrY;
61 | zoomSubjectNewCenter.distanceToZoomCenter = zoomSubjectCurrentCenter.distanceToZoomCenter * growthRate;
62 | zoomSubjectNewCenter.xOrY = zoomSubjectNewCenter.distanceToZoomCenter + zoomCenterXOrY;
63 | const newOffsetUnscaled = zoomSubjectNewCenter.xOrY - zoomSubjectOriginalCenter.xOrY;
64 | return newOffsetUnscaled / newScale;
65 | }
66 | //# sourceMappingURL=calcNewScaledOffsetForZoomCentering.js.map
--------------------------------------------------------------------------------
/lib/module/helper/calcNewScaledOffsetForZoomCentering.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["calcNewScaledOffsetForZoomCentering.ts"],"names":["calcNewScaledOffsetForZoomCentering","oldOffsetXOrYScaled","zoomSubjectOriginalWidthOrHeight","oldScale","newScale","zoomCenterXOrY","oldOffSetUnscaled","growthRate","zoomSubjectOriginalCenter","zoomSubjectCurrentCenter","zoomSubjectNewCenter","xOrY","distanceToZoomCenter","newOffsetUnscaled"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASA,mCAAT,CACLC,mBADK,EAELC,gCAFK,EAGLC,QAHK,EAILC,QAJK,EAKLC,cALK,EAML;AACA,QAAMC,iBAAiB,GAAGL,mBAAmB,GAAGE,QAAhD;AACA,QAAMI,UAAU,GAAGH,QAAQ,GAAGD,QAA9B,CAFA,CAIA;;AACA,QAAMK,yBAAyB,GAAG,EAAlC;AACA,QAAMC,wBAAwB,GAAG,EAAjC;AACA,QAAMC,oBAAoB,GAAG,EAA7B;AAEAF,EAAAA,yBAAyB,CAACG,IAA1B,GAAiCT,gCAAgC,GAAG,CAApE;AACAO,EAAAA,wBAAwB,CAACE,IAAzB,GACEH,yBAAyB,CAACG,IAA1B,GAAiCL,iBADnC;AAEAG,EAAAA,wBAAwB,CAACG,oBAAzB,GACEH,wBAAwB,CAACE,IAAzB,GAAgCN,cADlC;AAGAK,EAAAA,oBAAoB,CAACE,oBAArB,GACEH,wBAAwB,CAACG,oBAAzB,GAAgDL,UADlD;AAEAG,EAAAA,oBAAoB,CAACC,IAArB,GACED,oBAAoB,CAACE,oBAArB,GAA4CP,cAD9C;AAGA,QAAMQ,iBAAiB,GACrBH,oBAAoB,CAACC,IAArB,GAA4BH,yBAAyB,CAACG,IADxD;AAGA,SAAOE,iBAAiB,GAAGT,QAA3B;AACD","sourcesContent":["/**\n * Calculates the new offset for the zoomSubject to ensure zoom center position is retained after zooming.\n * Parameters should correspond to whether we need the offset for the X or Y axis\n *\n * ## Terms Used:\n *\n * - Zoom Subject: the view that's being zoomed and panned\n * - Zoom Center: the point whose relative position to the window is retained\n * - Unscaled: a measurement in pixels denoting the true size as observed by the users' eyes\n * - Scaled: a measurement in pixels scaled to the \"scale transformation\" of the zoom subject to match with its true size.\n * *For example:*\n * If the scale on the zoom subject is 0.5,\n * then to draw an actual 4px line on the zoom subject, we need to scale it to 4px / 0.5 = 8px\n * 8px is the scaled measurement\n *\n * ## Overall idea of this algorithm:\n *\n * When users perform zooming by pinching the screen,\n * we need to shift the zoom subject so that the position of the zoom center is always the same.\n * The offset amount to shift the layer is the returned value.\n *\n *\n * ## How we achieve our goal:\n *\n * To retain, the zoom center position, whenever a zoom action is performed,\n * we just need to make sure the distances from all the points in the zoom subject\n * to the zoom center increases or decreases by the growth rate of the scale.\n *\n * ```\n * newDistanceAnyPointToZoomCenter = oldDistanceAnyPointToZoomCenter * (newScale/oldScale)\n * ```\n *\n * We can't calculate that for all the points because there are unlimited points on a plain.\n * However, due to the way `transform` works in RN, every point is scaled from the zoom subject center.\n * Therefore, it's sufficient to base our calculation on the distance from the zoom subject center to the zoom center.\n *\n * ```\n * newDistanceZoomSubjectCenterToZoomCenter = oldDistanceZoomSubjectCenterToZoomCenter * (newScale/oldScale)\n * ```\n *\n * Once we have `newDistanceZoomSubjectCenterToZoomCenter`,\n * we can easily calculate the position of the new center, which leads us to the offset amount.\n * Refer to the code for more details\n *\n * @param oldOffsetXOrYScaled\n * @param zoomSubjectOriginalWidthOrHeight\n * @param oldScale\n * @param newScale\n * @param zoomCenterXOrY\n */\nexport function calcNewScaledOffsetForZoomCentering(\n oldOffsetXOrYScaled: number,\n zoomSubjectOriginalWidthOrHeight: number,\n oldScale: number,\n newScale: number,\n zoomCenterXOrY: number\n) {\n const oldOffSetUnscaled = oldOffsetXOrYScaled * oldScale;\n const growthRate = newScale / oldScale;\n\n // these act like namespaces just for the sake of readability\n const zoomSubjectOriginalCenter = {} as Center;\n const zoomSubjectCurrentCenter = {} as Center;\n const zoomSubjectNewCenter = {} as Center;\n\n zoomSubjectOriginalCenter.xOrY = zoomSubjectOriginalWidthOrHeight / 2;\n zoomSubjectCurrentCenter.xOrY =\n zoomSubjectOriginalCenter.xOrY + oldOffSetUnscaled;\n zoomSubjectCurrentCenter.distanceToZoomCenter =\n zoomSubjectCurrentCenter.xOrY - zoomCenterXOrY;\n\n zoomSubjectNewCenter.distanceToZoomCenter =\n zoomSubjectCurrentCenter.distanceToZoomCenter * growthRate;\n zoomSubjectNewCenter.xOrY =\n zoomSubjectNewCenter.distanceToZoomCenter + zoomCenterXOrY;\n\n const newOffsetUnscaled =\n zoomSubjectNewCenter.xOrY - zoomSubjectOriginalCenter.xOrY;\n\n return newOffsetUnscaled / newScale;\n}\n\ninterface Center {\n xOrY: number;\n distanceToZoomCenter: number;\n}\n"]}
--------------------------------------------------------------------------------
/lib/module/helper/coordinateConversion.js:
--------------------------------------------------------------------------------
1 | export const defaultTransformSubjectData = {
2 | offsetX: 0,
3 | offsetY: 0,
4 | zoomLevel: 0,
5 | originalWidth: 0,
6 | originalHeight: 0,
7 | originalPageX: 0,
8 | originalPageY: 0
9 | };
10 | /**
11 | * Assuming you have an image that's being resized to fit into a container
12 | * using the "contain" resize mode. You can use this function to calculate the
13 | * size of the image after fitting.
14 | *
15 | * Since our sheet is resized in this manner, we need this function
16 | * for things like pan boundaries and marker placement
17 | *
18 | * @param imgSize
19 | * @param containerSize
20 | */
21 |
22 | export function applyContainResizeMode(imgSize, containerSize) {
23 | const {
24 | width: imageWidth,
25 | height: imageHeight
26 | } = imgSize;
27 | const {
28 | width: areaWidth,
29 | height: areaHeight
30 | } = containerSize;
31 | const imageAspect = imageWidth / imageHeight;
32 | const areaAspect = areaWidth / areaHeight;
33 | let newSize;
34 |
35 | if (imageAspect >= areaAspect) {
36 | // longest edge is horizontal
37 | newSize = {
38 | width: areaWidth,
39 | height: areaWidth / imageAspect
40 | };
41 | } else {
42 | // longest edge is vertical
43 | newSize = {
44 | width: areaHeight * imageAspect,
45 | height: areaHeight
46 | };
47 | }
48 |
49 | if (isNaN(newSize.height)) newSize.height = areaHeight;
50 | if (isNaN(newSize.width)) newSize.width = areaWidth;
51 | const scale = imageWidth ? newSize.width / imageWidth : newSize.height / imageHeight;
52 | if (!isFinite(scale)) return {
53 | size: null,
54 | scale: null
55 | };
56 | return {
57 | size: newSize,
58 | scale
59 | };
60 | }
61 | /**
62 | * get the coord of image's origin relative to the transformSubject
63 | * @param resizedImageSize
64 | * @param transformSubject
65 | */
66 |
67 | export function getImageOriginOnTransformSubject(resizedImageSize, transformSubject) {
68 | const {
69 | offsetX,
70 | offsetY,
71 | zoomLevel,
72 | originalWidth,
73 | originalHeight
74 | } = transformSubject;
75 | return {
76 | x: offsetX * zoomLevel + originalWidth / 2 - resizedImageSize.width / 2 * zoomLevel,
77 | y: offsetY * zoomLevel + originalHeight / 2 - resizedImageSize.height / 2 * zoomLevel
78 | };
79 | }
80 | /**
81 | * Translates the coord system of a point from the viewport's space to the image's space
82 | *
83 | * @param pointOnContainer
84 | * @param sheetImageSize
85 | * @param transformSubject
86 | *
87 | * @return {Vec2D} returns null if point is out of the sheet's bound
88 | */
89 |
90 | export function viewportPositionToImagePosition({
91 | viewportPosition,
92 | imageSize,
93 | zoomableEvent
94 | }) {
95 | const {
96 | size: resizedImgSize,
97 | scale: resizedImgScale
98 | } = applyContainResizeMode(imageSize, {
99 | width: zoomableEvent.originalWidth,
100 | height: zoomableEvent.originalHeight
101 | });
102 | if (resizedImgScale == null) return null;
103 | const sheetOriginOnContainer = getImageOriginOnTransformSubject(resizedImgSize, zoomableEvent);
104 | const pointOnSheet = {
105 | x: (viewportPosition.x - sheetOriginOnContainer.x) / zoomableEvent.zoomLevel / resizedImgScale,
106 | y: (viewportPosition.y - sheetOriginOnContainer.y) / zoomableEvent.zoomLevel / resizedImgScale
107 | };
108 | return pointOnSheet;
109 | }
110 | //# sourceMappingURL=coordinateConversion.js.map
--------------------------------------------------------------------------------
/lib/module/helper/coordinateConversion.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["coordinateConversion.ts"],"names":["defaultTransformSubjectData","offsetX","offsetY","zoomLevel","originalWidth","originalHeight","originalPageX","originalPageY","applyContainResizeMode","imgSize","containerSize","width","imageWidth","height","imageHeight","areaWidth","areaHeight","imageAspect","areaAspect","newSize","isNaN","scale","isFinite","size","getImageOriginOnTransformSubject","resizedImageSize","transformSubject","x","y","viewportPositionToImagePosition","viewportPosition","imageSize","zoomableEvent","resizedImgSize","resizedImgScale","sheetOriginOnContainer","pointOnSheet"],"mappings":"AAEA,OAAO,MAAMA,2BAA8C,GAAG;AAC5DC,EAAAA,OAAO,EAAE,CADmD;AAE5DC,EAAAA,OAAO,EAAE,CAFmD;AAG5DC,EAAAA,SAAS,EAAE,CAHiD;AAI5DC,EAAAA,aAAa,EAAE,CAJ6C;AAK5DC,EAAAA,cAAc,EAAE,CAL4C;AAM5DC,EAAAA,aAAa,EAAE,CAN6C;AAO5DC,EAAAA,aAAa,EAAE;AAP6C,CAAvD;AAUP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACA,OAAO,SAASC,sBAAT,CACLC,OADK,EAELC,aAFK,EAG0D;AAC/D,QAAM;AAAEC,IAAAA,KAAK,EAAEC,UAAT;AAAqBC,IAAAA,MAAM,EAAEC;AAA7B,MAA6CL,OAAnD;AACA,QAAM;AAAEE,IAAAA,KAAK,EAAEI,SAAT;AAAoBF,IAAAA,MAAM,EAAEG;AAA5B,MAA2CN,aAAjD;AACA,QAAMO,WAAW,GAAGL,UAAU,GAAGE,WAAjC;AACA,QAAMI,UAAU,GAAGH,SAAS,GAAGC,UAA/B;AAEA,MAAIG,OAAJ;;AACA,MAAIF,WAAW,IAAIC,UAAnB,EAA+B;AAC7B;AACAC,IAAAA,OAAO,GAAG;AAAER,MAAAA,KAAK,EAAEI,SAAT;AAAoBF,MAAAA,MAAM,EAAEE,SAAS,GAAGE;AAAxC,KAAV;AACD,GAHD,MAGO;AACL;AACAE,IAAAA,OAAO,GAAG;AAAER,MAAAA,KAAK,EAAEK,UAAU,GAAGC,WAAtB;AAAmCJ,MAAAA,MAAM,EAAEG;AAA3C,KAAV;AACD;;AAED,MAAII,KAAK,CAACD,OAAO,CAACN,MAAT,CAAT,EAA2BM,OAAO,CAACN,MAAR,GAAiBG,UAAjB;AAC3B,MAAII,KAAK,CAACD,OAAO,CAACR,KAAT,CAAT,EAA0BQ,OAAO,CAACR,KAAR,GAAgBI,SAAhB;AAE1B,QAAMM,KAAK,GAAGT,UAAU,GACpBO,OAAO,CAACR,KAAR,GAAgBC,UADI,GAEpBO,OAAO,CAACN,MAAR,GAAiBC,WAFrB;AAIA,MAAI,CAACQ,QAAQ,CAACD,KAAD,CAAb,EAAsB,OAAO;AAAEE,IAAAA,IAAI,EAAE,IAAR;AAAcF,IAAAA,KAAK,EAAE;AAArB,GAAP;AAEtB,SAAO;AACLE,IAAAA,IAAI,EAAEJ,OADD;AAELE,IAAAA;AAFK,GAAP;AAID;AAED;AACA;AACA;AACA;AACA;;AACA,OAAO,SAASG,gCAAT,CACLC,gBADK,EAELC,gBAFK,EAGL;AACA,QAAM;AAAEzB,IAAAA,OAAF;AAAWC,IAAAA,OAAX;AAAoBC,IAAAA,SAApB;AAA+BC,IAAAA,aAA/B;AAA8CC,IAAAA;AAA9C,MACJqB,gBADF;AAEA,SAAO;AACLC,IAAAA,CAAC,EACC1B,OAAO,GAAGE,SAAV,GACAC,aAAa,GAAG,CADhB,GAECqB,gBAAgB,CAACd,KAAjB,GAAyB,CAA1B,GAA+BR,SAJ5B;AAKLyB,IAAAA,CAAC,EACC1B,OAAO,GAAGC,SAAV,GACAE,cAAc,GAAG,CADjB,GAECoB,gBAAgB,CAACZ,MAAjB,GAA0B,CAA3B,GAAgCV;AAR7B,GAAP;AAUD;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACA,OAAO,SAAS0B,+BAAT,CAAyC;AAC9CC,EAAAA,gBAD8C;AAE9CC,EAAAA,SAF8C;AAG9CC,EAAAA;AAH8C,CAAzC,EAQU;AACf,QAAM;AAAET,IAAAA,IAAI,EAAEU,cAAR;AAAwBZ,IAAAA,KAAK,EAAEa;AAA/B,MACJ1B,sBAAsB,CAACuB,SAAD,EAAY;AAChCpB,IAAAA,KAAK,EAAEqB,aAAa,CAAC5B,aADW;AAEhCS,IAAAA,MAAM,EAAEmB,aAAa,CAAC3B;AAFU,GAAZ,CADxB;AAMA,MAAI6B,eAAe,IAAI,IAAvB,EAA6B,OAAO,IAAP;AAE7B,QAAMC,sBAAsB,GAAGX,gCAAgC,CAC7DS,cAD6D,EAE7DD,aAF6D,CAA/D;AAKA,QAAMI,YAAY,GAAG;AACnBT,IAAAA,CAAC,EACC,CAACG,gBAAgB,CAACH,CAAjB,GAAqBQ,sBAAsB,CAACR,CAA7C,IACAK,aAAa,CAAC7B,SADd,GAEA+B,eAJiB;AAKnBN,IAAAA,CAAC,EACC,CAACE,gBAAgB,CAACF,CAAjB,GAAqBO,sBAAsB,CAACP,CAA7C,IACAI,aAAa,CAAC7B,SADd,GAEA+B;AARiB,GAArB;AAWA,SAAOE,YAAP;AACD","sourcesContent":["import { Size2D, Vec2D, ZoomableViewEvent } from 'src/typings';\n\nexport const defaultTransformSubjectData: ZoomableViewEvent = {\n offsetX: 0,\n offsetY: 0,\n zoomLevel: 0,\n originalWidth: 0,\n originalHeight: 0,\n originalPageX: 0,\n originalPageY: 0,\n};\n\n/**\n * Assuming you have an image that's being resized to fit into a container\n * using the \"contain\" resize mode. You can use this function to calculate the\n * size of the image after fitting.\n *\n * Since our sheet is resized in this manner, we need this function\n * for things like pan boundaries and marker placement\n *\n * @param imgSize\n * @param containerSize\n */\nexport function applyContainResizeMode(\n imgSize: Size2D,\n containerSize: Size2D\n): { size: Size2D; scale: number } | { size: null; scale: null } {\n const { width: imageWidth, height: imageHeight } = imgSize;\n const { width: areaWidth, height: areaHeight } = containerSize;\n const imageAspect = imageWidth / imageHeight;\n const areaAspect = areaWidth / areaHeight;\n\n let newSize;\n if (imageAspect >= areaAspect) {\n // longest edge is horizontal\n newSize = { width: areaWidth, height: areaWidth / imageAspect };\n } else {\n // longest edge is vertical\n newSize = { width: areaHeight * imageAspect, height: areaHeight };\n }\n\n if (isNaN(newSize.height)) newSize.height = areaHeight;\n if (isNaN(newSize.width)) newSize.width = areaWidth;\n\n const scale = imageWidth\n ? newSize.width / imageWidth\n : newSize.height / imageHeight;\n\n if (!isFinite(scale)) return { size: null, scale: null };\n\n return {\n size: newSize,\n scale,\n };\n}\n\n/**\n * get the coord of image's origin relative to the transformSubject\n * @param resizedImageSize\n * @param transformSubject\n */\nexport function getImageOriginOnTransformSubject(\n resizedImageSize: Size2D,\n transformSubject: ZoomableViewEvent\n) {\n const { offsetX, offsetY, zoomLevel, originalWidth, originalHeight } =\n transformSubject;\n return {\n x:\n offsetX * zoomLevel +\n originalWidth / 2 -\n (resizedImageSize.width / 2) * zoomLevel,\n y:\n offsetY * zoomLevel +\n originalHeight / 2 -\n (resizedImageSize.height / 2) * zoomLevel,\n };\n}\n\n/**\n * Translates the coord system of a point from the viewport's space to the image's space\n *\n * @param pointOnContainer\n * @param sheetImageSize\n * @param transformSubject\n *\n * @return {Vec2D} returns null if point is out of the sheet's bound\n */\nexport function viewportPositionToImagePosition({\n viewportPosition,\n imageSize,\n zoomableEvent,\n}: {\n viewportPosition: Vec2D;\n imageSize: Size2D;\n zoomableEvent: ZoomableViewEvent;\n}): Vec2D | null {\n const { size: resizedImgSize, scale: resizedImgScale } =\n applyContainResizeMode(imageSize, {\n width: zoomableEvent.originalWidth,\n height: zoomableEvent.originalHeight,\n });\n\n if (resizedImgScale == null) return null;\n\n const sheetOriginOnContainer = getImageOriginOnTransformSubject(\n resizedImgSize,\n zoomableEvent\n );\n\n const pointOnSheet = {\n x:\n (viewportPosition.x - sheetOriginOnContainer.x) /\n zoomableEvent.zoomLevel /\n resizedImgScale,\n y:\n (viewportPosition.y - sheetOriginOnContainer.y) /\n zoomableEvent.zoomLevel /\n resizedImgScale,\n };\n\n return pointOnSheet;\n}\n"]}
--------------------------------------------------------------------------------
/lib/module/helper/index.js:
--------------------------------------------------------------------------------
1 | export { calcNewScaledOffsetForZoomCentering } from './calcNewScaledOffsetForZoomCentering';
2 | /**
3 | * Calculates the gesture center point relative to the page coordinate system
4 | *
5 | * We're unable to use touch.locationX/Y
6 | * because locationX uses the axis system of the leaf element that the touch occurs on,
7 | * which makes it even more complicated to translate into our container's axis system.
8 | *
9 | * We're also unable to use gestureState.moveX/Y
10 | * because gestureState.moveX/Y is messed up on real device
11 | * (Sometimes it's the center point, but sometimes it randomly takes the position of one of the touches)
12 | */
13 |
14 | export function calcGestureCenterPoint(e, gestureState) {
15 | const touches = e.nativeEvent.touches;
16 | if (!touches[0]) return null;
17 |
18 | if (gestureState.numberActiveTouches === 2) {
19 | if (!touches[1]) return null;
20 | return {
21 | x: (touches[0].pageX + touches[1].pageX) / 2,
22 | y: (touches[0].pageY + touches[1].pageY) / 2
23 | };
24 | }
25 |
26 | if (gestureState.numberActiveTouches === 1) {
27 | return {
28 | x: touches[0].pageX,
29 | y: touches[0].pageY
30 | };
31 | }
32 |
33 | return null;
34 | }
35 | export function calcGestureTouchDistance(e, gestureState) {
36 | const touches = e.nativeEvent.touches;
37 | if (gestureState.numberActiveTouches !== 2 || !touches[0] || !touches[1]) return null;
38 | const dx = Math.abs(touches[0].pageX - touches[1].pageX);
39 | const dy = Math.abs(touches[0].pageY - touches[1].pageY);
40 | return Math.sqrt(dx * dx + dy * dy);
41 | }
42 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/lib/module/helper/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.ts"],"names":["calcNewScaledOffsetForZoomCentering","calcGestureCenterPoint","e","gestureState","touches","nativeEvent","numberActiveTouches","x","pageX","y","pageY","calcGestureTouchDistance","dx","Math","abs","dy","sqrt"],"mappings":"AAGA,SAASA,mCAAT,QAAoD,uCAApD;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACA,OAAO,SAASC,sBAAT,CACLC,CADK,EAELC,YAFK,EAGS;AACd,QAAMC,OAAO,GAAGF,CAAC,CAACG,WAAF,CAAcD,OAA9B;AACA,MAAI,CAACA,OAAO,CAAC,CAAD,CAAZ,EAAiB,OAAO,IAAP;;AAEjB,MAAID,YAAY,CAACG,mBAAb,KAAqC,CAAzC,EAA4C;AAC1C,QAAI,CAACF,OAAO,CAAC,CAAD,CAAZ,EAAiB,OAAO,IAAP;AACjB,WAAO;AACLG,MAAAA,CAAC,EAAE,CAACH,OAAO,CAAC,CAAD,CAAP,CAAWI,KAAX,GAAmBJ,OAAO,CAAC,CAAD,CAAP,CAAWI,KAA/B,IAAwC,CADtC;AAELC,MAAAA,CAAC,EAAE,CAACL,OAAO,CAAC,CAAD,CAAP,CAAWM,KAAX,GAAmBN,OAAO,CAAC,CAAD,CAAP,CAAWM,KAA/B,IAAwC;AAFtC,KAAP;AAID;;AACD,MAAIP,YAAY,CAACG,mBAAb,KAAqC,CAAzC,EAA4C;AAC1C,WAAO;AACLC,MAAAA,CAAC,EAAEH,OAAO,CAAC,CAAD,CAAP,CAAWI,KADT;AAELC,MAAAA,CAAC,EAAEL,OAAO,CAAC,CAAD,CAAP,CAAWM;AAFT,KAAP;AAID;;AAED,SAAO,IAAP;AACD;AAED,OAAO,SAASC,wBAAT,CACLT,CADK,EAELC,YAFK,EAGU;AACf,QAAMC,OAAO,GAAGF,CAAC,CAACG,WAAF,CAAcD,OAA9B;AACA,MAAID,YAAY,CAACG,mBAAb,KAAqC,CAArC,IAA0C,CAACF,OAAO,CAAC,CAAD,CAAlD,IAAyD,CAACA,OAAO,CAAC,CAAD,CAArE,EACE,OAAO,IAAP;AAEF,QAAMQ,EAAE,GAAGC,IAAI,CAACC,GAAL,CAASV,OAAO,CAAC,CAAD,CAAP,CAAWI,KAAX,GAAmBJ,OAAO,CAAC,CAAD,CAAP,CAAWI,KAAvC,CAAX;AACA,QAAMO,EAAE,GAAGF,IAAI,CAACC,GAAL,CAASV,OAAO,CAAC,CAAD,CAAP,CAAWM,KAAX,GAAmBN,OAAO,CAAC,CAAD,CAAP,CAAWM,KAAvC,CAAX;AACA,SAAOG,IAAI,CAACG,IAAL,CAAUJ,EAAE,GAAGA,EAAL,GAAUG,EAAE,GAAGA,EAAzB,CAAP;AACD","sourcesContent":["import { GestureResponderEvent, PanResponderGestureState } from 'react-native';\nimport { Vec2D } from '../typings';\n\nexport { calcNewScaledOffsetForZoomCentering } from './calcNewScaledOffsetForZoomCentering';\n\n/**\n * Calculates the gesture center point relative to the page coordinate system\n *\n * We're unable to use touch.locationX/Y\n * because locationX uses the axis system of the leaf element that the touch occurs on,\n * which makes it even more complicated to translate into our container's axis system.\n *\n * We're also unable to use gestureState.moveX/Y\n * because gestureState.moveX/Y is messed up on real device\n * (Sometimes it's the center point, but sometimes it randomly takes the position of one of the touches)\n */\nexport function calcGestureCenterPoint(\n e: GestureResponderEvent,\n gestureState: PanResponderGestureState\n): Vec2D | null {\n const touches = e.nativeEvent.touches;\n if (!touches[0]) return null;\n\n if (gestureState.numberActiveTouches === 2) {\n if (!touches[1]) return null;\n return {\n x: (touches[0].pageX + touches[1].pageX) / 2,\n y: (touches[0].pageY + touches[1].pageY) / 2,\n };\n }\n if (gestureState.numberActiveTouches === 1) {\n return {\n x: touches[0].pageX,\n y: touches[0].pageY,\n };\n }\n\n return null;\n}\n\nexport function calcGestureTouchDistance(\n e: GestureResponderEvent,\n gestureState: PanResponderGestureState\n): number | null {\n const touches = e.nativeEvent.touches;\n if (gestureState.numberActiveTouches !== 2 || !touches[0] || !touches[1])\n return null;\n\n const dx = Math.abs(touches[0].pageX - touches[1].pageX);\n const dy = Math.abs(touches[0].pageY - touches[1].pageY);\n return Math.sqrt(dx * dx + dy * dy);\n}\n"]}
--------------------------------------------------------------------------------
/lib/module/index.js:
--------------------------------------------------------------------------------
1 | import { applyContainResizeMode, getImageOriginOnTransformSubject, viewportPositionToImagePosition } from './helper/coordinateConversion';
2 | import ReactNativeZoomableView from './ReactNativeZoomableView';
3 | export { ReactNativeZoomableView, // Helper functions for coordinate conversion
4 | applyContainResizeMode, getImageOriginOnTransformSubject, viewportPositionToImagePosition };
5 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/lib/module/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.tsx"],"names":["applyContainResizeMode","getImageOriginOnTransformSubject","viewportPositionToImagePosition","ReactNativeZoomableView"],"mappings":"AAAA,SACEA,sBADF,EAEEC,gCAFF,EAGEC,+BAHF,QAIO,+BAJP;AAKA,OAAOC,uBAAP,MAAoC,2BAApC;AAMA,SACEA,uBADF,EAIE;AACAH,sBALF,EAMEC,gCANF,EAOEC,+BAPF","sourcesContent":["import {\n applyContainResizeMode,\n getImageOriginOnTransformSubject,\n viewportPositionToImagePosition,\n} from './helper/coordinateConversion';\nimport ReactNativeZoomableView from './ReactNativeZoomableView';\nimport type {\n ReactNativeZoomableViewProps,\n ZoomableViewEvent,\n} from './typings';\n\nexport {\n ReactNativeZoomableView,\n ReactNativeZoomableViewProps,\n ZoomableViewEvent,\n // Helper functions for coordinate conversion\n applyContainResizeMode,\n getImageOriginOnTransformSubject,\n viewportPositionToImagePosition,\n};\n"]}
--------------------------------------------------------------------------------
/lib/module/typings/index.js:
--------------------------------------------------------------------------------
1 | export let SwipeDirection;
2 |
3 | (function (SwipeDirection) {
4 | SwipeDirection["SWIPE_UP"] = "SWIPE_UP";
5 | SwipeDirection["SWIPE_DOWN"] = "SWIPE_DOWN";
6 | SwipeDirection["SWIPE_LEFT"] = "SWIPE_LEFT";
7 | SwipeDirection["SWIPE_RIGHT"] = "SWIPE_RIGHT";
8 | })(SwipeDirection || (SwipeDirection = {}));
9 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/lib/module/typings/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.ts"],"names":["SwipeDirection"],"mappings":"AASA,WAAYA,cAAZ;;WAAYA,c;AAAAA,EAAAA,c;AAAAA,EAAAA,c;AAAAA,EAAAA,c;AAAAA,EAAAA,c;GAAAA,c,KAAAA,c","sourcesContent":["import {\n Animated,\n GestureResponderEvent,\n LayoutChangeEvent,\n PanResponderGestureState,\n ViewProps,\n} from 'react-native';\nimport { ReactNode } from 'react';\n\nexport enum SwipeDirection {\n SWIPE_UP = 'SWIPE_UP',\n SWIPE_DOWN = 'SWIPE_DOWN',\n SWIPE_LEFT = 'SWIPE_LEFT',\n SWIPE_RIGHT = 'SWIPE_RIGHT',\n}\n\nexport interface ZoomableViewEvent {\n zoomLevel: number;\n offsetX: number;\n offsetY: number;\n originalHeight: number;\n originalWidth: number;\n originalPageX: number;\n originalPageY: number;\n}\n\nexport interface ReactNativeZoomableViewProps {\n // options\n style?: ViewProps['style'];\n children?: ReactNode;\n zoomEnabled?: boolean;\n panEnabled?: boolean;\n initialZoom?: number;\n initialOffsetX?: number;\n initialOffsetY?: number;\n contentWidth?: number;\n contentHeight?: number;\n panBoundaryPadding?: number;\n maxZoom?: number;\n minZoom?: number;\n doubleTapDelay?: number;\n doubleTapZoomToCenter?: boolean;\n bindToBorders?: boolean;\n zoomStep?: number;\n pinchToZoomInSensitivity?: number;\n pinchToZoomOutSensitivity?: number;\n movementSensibility?: number;\n longPressDuration?: number;\n visualTouchFeedbackEnabled?: boolean;\n disablePanOnInitialZoom?: boolean;\n\n // Zoom animated value ref\n zoomAnimatedValue?: Animated.Value;\n panAnimatedValueXY?: Animated.ValueXY;\n\n // debug\n debug?: boolean;\n\n // callbacks\n onLayout?: (event: Pick) => void;\n onTransform?: (zoomableViewEventObject: ZoomableViewEvent) => void;\n onSingleTap?: (\n event: GestureResponderEvent,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onDoubleTapBefore?: (\n event: GestureResponderEvent,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onDoubleTapAfter?: (\n event: GestureResponderEvent,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onShiftingBefore?: (\n event: GestureResponderEvent | null,\n gestureState: PanResponderGestureState | null,\n zoomableViewEventObject: ZoomableViewEvent\n ) => boolean;\n onShiftingAfter?: (\n event: GestureResponderEvent | null,\n gestureState: PanResponderGestureState | null,\n zoomableViewEventObject: ZoomableViewEvent\n ) => boolean;\n onShiftingEnd?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onZoomBefore?: (\n event: GestureResponderEvent | null,\n gestureState: PanResponderGestureState | null,\n zoomableViewEventObject: ZoomableViewEvent\n ) => boolean | undefined;\n onZoomAfter?: (\n event: GestureResponderEvent | null,\n gestureState: PanResponderGestureState | null,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onZoomEnd?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onLongPress?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onStartShouldSetPanResponder?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent,\n baseComponentResult: boolean\n ) => boolean;\n onPanResponderGrant?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onPanResponderEnd?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onPanResponderMove?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => boolean;\n onPanResponderTerminate?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => void;\n onPanResponderTerminationRequest?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => boolean;\n onShouldBlockNativeResponder?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState,\n zoomableViewEventObject: ZoomableViewEvent\n ) => boolean;\n onStartShouldSetPanResponderCapture?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState\n ) => boolean;\n onMoveShouldSetPanResponderCapture?: (\n event: GestureResponderEvent,\n gestureState: PanResponderGestureState\n ) => boolean;\n onStaticPinPress?: (event: GestureResponderEvent) => void;\n onStaticPinLongPress?: (event: GestureResponderEvent) => void;\n staticPinPosition?: Vec2D;\n staticPinIcon?: React.ReactElement;\n onStaticPinPositionChange?: (position: Vec2D) => void;\n onStaticPinPositionMove?: (position: Vec2D) => void;\n animatePin: boolean;\n pinProps?: ViewProps;\n disableMomentum?: boolean;\n}\n\nexport interface Vec2D {\n x: number;\n y: number;\n}\n\nexport interface Size2D {\n width: number;\n height: number;\n}\n\nexport interface TouchPoint extends Vec2D {\n id: string;\n isSecondTap?: boolean;\n}\n\nexport interface ReactNativeZoomableViewState {\n touches?: TouchPoint[];\n originalWidth: number;\n originalHeight: number;\n originalPageX: number;\n originalPageY: number;\n originalX: number;\n originalY: number;\n debugPoints?: undefined | Vec2D[];\n pinSize: Size2D;\n}\n\nexport interface ReactNativeZoomableViewWithGesturesProps\n extends ReactNativeZoomableViewProps {\n swipeLengthThreshold?: number;\n swipeVelocityThreshold?: number;\n swipeDirectionalThreshold?: number;\n swipeMinZoom?: number;\n swipeMaxZoom?: number;\n swipeDisabled?: boolean;\n onSwipe?: (\n swipeDirection: SwipeDirection,\n gestureState: PanResponderGestureState\n ) => void;\n onSwipeUp?: (gestureState: PanResponderGestureState) => void;\n onSwipeDown?: (gestureState: PanResponderGestureState) => void;\n onSwipeLeft?: (gestureState: PanResponderGestureState) => void;\n onSwipeRight?: (gestureState: PanResponderGestureState) => void;\n}\n"]}
--------------------------------------------------------------------------------
/lib/typescript/ReactNativeZoomableView.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { Component, RefObject } from 'react';
3 | import { GestureResponderEvent, PanResponderCallbacks, PanResponderGestureState, PanResponderInstance, View } from 'react-native';
4 | import { Vec2D, ReactNativeZoomableViewProps, ReactNativeZoomableViewState, ZoomableViewEvent } from './typings';
5 | declare class ReactNativeZoomableView extends Component {
6 | zoomSubjectWrapperRef: RefObject;
7 | gestureHandlers: PanResponderInstance;
8 | doubleTapFirstTapReleaseTimestamp: number | undefined;
9 | static defaultProps: {
10 | zoomEnabled: boolean;
11 | panEnabled: boolean;
12 | initialZoom: number;
13 | initialOffsetX: number;
14 | initialOffsetY: number;
15 | maxZoom: number;
16 | minZoom: number;
17 | pinchToZoomInSensitivity: number;
18 | pinchToZoomOutSensitivity: number;
19 | movementSensibility: number;
20 | doubleTapDelay: number;
21 | bindToBorders: boolean;
22 | zoomStep: number;
23 | onLongPress: null;
24 | longPressDuration: number;
25 | contentWidth: undefined;
26 | contentHeight: undefined;
27 | panBoundaryPadding: number;
28 | visualTouchFeedbackEnabled: boolean;
29 | staticPinPosition: undefined;
30 | staticPinIcon: undefined;
31 | onStaticPinPositionChange: undefined;
32 | onStaticPinPositionMove: undefined;
33 | animatePin: boolean;
34 | disablePanOnInitialZoom: boolean;
35 | };
36 | private panAnim;
37 | private zoomAnim;
38 | private pinAnim;
39 | private __offsets;
40 | private zoomLevel;
41 | private lastGestureCenterPosition;
42 | private lastGestureTouchDistance;
43 | private gestureType;
44 | private _gestureStarted;
45 | private set gestureStarted(value);
46 | get gestureStarted(): boolean;
47 | /**
48 | * Last press time (used to evaluate whether user double tapped)
49 | * @type {number}
50 | */
51 | private longPressTimeout;
52 | private onTransformInvocationInitialized;
53 | private singleTapTimeoutId;
54 | private touches;
55 | private doubleTapFirstTap;
56 | private measureZoomSubjectInterval;
57 | constructor(props: ReactNativeZoomableViewProps);
58 | private raisePin;
59 | private dropPin;
60 | private set offsetX(value);
61 | private set offsetY(value);
62 | private get offsetX();
63 | private get offsetY();
64 | private __setOffset;
65 | private __getOffset;
66 | componentDidUpdate(prevProps: ReactNativeZoomableViewProps, prevState: ReactNativeZoomableViewState): void;
67 | componentDidMount(): void;
68 | componentWillUnmount(): void;
69 | debouncedOnStaticPinPositionChange: import("lodash").DebouncedFunc<(position: Vec2D) => void | undefined>;
70 | /**
71 | * try to invoke onTransform
72 | * @private
73 | */
74 | _invokeOnTransform(): {
75 | successful: boolean;
76 | };
77 | /**
78 | * Returns additional information about components current state for external event hooks
79 | *
80 | * @returns {{}}
81 | * @private
82 | */
83 | _getZoomableViewEventObject(overwriteObj?: {}): ZoomableViewEvent;
84 | /**
85 | * Get the original box dimensions and save them for later use.
86 | * (They will be used to calculate boxBorders)
87 | *
88 | * @private
89 | */
90 | private measureZoomSubject;
91 | /**
92 | * Handles the start of touch events and checks for taps
93 | *
94 | * @param e
95 | * @param gestureState
96 | * @returns {boolean}
97 | *
98 | * @private
99 | */
100 | _handleStartShouldSetPanResponder: (e: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
101 | /**
102 | * Calculates pinch distance
103 | *
104 | * @param e
105 | * @param gestureState
106 | * @private
107 | */
108 | _handlePanResponderGrant: NonNullable;
109 | /**
110 | * Handles the end of touch events
111 | *
112 | * @param e
113 | * @param gestureState
114 | *
115 | * @private
116 | */
117 | _handlePanResponderEnd: NonNullable;
118 | /**
119 | * Handles the actual movement of our pan responder
120 | *
121 | * @param e
122 | * @param gestureState
123 | *
124 | * @private
125 | */
126 | _handlePanResponderMove: (e: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean | undefined;
127 | /**
128 | * Handles the pinch movement and zooming
129 | *
130 | * @param e
131 | * @param gestureState
132 | *
133 | * @private
134 | */
135 | _handlePinching(e: GestureResponderEvent, gestureState: PanResponderGestureState): void;
136 | /**
137 | * Used to debug pinch events
138 | * @param gestureResponderEvent
139 | * @param zoomCenter
140 | * @param points
141 | */
142 | _setPinchDebugPoints(gestureResponderEvent: GestureResponderEvent, zoomCenter: Vec2D, ...points: Vec2D[]): void;
143 | /**
144 | * Calculates the amount the offset should shift since the last position during panning
145 | *
146 | * @param {Vec2D} gestureCenterPoint
147 | *
148 | * @private
149 | */
150 | _calcOffsetShiftSinceLastGestureState(gestureCenterPoint: Vec2D): {
151 | x: number;
152 | y: number;
153 | } | null;
154 | /**
155 | * Handles movement by tap and move
156 | *
157 | * @param gestureState
158 | *
159 | * @private
160 | */
161 | _handleShifting(gestureState: PanResponderGestureState): void;
162 | /**
163 | * Set the state to offset moved
164 | *
165 | * @param {number} newOffsetX
166 | * @param {number} newOffsetY
167 | * @returns
168 | */
169 | _setNewOffsetPosition(newOffsetX: number, newOffsetY: number): void;
170 | /**
171 | * Check whether the press event is double tap
172 | * or single tap and handle the event accordingly
173 | *
174 | * @param e
175 | *
176 | * @private
177 | */
178 | private _resolveAndHandleTap;
179 | moveStaticPinTo: (position: Vec2D, duration?: number) => void;
180 | private _staticPinPosition;
181 | private _updateStaticPin;
182 | private _addTouch;
183 | private _removeTouch;
184 | /**
185 | * Handles the double tap event
186 | *
187 | * @param e
188 | *
189 | * @private
190 | */
191 | _handleDoubleTap(e: GestureResponderEvent): void;
192 | /**
193 | * Returns the next zoom step based on current step and zoomStep property.
194 | * If we are zoomed all the way in -> return to initialzoom
195 | *
196 | * @returns {*}
197 | */
198 | _getNextZoomStep(): number | undefined;
199 | /**
200 | * Zooms to a specific level. A "zoom center" can be provided, which specifies
201 | * the point that will remain in the same position on the screen after the zoom.
202 | * The coordinates of the zoom center is relative to the zoom subject.
203 | * { x: 0, y: 0 } is the very center of the zoom subject.
204 | *
205 | * @param newZoomLevel
206 | * @param zoomCenter - If not supplied, the container's center is the zoom center
207 | */
208 | zoomTo(newZoomLevel: number, zoomCenter?: Vec2D): boolean;
209 | /**
210 | * Zooms in or out by a specified change level
211 | * Use a positive number for `zoomLevelChange` to zoom in
212 | * Use a negative number for `zoomLevelChange` to zoom out
213 | *
214 | * Returns a promise if everything was updated and a boolean, whether it could be updated or if it exceeded the min/max zoom limits.
215 | *
216 | * @param {number | null} zoomLevelChange
217 | *
218 | * @return {bool}
219 | */
220 | zoomBy(zoomLevelChange: number): boolean;
221 | /**
222 | * Moves the zoomed view to a specified position
223 | * Returns a promise when finished
224 | *
225 | * @param {number} newOffsetX the new position we want to move it to (x-axis)
226 | * @param {number} newOffsetY the new position we want to move it to (y-axis)
227 | *
228 | * @return {bool}
229 | */
230 | moveTo(newOffsetX: number, newOffsetY: number): void;
231 | /**
232 | * Moves the zoomed view by a certain amount.
233 | *
234 | * Returns a promise when finished
235 | *
236 | * @param {number} offsetChangeX the amount we want to move the offset by (x-axis)
237 | * @param {number} offsetChangeY the amount we want to move the offset by (y-axis)
238 | *
239 | * @return {bool}
240 | */
241 | moveBy(offsetChangeX: number, offsetChangeY: number): void;
242 | render(): JSX.Element;
243 | }
244 | export default ReactNativeZoomableView;
245 | export { ReactNativeZoomableView };
246 |
--------------------------------------------------------------------------------
/lib/typescript/__tests__/index.test.d.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openspacelabs/react-native-zoomable-view/9a4219bfd5250bea2995309ac847b57de0e3b3ae/lib/typescript/__tests__/index.test.d.ts
--------------------------------------------------------------------------------
/lib/typescript/animations/index.d.ts:
--------------------------------------------------------------------------------
1 | import { Animated } from 'react-native';
2 | import { Vec2D } from '../typings';
3 | export declare function getBoundaryCrossedAnim(animValue: Animated.Value, toValue: number): Animated.CompositeAnimation;
4 | export declare function getPanMomentumDecayAnim(animValue: Animated.Value | Animated.ValueXY, velocity: number | Vec2D): Animated.CompositeAnimation;
5 | export declare function getZoomToAnimation(animValue: Animated.Value, toValue: number): Animated.CompositeAnimation;
6 |
--------------------------------------------------------------------------------
/lib/typescript/components/AnimatedTouchFeedback.d.ts:
--------------------------------------------------------------------------------
1 | export declare const AnimatedTouchFeedback: ({ x, y, animationDelay, animationDuration, onAnimationDone, }: {
2 | x: number;
3 | y: number;
4 | animationDuration: number;
5 | animationDelay?: number | undefined;
6 | onAnimationDone?: (() => void) | undefined;
7 | }) => JSX.Element;
8 |
--------------------------------------------------------------------------------
/lib/typescript/components/StaticPin.d.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Animated, GestureResponderEvent, PanResponderGestureState, ViewProps } from 'react-native';
3 | import { Size2D } from 'src/typings';
4 | export declare const StaticPin: ({ staticPinPosition, pinAnim, staticPinIcon, pinSize, onParentMove, onPress, onLongPress, setPinSize, pinProps, }: {
5 | staticPinPosition: {
6 | x: number;
7 | y: number;
8 | };
9 | pinAnim: Animated.ValueXY;
10 | staticPinIcon: React.ReactNode;
11 | pinSize: Size2D;
12 | /** Internal handler for passing move event to parent */
13 | onParentMove: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean | undefined;
14 | onPress?: ((evt: GestureResponderEvent) => void) | undefined;
15 | onLongPress?: ((evt: GestureResponderEvent) => void) | undefined;
16 | setPinSize: (size: Size2D) => void;
17 | pinProps?: ViewProps | undefined;
18 | }) => JSX.Element;
19 |
--------------------------------------------------------------------------------
/lib/typescript/components/index.d.ts:
--------------------------------------------------------------------------------
1 | export { AnimatedTouchFeedback } from './AnimatedTouchFeedback';
2 |
--------------------------------------------------------------------------------
/lib/typescript/debugHelper/index.d.ts:
--------------------------------------------------------------------------------
1 | export declare const DebugTouchPoint: ({ diameter, x, y, color, }: {
2 | diameter?: number | undefined;
3 | x?: number | undefined;
4 | y?: number | undefined;
5 | color?: string | undefined;
6 | }) => JSX.Element;
7 | export declare const DebugRect: ({ height, x, y, color, }: {
8 | height: number;
9 | x: number;
10 | y: number;
11 | color: string;
12 | }) => JSX.Element;
13 |
--------------------------------------------------------------------------------
/lib/typescript/helper/applyPanBoundariesToOffset.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Takes a single offset value and calculates the correct offset value
3 | * to make sure it's within the pan boundaries
4 | *
5 | *
6 | * @param offsetScaled
7 | * @param containerSize
8 | * @param contentSize
9 | * @param scale
10 | * @param boundaryPadding - see README
11 | *
12 | * @returns {number}
13 | */
14 | export declare function applyPanBoundariesToOffset(offsetScaled: number, containerSize: number, contentSize: number, scale: number, boundaryPadding: number): number;
15 |
--------------------------------------------------------------------------------
/lib/typescript/helper/calcNewScaledOffsetForZoomCentering.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Calculates the new offset for the zoomSubject to ensure zoom center position is retained after zooming.
3 | * Parameters should correspond to whether we need the offset for the X or Y axis
4 | *
5 | * ## Terms Used:
6 | *
7 | * - Zoom Subject: the view that's being zoomed and panned
8 | * - Zoom Center: the point whose relative position to the window is retained
9 | * - Unscaled: a measurement in pixels denoting the true size as observed by the users' eyes
10 | * - Scaled: a measurement in pixels scaled to the "scale transformation" of the zoom subject to match with its true size.
11 | * *For example:*
12 | * If the scale on the zoom subject is 0.5,
13 | * then to draw an actual 4px line on the zoom subject, we need to scale it to 4px / 0.5 = 8px
14 | * 8px is the scaled measurement
15 | *
16 | * ## Overall idea of this algorithm:
17 | *
18 | * When users perform zooming by pinching the screen,
19 | * we need to shift the zoom subject so that the position of the zoom center is always the same.
20 | * The offset amount to shift the layer is the returned value.
21 | *
22 | *
23 | * ## How we achieve our goal:
24 | *
25 | * To retain, the zoom center position, whenever a zoom action is performed,
26 | * we just need to make sure the distances from all the points in the zoom subject
27 | * to the zoom center increases or decreases by the growth rate of the scale.
28 | *
29 | * ```
30 | * newDistanceAnyPointToZoomCenter = oldDistanceAnyPointToZoomCenter * (newScale/oldScale)
31 | * ```
32 | *
33 | * We can't calculate that for all the points because there are unlimited points on a plain.
34 | * However, due to the way `transform` works in RN, every point is scaled from the zoom subject center.
35 | * Therefore, it's sufficient to base our calculation on the distance from the zoom subject center to the zoom center.
36 | *
37 | * ```
38 | * newDistanceZoomSubjectCenterToZoomCenter = oldDistanceZoomSubjectCenterToZoomCenter * (newScale/oldScale)
39 | * ```
40 | *
41 | * Once we have `newDistanceZoomSubjectCenterToZoomCenter`,
42 | * we can easily calculate the position of the new center, which leads us to the offset amount.
43 | * Refer to the code for more details
44 | *
45 | * @param oldOffsetXOrYScaled
46 | * @param zoomSubjectOriginalWidthOrHeight
47 | * @param oldScale
48 | * @param newScale
49 | * @param zoomCenterXOrY
50 | */
51 | export declare function calcNewScaledOffsetForZoomCentering(oldOffsetXOrYScaled: number, zoomSubjectOriginalWidthOrHeight: number, oldScale: number, newScale: number, zoomCenterXOrY: number): number;
52 |
--------------------------------------------------------------------------------
/lib/typescript/helper/coordinateConversion.d.ts:
--------------------------------------------------------------------------------
1 | import { Size2D, Vec2D, ZoomableViewEvent } from 'src/typings';
2 | export declare const defaultTransformSubjectData: ZoomableViewEvent;
3 | /**
4 | * Assuming you have an image that's being resized to fit into a container
5 | * using the "contain" resize mode. You can use this function to calculate the
6 | * size of the image after fitting.
7 | *
8 | * Since our sheet is resized in this manner, we need this function
9 | * for things like pan boundaries and marker placement
10 | *
11 | * @param imgSize
12 | * @param containerSize
13 | */
14 | export declare function applyContainResizeMode(imgSize: Size2D, containerSize: Size2D): {
15 | size: Size2D;
16 | scale: number;
17 | } | {
18 | size: null;
19 | scale: null;
20 | };
21 | /**
22 | * get the coord of image's origin relative to the transformSubject
23 | * @param resizedImageSize
24 | * @param transformSubject
25 | */
26 | export declare function getImageOriginOnTransformSubject(resizedImageSize: Size2D, transformSubject: ZoomableViewEvent): {
27 | x: number;
28 | y: number;
29 | };
30 | /**
31 | * Translates the coord system of a point from the viewport's space to the image's space
32 | *
33 | * @param pointOnContainer
34 | * @param sheetImageSize
35 | * @param transformSubject
36 | *
37 | * @return {Vec2D} returns null if point is out of the sheet's bound
38 | */
39 | export declare function viewportPositionToImagePosition({ viewportPosition, imageSize, zoomableEvent, }: {
40 | viewportPosition: Vec2D;
41 | imageSize: Size2D;
42 | zoomableEvent: ZoomableViewEvent;
43 | }): Vec2D | null;
44 |
--------------------------------------------------------------------------------
/lib/typescript/helper/index.d.ts:
--------------------------------------------------------------------------------
1 | import { GestureResponderEvent, PanResponderGestureState } from 'react-native';
2 | import { Vec2D } from '../typings';
3 | export { calcNewScaledOffsetForZoomCentering } from './calcNewScaledOffsetForZoomCentering';
4 | /**
5 | * Calculates the gesture center point relative to the page coordinate system
6 | *
7 | * We're unable to use touch.locationX/Y
8 | * because locationX uses the axis system of the leaf element that the touch occurs on,
9 | * which makes it even more complicated to translate into our container's axis system.
10 | *
11 | * We're also unable to use gestureState.moveX/Y
12 | * because gestureState.moveX/Y is messed up on real device
13 | * (Sometimes it's the center point, but sometimes it randomly takes the position of one of the touches)
14 | */
15 | export declare function calcGestureCenterPoint(e: GestureResponderEvent, gestureState: PanResponderGestureState): Vec2D | null;
16 | export declare function calcGestureTouchDistance(e: GestureResponderEvent, gestureState: PanResponderGestureState): number | null;
17 |
--------------------------------------------------------------------------------
/lib/typescript/index.d.ts:
--------------------------------------------------------------------------------
1 | import { applyContainResizeMode, getImageOriginOnTransformSubject, viewportPositionToImagePosition } from './helper/coordinateConversion';
2 | import ReactNativeZoomableView from './ReactNativeZoomableView';
3 | import type { ReactNativeZoomableViewProps, ZoomableViewEvent } from './typings';
4 | export { ReactNativeZoomableView, ReactNativeZoomableViewProps, ZoomableViewEvent, applyContainResizeMode, getImageOriginOnTransformSubject, viewportPositionToImagePosition, };
5 |
--------------------------------------------------------------------------------
/lib/typescript/typings/index.d.ts:
--------------------------------------------------------------------------------
1 | import { Animated, GestureResponderEvent, LayoutChangeEvent, PanResponderGestureState, ViewProps } from 'react-native';
2 | import { ReactNode } from 'react';
3 | export declare enum SwipeDirection {
4 | SWIPE_UP = "SWIPE_UP",
5 | SWIPE_DOWN = "SWIPE_DOWN",
6 | SWIPE_LEFT = "SWIPE_LEFT",
7 | SWIPE_RIGHT = "SWIPE_RIGHT"
8 | }
9 | export interface ZoomableViewEvent {
10 | zoomLevel: number;
11 | offsetX: number;
12 | offsetY: number;
13 | originalHeight: number;
14 | originalWidth: number;
15 | originalPageX: number;
16 | originalPageY: number;
17 | }
18 | export interface ReactNativeZoomableViewProps {
19 | style?: ViewProps['style'];
20 | children?: ReactNode;
21 | zoomEnabled?: boolean;
22 | panEnabled?: boolean;
23 | initialZoom?: number;
24 | initialOffsetX?: number;
25 | initialOffsetY?: number;
26 | contentWidth?: number;
27 | contentHeight?: number;
28 | panBoundaryPadding?: number;
29 | maxZoom?: number;
30 | minZoom?: number;
31 | doubleTapDelay?: number;
32 | doubleTapZoomToCenter?: boolean;
33 | bindToBorders?: boolean;
34 | zoomStep?: number;
35 | pinchToZoomInSensitivity?: number;
36 | pinchToZoomOutSensitivity?: number;
37 | movementSensibility?: number;
38 | longPressDuration?: number;
39 | visualTouchFeedbackEnabled?: boolean;
40 | disablePanOnInitialZoom?: boolean;
41 | zoomAnimatedValue?: Animated.Value;
42 | panAnimatedValueXY?: Animated.ValueXY;
43 | debug?: boolean;
44 | onLayout?: (event: Pick) => void;
45 | onTransform?: (zoomableViewEventObject: ZoomableViewEvent) => void;
46 | onSingleTap?: (event: GestureResponderEvent, zoomableViewEventObject: ZoomableViewEvent) => void;
47 | onDoubleTapBefore?: (event: GestureResponderEvent, zoomableViewEventObject: ZoomableViewEvent) => void;
48 | onDoubleTapAfter?: (event: GestureResponderEvent, zoomableViewEventObject: ZoomableViewEvent) => void;
49 | onShiftingBefore?: (event: GestureResponderEvent | null, gestureState: PanResponderGestureState | null, zoomableViewEventObject: ZoomableViewEvent) => boolean;
50 | onShiftingAfter?: (event: GestureResponderEvent | null, gestureState: PanResponderGestureState | null, zoomableViewEventObject: ZoomableViewEvent) => boolean;
51 | onShiftingEnd?: (event: GestureResponderEvent, gestureState: PanResponderGestureState, zoomableViewEventObject: ZoomableViewEvent) => void;
52 | onZoomBefore?: (event: GestureResponderEvent | null, gestureState: PanResponderGestureState | null, zoomableViewEventObject: ZoomableViewEvent) => boolean | undefined;
53 | onZoomAfter?: (event: GestureResponderEvent | null, gestureState: PanResponderGestureState | null, zoomableViewEventObject: ZoomableViewEvent) => void;
54 | onZoomEnd?: (event: GestureResponderEvent, gestureState: PanResponderGestureState, zoomableViewEventObject: ZoomableViewEvent) => void;
55 | onLongPress?: (event: GestureResponderEvent, gestureState: PanResponderGestureState, zoomableViewEventObject: ZoomableViewEvent) => void;
56 | onStartShouldSetPanResponder?: (event: GestureResponderEvent, gestureState: PanResponderGestureState, zoomableViewEventObject: ZoomableViewEvent, baseComponentResult: boolean) => boolean;
57 | onPanResponderGrant?: (event: GestureResponderEvent, gestureState: PanResponderGestureState, zoomableViewEventObject: ZoomableViewEvent) => void;
58 | onPanResponderEnd?: (event: GestureResponderEvent, gestureState: PanResponderGestureState, zoomableViewEventObject: ZoomableViewEvent) => void;
59 | onPanResponderMove?: (event: GestureResponderEvent, gestureState: PanResponderGestureState, zoomableViewEventObject: ZoomableViewEvent) => boolean;
60 | onPanResponderTerminate?: (event: GestureResponderEvent, gestureState: PanResponderGestureState, zoomableViewEventObject: ZoomableViewEvent) => void;
61 | onPanResponderTerminationRequest?: (event: GestureResponderEvent, gestureState: PanResponderGestureState, zoomableViewEventObject: ZoomableViewEvent) => boolean;
62 | onShouldBlockNativeResponder?: (event: GestureResponderEvent, gestureState: PanResponderGestureState, zoomableViewEventObject: ZoomableViewEvent) => boolean;
63 | onStartShouldSetPanResponderCapture?: (event: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
64 | onMoveShouldSetPanResponderCapture?: (event: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
65 | onStaticPinPress?: (event: GestureResponderEvent) => void;
66 | onStaticPinLongPress?: (event: GestureResponderEvent) => void;
67 | staticPinPosition?: Vec2D;
68 | staticPinIcon?: React.ReactElement;
69 | onStaticPinPositionChange?: (position: Vec2D) => void;
70 | onStaticPinPositionMove?: (position: Vec2D) => void;
71 | animatePin: boolean;
72 | pinProps?: ViewProps;
73 | disableMomentum?: boolean;
74 | }
75 | export interface Vec2D {
76 | x: number;
77 | y: number;
78 | }
79 | export interface Size2D {
80 | width: number;
81 | height: number;
82 | }
83 | export interface TouchPoint extends Vec2D {
84 | id: string;
85 | isSecondTap?: boolean;
86 | }
87 | export interface ReactNativeZoomableViewState {
88 | touches?: TouchPoint[];
89 | originalWidth: number;
90 | originalHeight: number;
91 | originalPageX: number;
92 | originalPageY: number;
93 | originalX: number;
94 | originalY: number;
95 | debugPoints?: undefined | Vec2D[];
96 | pinSize: Size2D;
97 | }
98 | export interface ReactNativeZoomableViewWithGesturesProps extends ReactNativeZoomableViewProps {
99 | swipeLengthThreshold?: number;
100 | swipeVelocityThreshold?: number;
101 | swipeDirectionalThreshold?: number;
102 | swipeMinZoom?: number;
103 | swipeMaxZoom?: number;
104 | swipeDisabled?: boolean;
105 | onSwipe?: (swipeDirection: SwipeDirection, gestureState: PanResponderGestureState) => void;
106 | onSwipeUp?: (gestureState: PanResponderGestureState) => void;
107 | onSwipeDown?: (gestureState: PanResponderGestureState) => void;
108 | onSwipeLeft?: (gestureState: PanResponderGestureState) => void;
109 | onSwipeRight?: (gestureState: PanResponderGestureState) => void;
110 | }
111 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@openspacelabs/react-native-zoomable-view",
3 | "version": "2.4.2",
4 | "description": "A view component for react-native with pinch to zoom, tap to move and double tap to zoom capability.",
5 | "main": "lib/commonjs/index",
6 | "module": "lib/module/index",
7 | "types": "lib/typescript/index.d.ts",
8 | "react-native": "src/index",
9 | "source": "src/index",
10 | "files": [
11 | "src",
12 | "lib",
13 | "android",
14 | "ios",
15 | "cpp",
16 | "react-native-zoomable-view.podspec",
17 | "!lib/typescript/example",
18 | "!android/build",
19 | "!ios/build",
20 | "!**/__tests__",
21 | "!**/__fixtures__",
22 | "!**/__mocks__"
23 | ],
24 | "scripts": {
25 | "test": "jest",
26 | "typescript": "tsc --noEmit",
27 | "lint": "eslint \"**/*.{js,ts,tsx}\"",
28 | "release": "release-it",
29 | "example": "yarn --cwd example",
30 | "pods": "cd example && pod-install --quiet",
31 | "bootstrap": "yarn example && yarn && yarn pods"
32 | },
33 | "keywords": [
34 | "react-native",
35 | "react-component",
36 | "ios",
37 | "android",
38 | "pinch-to-zoom",
39 | "pinch",
40 | "mobile",
41 | "native",
42 | "component",
43 | "view",
44 | "zoom",
45 | "zoomable",
46 | "double",
47 | "tap"
48 | ],
49 | "repository": {
50 | "type": "git",
51 | "url": "git+https://github.com/openspacelabs/react-native-zoomable-view.git"
52 | },
53 | "author": "Open Space Labs Inc (https://github.com/openspacelabs)",
54 | "license": "MIT",
55 | "bugs": {
56 | "url": "https://github.com/openspacelabs/react-native-zoomable-view/issues"
57 | },
58 | "homepage": "https://github.com/openspacelabs/react-native-zoomable-view#readme",
59 | "publishConfig": {
60 | "registry": "https://registry.npmjs.org/"
61 | },
62 | "dependencies": {
63 | "prop-types": "^15.7.2"
64 | },
65 | "devDependencies": {
66 | "@commitlint/config-conventional": "^11.0.0",
67 | "@react-native/eslint-config": "^0.73.0",
68 | "@release-it/conventional-changelog": "^2.0.0",
69 | "@types/jest": "^26.0.0",
70 | "@types/lodash": "^4.17.7",
71 | "@types/react": "^16.9.19",
72 | "@types/react-native": "^0.65.4",
73 | "@typescript-eslint/eslint-plugin": "^7.18.0",
74 | "@typescript-eslint/parser": "^7.18.0",
75 | "commitlint": "^11.0.0",
76 | "eslint": "^8.57.1",
77 | "eslint-config-prettier": "^7.0.0",
78 | "eslint-plugin-prettier": "^3.1.3",
79 | "husky": "^4.2.5",
80 | "jest": "^26.0.1",
81 | "pod-install": "^0.1.0",
82 | "prettier": "^2.0.5",
83 | "react": "17.0.2",
84 | "react-native": "0.68.2",
85 | "react-native-builder-bob": "^0.18.1",
86 | "release-it": "^14.2.2",
87 | "typescript": "^4.9.5"
88 | },
89 | "peerDependencies": {
90 | "react": ">=16.8.0",
91 | "react-native": ">=0.54.0"
92 | },
93 | "jest": {
94 | "preset": "react-native",
95 | "modulePathIgnorePatterns": [
96 | "/example/node_modules",
97 | "/lib/"
98 | ]
99 | },
100 | "husky": {
101 | "hooks": {
102 | "pre-commit": "yarn lint && yarn typescript && bob build && git add lib"
103 | }
104 | },
105 | "commitlint": {
106 | "extends": [
107 | "@commitlint/config-conventional"
108 | ]
109 | },
110 | "release-it": {
111 | "git": {
112 | "commitMessage": "chore: release ${version}",
113 | "tagName": "v${version}"
114 | },
115 | "npm": {
116 | "publish": true
117 | },
118 | "github": {
119 | "release": true
120 | },
121 | "plugins": {
122 | "@release-it/conventional-changelog": {
123 | "preset": "angular"
124 | }
125 | }
126 | },
127 | "eslintConfig": {
128 | "root": true,
129 | "env": {
130 | "es6": true
131 | },
132 | "parserOptions": {
133 | "ecmaVersion": 2018
134 | },
135 | "extends": [
136 | "plugin:react-native/all",
137 | "prettier"
138 | ],
139 | "plugins": [
140 | "prettier",
141 | "react",
142 | "react-native"
143 | ],
144 | "rules": {
145 | "prettier/prettier": [
146 | "error",
147 | {
148 | "quoteProps": "consistent",
149 | "singleQuote": true,
150 | "tabWidth": 2,
151 | "trailingComma": "es5",
152 | "useTabs": false
153 | }
154 | ],
155 | "react-native/no-color-literals": "off"
156 | },
157 | "overrides": [
158 | {
159 | "files": [
160 | "*.ts",
161 | "*.tsx"
162 | ],
163 | "parser": "@typescript-eslint/parser",
164 | "plugins": [
165 | "@typescript-eslint"
166 | ],
167 | "extends": [
168 | "plugin:@typescript-eslint/strict-type-checked"
169 | ],
170 | "parserOptions": {
171 | "project": [
172 | "./tsconfig.json"
173 | ]
174 | },
175 | "rules": {
176 | "@typescript-eslint/restrict-template-expressions": [
177 | "error",
178 | {
179 | "allowNumber": true
180 | }
181 | ]
182 | }
183 | }
184 | ]
185 | },
186 | "eslintIgnore": [
187 | "node_modules/",
188 | "lib/"
189 | ],
190 | "prettier": {
191 | "quoteProps": "consistent",
192 | "singleQuote": true,
193 | "tabWidth": 2,
194 | "trailingComma": "es5",
195 | "useTabs": false
196 | },
197 | "react-native-builder-bob": {
198 | "source": "src",
199 | "output": "lib",
200 | "targets": [
201 | "commonjs",
202 | "module",
203 | [
204 | "typescript",
205 | {
206 | "project": "tsconfig.build.json"
207 | }
208 | ]
209 | ]
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/scripts/bootstrap.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const child_process = require('child_process');
3 |
4 | const root = path.resolve(__dirname, '..');
5 | const args = process.argv.slice(2);
6 | const options = {
7 | cwd: process.cwd(),
8 | env: process.env,
9 | stdio: 'inherit',
10 | encoding: 'utf-8',
11 | };
12 |
13 | let result;
14 |
15 | if (process.cwd() !== root || args.length) {
16 | // We're not in the root of the project, or additional arguments were passed
17 | // In this case, forward the command to `yarn`
18 | result = child_process.spawnSync('yarn', args, options);
19 | } else {
20 | // If `yarn` is run without arguments, perform bootstrap
21 | result = child_process.spawnSync('yarn', ['bootstrap'], options);
22 | }
23 |
24 | process.exitCode = result.status;
25 |
--------------------------------------------------------------------------------
/src/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | it.todo('write a test');
2 |
--------------------------------------------------------------------------------
/src/animations/index.ts:
--------------------------------------------------------------------------------
1 | import { Animated, Easing } from 'react-native';
2 | import { Vec2D } from '../typings';
3 |
4 | export function getBoundaryCrossedAnim(
5 | animValue: Animated.Value,
6 | toValue: number
7 | ) {
8 | return Animated.spring(animValue, {
9 | overshootClamping: true,
10 | toValue,
11 | useNativeDriver: true,
12 | });
13 | }
14 |
15 | export function getPanMomentumDecayAnim(
16 | animValue: Animated.Value | Animated.ValueXY,
17 | velocity: number | Vec2D
18 | ) {
19 | return Animated.decay(animValue, {
20 | velocity,
21 | deceleration: 0.994,
22 | useNativeDriver: true,
23 | });
24 | }
25 |
26 | export function getZoomToAnimation(animValue: Animated.Value, toValue: number) {
27 | return Animated.timing(animValue, {
28 | easing: Easing.out(Easing.ease),
29 | toValue,
30 | useNativeDriver: true,
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/src/assets/pin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openspacelabs/react-native-zoomable-view/9a4219bfd5250bea2995309ac847b57de0e3b3ae/src/assets/pin.png
--------------------------------------------------------------------------------
/src/components/AnimatedTouchFeedback.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | import { Animated, Easing, StyleSheet } from 'react-native';
3 |
4 | export const AnimatedTouchFeedback = ({
5 | x,
6 | y,
7 | animationDelay,
8 | animationDuration,
9 | onAnimationDone,
10 | }: {
11 | x: number;
12 | y: number;
13 | animationDuration: number;
14 | animationDelay?: number;
15 | onAnimationDone?: () => void;
16 | }) => {
17 | const appearDisappearAnimRef = useRef(new Animated.Value(0));
18 | const onAnimationDoneRef = useRef(onAnimationDone);
19 | onAnimationDoneRef.current = onAnimationDone;
20 |
21 | useEffect(() => {
22 | appearDisappearAnimRef.current.setValue(0);
23 | const inDuration = animationDuration * 0.8;
24 | const outDuration = animationDuration - inDuration;
25 | Animated.sequence([
26 | Animated.timing(appearDisappearAnimRef.current, {
27 | delay: animationDelay || 0,
28 | toValue: 1,
29 | duration: inDuration,
30 | easing: Easing.linear,
31 | useNativeDriver: true,
32 | }),
33 | Animated.timing(appearDisappearAnimRef.current, {
34 | toValue: 0,
35 | duration: outDuration,
36 | easing: Easing.out(Easing.ease),
37 | useNativeDriver: true,
38 | }),
39 | ]).start(() => onAnimationDoneRef.current?.());
40 | }, [animationDelay, animationDuration]);
41 |
42 | return (
43 |
65 | );
66 | };
67 |
68 | const styles = StyleSheet.create({
69 | animatedTouchFeedback: {
70 | backgroundColor: 'lightgray',
71 | borderRadius: 40,
72 | height: 40,
73 | position: 'absolute',
74 | width: 40,
75 | },
76 | });
77 |
--------------------------------------------------------------------------------
/src/components/StaticPin.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Animated,
4 | View,
5 | Image,
6 | StyleSheet,
7 | GestureResponderEvent,
8 | PanResponderGestureState,
9 | PanResponder,
10 | ViewProps,
11 | } from 'react-native';
12 | import { Size2D } from 'src/typings';
13 |
14 | export const StaticPin = ({
15 | staticPinPosition,
16 | pinAnim,
17 | staticPinIcon,
18 | pinSize,
19 | onParentMove,
20 | onPress,
21 | onLongPress,
22 | setPinSize,
23 | pinProps = {},
24 | }: {
25 | staticPinPosition: { x: number; y: number };
26 | pinAnim: Animated.ValueXY;
27 | staticPinIcon: React.ReactNode;
28 | pinSize: Size2D;
29 | /** Internal handler for passing move event to parent */
30 | onParentMove: (
31 | evt: GestureResponderEvent,
32 | gestureState: PanResponderGestureState
33 | ) => boolean | undefined;
34 | onPress?: (evt: GestureResponderEvent) => void;
35 | onLongPress?: (evt: GestureResponderEvent) => void;
36 | setPinSize: (size: Size2D) => void;
37 | pinProps?: ViewProps;
38 | }) => {
39 | const tapTime = React.useRef(0);
40 | const transform = [
41 | { translateY: -pinSize.height },
42 | { translateX: -pinSize.width / 2 },
43 | ...pinAnim.getTranslateTransform(),
44 | ];
45 |
46 | const opacity = pinSize.width && pinSize.height ? 1 : 0;
47 |
48 | const panResponder = React.useRef(
49 | PanResponder.create({
50 | onStartShouldSetPanResponder: () => {
51 | tapTime.current = Date.now();
52 |
53 | // We want to handle tap on this so set true
54 | return true;
55 | },
56 | onPanResponderMove: (evt, gestureState) => {
57 | // However if the user moves finger we want to pass this evt to parent
58 | // to handle panning (tap not recognized)
59 | if (Math.abs(gestureState.dx) > 5 && Math.abs(gestureState.dy) > 5)
60 | onParentMove(evt, gestureState);
61 | },
62 | onPanResponderRelease: (evt, gestureState) => {
63 | if (Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5)
64 | return;
65 | const dt = Date.now() - tapTime.current;
66 | if (onPress && dt < 500) {
67 | onPress(evt);
68 | }
69 | if (onLongPress && dt > 500) {
70 | // RN long press is 500ms
71 | onLongPress(evt);
72 | }
73 | },
74 | })
75 | ).current;
76 |
77 | return (
78 |
89 | {
91 | setPinSize(layout);
92 | }}
93 | {...panResponder.panHandlers}
94 | >
95 | {staticPinIcon || (
96 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
97 |
98 | )}
99 |
100 |
101 | );
102 | };
103 |
104 | const styles = StyleSheet.create({
105 | pin: {
106 | height: 64,
107 | width: 48,
108 | },
109 | pinWrapper: {
110 | position: 'absolute',
111 | },
112 | });
113 |
--------------------------------------------------------------------------------
/src/components/index.tsx:
--------------------------------------------------------------------------------
1 | export { AnimatedTouchFeedback } from './AnimatedTouchFeedback';
2 |
--------------------------------------------------------------------------------
/src/debugHelper/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, StyleSheet } from 'react-native';
2 | import React from 'react';
3 |
4 | export const DebugTouchPoint = ({
5 | diameter = 20,
6 | x = 0,
7 | y = 0,
8 | color = 'yellow',
9 | }) => {
10 | const radius = diameter / 2;
11 | return (
12 |
26 | );
27 | };
28 | export const DebugRect = ({
29 | height,
30 | x = 0,
31 | y = 0,
32 | color = 'yellow',
33 | }: {
34 | height: number;
35 | x: number;
36 | y: number;
37 | color: string;
38 | }) => {
39 | const width = 5;
40 | return (
41 |
54 | );
55 | };
56 |
57 | const styles = StyleSheet.create({
58 | debugPoint: {
59 | opacity: 0.7,
60 | position: 'absolute',
61 | },
62 | debugRect: {
63 | opacity: 0.5,
64 | position: 'absolute',
65 | },
66 | });
67 |
--------------------------------------------------------------------------------
/src/helper/applyPanBoundariesToOffset.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Takes a single offset value and calculates the correct offset value
3 | * to make sure it's within the pan boundaries
4 | *
5 | *
6 | * @param offsetScaled
7 | * @param containerSize
8 | * @param contentSize
9 | * @param scale
10 | * @param boundaryPadding - see README
11 | *
12 | * @returns {number}
13 | */
14 | export function applyPanBoundariesToOffset(
15 | offsetScaled: number,
16 | containerSize: number,
17 | contentSize: number,
18 | scale: number,
19 | boundaryPadding: number
20 | ) {
21 | const contentSizeUnscaled = contentSize * scale;
22 | const offsetUnscaled = offsetScaled * scale;
23 |
24 | const contentStartBorderUnscaled =
25 | containerSize / 2 + offsetUnscaled - contentSizeUnscaled / 2;
26 | const contentEndBorderUnscaled =
27 | contentStartBorderUnscaled + contentSizeUnscaled;
28 |
29 | const containerStartBorder = 0;
30 | const containerEndBorder = containerStartBorder + containerSize;
31 |
32 | // do not let boundary padding be greater than the container size or less than 0
33 | if (!boundaryPadding || boundaryPadding < 0) boundaryPadding = 0;
34 | if (boundaryPadding > containerSize) boundaryPadding = containerSize;
35 |
36 | // Calculate container's measurements with boundary padding applied.
37 | // this should shrink the container's size by the amount of the boundary padding,
38 | // so that the content inside can be panned a bit further away from the original container's boundaries.
39 | const paddedContainerSize = containerSize - boundaryPadding * 2;
40 | const paddedContainerStartBorder = containerStartBorder + boundaryPadding;
41 | const paddedContainerEndBorder = containerEndBorder - boundaryPadding;
42 |
43 | // if content is smaller than the padded container,
44 | // don't let the content move
45 | if (contentSizeUnscaled < paddedContainerSize) {
46 | return 0;
47 | }
48 |
49 | // if content is larger than the padded container,
50 | // don't let the padded container go outside of content
51 |
52 | // maximum distance the content's center can move from its original position.
53 | // assuming the content original center is the container's center.
54 | const contentMaxOffsetScaled =
55 | (paddedContainerSize / 2 - contentSizeUnscaled / 2) / scale;
56 |
57 | if (
58 | // content reaching the end boundary
59 | contentEndBorderUnscaled < paddedContainerEndBorder
60 | ) {
61 | return contentMaxOffsetScaled;
62 | }
63 | if (
64 | // content reaching the start boundary
65 | contentStartBorderUnscaled > paddedContainerStartBorder
66 | ) {
67 | return -contentMaxOffsetScaled;
68 | }
69 |
70 | return offsetScaled;
71 | }
72 |
--------------------------------------------------------------------------------
/src/helper/calcNewScaledOffsetForZoomCentering.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Calculates the new offset for the zoomSubject to ensure zoom center position is retained after zooming.
3 | * Parameters should correspond to whether we need the offset for the X or Y axis
4 | *
5 | * ## Terms Used:
6 | *
7 | * - Zoom Subject: the view that's being zoomed and panned
8 | * - Zoom Center: the point whose relative position to the window is retained
9 | * - Unscaled: a measurement in pixels denoting the true size as observed by the users' eyes
10 | * - Scaled: a measurement in pixels scaled to the "scale transformation" of the zoom subject to match with its true size.
11 | * *For example:*
12 | * If the scale on the zoom subject is 0.5,
13 | * then to draw an actual 4px line on the zoom subject, we need to scale it to 4px / 0.5 = 8px
14 | * 8px is the scaled measurement
15 | *
16 | * ## Overall idea of this algorithm:
17 | *
18 | * When users perform zooming by pinching the screen,
19 | * we need to shift the zoom subject so that the position of the zoom center is always the same.
20 | * The offset amount to shift the layer is the returned value.
21 | *
22 | *
23 | * ## How we achieve our goal:
24 | *
25 | * To retain, the zoom center position, whenever a zoom action is performed,
26 | * we just need to make sure the distances from all the points in the zoom subject
27 | * to the zoom center increases or decreases by the growth rate of the scale.
28 | *
29 | * ```
30 | * newDistanceAnyPointToZoomCenter = oldDistanceAnyPointToZoomCenter * (newScale/oldScale)
31 | * ```
32 | *
33 | * We can't calculate that for all the points because there are unlimited points on a plain.
34 | * However, due to the way `transform` works in RN, every point is scaled from the zoom subject center.
35 | * Therefore, it's sufficient to base our calculation on the distance from the zoom subject center to the zoom center.
36 | *
37 | * ```
38 | * newDistanceZoomSubjectCenterToZoomCenter = oldDistanceZoomSubjectCenterToZoomCenter * (newScale/oldScale)
39 | * ```
40 | *
41 | * Once we have `newDistanceZoomSubjectCenterToZoomCenter`,
42 | * we can easily calculate the position of the new center, which leads us to the offset amount.
43 | * Refer to the code for more details
44 | *
45 | * @param oldOffsetXOrYScaled
46 | * @param zoomSubjectOriginalWidthOrHeight
47 | * @param oldScale
48 | * @param newScale
49 | * @param zoomCenterXOrY
50 | */
51 | export function calcNewScaledOffsetForZoomCentering(
52 | oldOffsetXOrYScaled: number,
53 | zoomSubjectOriginalWidthOrHeight: number,
54 | oldScale: number,
55 | newScale: number,
56 | zoomCenterXOrY: number
57 | ) {
58 | const oldOffSetUnscaled = oldOffsetXOrYScaled * oldScale;
59 | const growthRate = newScale / oldScale;
60 |
61 | // these act like namespaces just for the sake of readability
62 | const zoomSubjectOriginalCenter = {} as Center;
63 | const zoomSubjectCurrentCenter = {} as Center;
64 | const zoomSubjectNewCenter = {} as Center;
65 |
66 | zoomSubjectOriginalCenter.xOrY = zoomSubjectOriginalWidthOrHeight / 2;
67 | zoomSubjectCurrentCenter.xOrY =
68 | zoomSubjectOriginalCenter.xOrY + oldOffSetUnscaled;
69 | zoomSubjectCurrentCenter.distanceToZoomCenter =
70 | zoomSubjectCurrentCenter.xOrY - zoomCenterXOrY;
71 |
72 | zoomSubjectNewCenter.distanceToZoomCenter =
73 | zoomSubjectCurrentCenter.distanceToZoomCenter * growthRate;
74 | zoomSubjectNewCenter.xOrY =
75 | zoomSubjectNewCenter.distanceToZoomCenter + zoomCenterXOrY;
76 |
77 | const newOffsetUnscaled =
78 | zoomSubjectNewCenter.xOrY - zoomSubjectOriginalCenter.xOrY;
79 |
80 | return newOffsetUnscaled / newScale;
81 | }
82 |
83 | interface Center {
84 | xOrY: number;
85 | distanceToZoomCenter: number;
86 | }
87 |
--------------------------------------------------------------------------------
/src/helper/coordinateConversion.ts:
--------------------------------------------------------------------------------
1 | import { Size2D, Vec2D, ZoomableViewEvent } from 'src/typings';
2 |
3 | export const defaultTransformSubjectData: ZoomableViewEvent = {
4 | offsetX: 0,
5 | offsetY: 0,
6 | zoomLevel: 0,
7 | originalWidth: 0,
8 | originalHeight: 0,
9 | originalPageX: 0,
10 | originalPageY: 0,
11 | };
12 |
13 | /**
14 | * Assuming you have an image that's being resized to fit into a container
15 | * using the "contain" resize mode. You can use this function to calculate the
16 | * size of the image after fitting.
17 | *
18 | * Since our sheet is resized in this manner, we need this function
19 | * for things like pan boundaries and marker placement
20 | *
21 | * @param imgSize
22 | * @param containerSize
23 | */
24 | export function applyContainResizeMode(
25 | imgSize: Size2D,
26 | containerSize: Size2D
27 | ): { size: Size2D; scale: number } | { size: null; scale: null } {
28 | const { width: imageWidth, height: imageHeight } = imgSize;
29 | const { width: areaWidth, height: areaHeight } = containerSize;
30 | const imageAspect = imageWidth / imageHeight;
31 | const areaAspect = areaWidth / areaHeight;
32 |
33 | let newSize;
34 | if (imageAspect >= areaAspect) {
35 | // longest edge is horizontal
36 | newSize = { width: areaWidth, height: areaWidth / imageAspect };
37 | } else {
38 | // longest edge is vertical
39 | newSize = { width: areaHeight * imageAspect, height: areaHeight };
40 | }
41 |
42 | if (isNaN(newSize.height)) newSize.height = areaHeight;
43 | if (isNaN(newSize.width)) newSize.width = areaWidth;
44 |
45 | const scale = imageWidth
46 | ? newSize.width / imageWidth
47 | : newSize.height / imageHeight;
48 |
49 | if (!isFinite(scale)) return { size: null, scale: null };
50 |
51 | return {
52 | size: newSize,
53 | scale,
54 | };
55 | }
56 |
57 | /**
58 | * get the coord of image's origin relative to the transformSubject
59 | * @param resizedImageSize
60 | * @param transformSubject
61 | */
62 | export function getImageOriginOnTransformSubject(
63 | resizedImageSize: Size2D,
64 | transformSubject: ZoomableViewEvent
65 | ) {
66 | const { offsetX, offsetY, zoomLevel, originalWidth, originalHeight } =
67 | transformSubject;
68 | return {
69 | x:
70 | offsetX * zoomLevel +
71 | originalWidth / 2 -
72 | (resizedImageSize.width / 2) * zoomLevel,
73 | y:
74 | offsetY * zoomLevel +
75 | originalHeight / 2 -
76 | (resizedImageSize.height / 2) * zoomLevel,
77 | };
78 | }
79 |
80 | /**
81 | * Translates the coord system of a point from the viewport's space to the image's space
82 | *
83 | * @param pointOnContainer
84 | * @param sheetImageSize
85 | * @param transformSubject
86 | *
87 | * @return {Vec2D} returns null if point is out of the sheet's bound
88 | */
89 | export function viewportPositionToImagePosition({
90 | viewportPosition,
91 | imageSize,
92 | zoomableEvent,
93 | }: {
94 | viewportPosition: Vec2D;
95 | imageSize: Size2D;
96 | zoomableEvent: ZoomableViewEvent;
97 | }): Vec2D | null {
98 | const { size: resizedImgSize, scale: resizedImgScale } =
99 | applyContainResizeMode(imageSize, {
100 | width: zoomableEvent.originalWidth,
101 | height: zoomableEvent.originalHeight,
102 | });
103 |
104 | if (resizedImgScale == null) return null;
105 |
106 | const sheetOriginOnContainer = getImageOriginOnTransformSubject(
107 | resizedImgSize,
108 | zoomableEvent
109 | );
110 |
111 | const pointOnSheet = {
112 | x:
113 | (viewportPosition.x - sheetOriginOnContainer.x) /
114 | zoomableEvent.zoomLevel /
115 | resizedImgScale,
116 | y:
117 | (viewportPosition.y - sheetOriginOnContainer.y) /
118 | zoomableEvent.zoomLevel /
119 | resizedImgScale,
120 | };
121 |
122 | return pointOnSheet;
123 | }
124 |
--------------------------------------------------------------------------------
/src/helper/index.ts:
--------------------------------------------------------------------------------
1 | import { GestureResponderEvent, PanResponderGestureState } from 'react-native';
2 | import { Vec2D } from '../typings';
3 |
4 | export { calcNewScaledOffsetForZoomCentering } from './calcNewScaledOffsetForZoomCentering';
5 |
6 | /**
7 | * Calculates the gesture center point relative to the page coordinate system
8 | *
9 | * We're unable to use touch.locationX/Y
10 | * because locationX uses the axis system of the leaf element that the touch occurs on,
11 | * which makes it even more complicated to translate into our container's axis system.
12 | *
13 | * We're also unable to use gestureState.moveX/Y
14 | * because gestureState.moveX/Y is messed up on real device
15 | * (Sometimes it's the center point, but sometimes it randomly takes the position of one of the touches)
16 | */
17 | export function calcGestureCenterPoint(
18 | e: GestureResponderEvent,
19 | gestureState: PanResponderGestureState
20 | ): Vec2D | null {
21 | const touches = e.nativeEvent.touches;
22 | if (!touches[0]) return null;
23 |
24 | if (gestureState.numberActiveTouches === 2) {
25 | if (!touches[1]) return null;
26 | return {
27 | x: (touches[0].pageX + touches[1].pageX) / 2,
28 | y: (touches[0].pageY + touches[1].pageY) / 2,
29 | };
30 | }
31 | if (gestureState.numberActiveTouches === 1) {
32 | return {
33 | x: touches[0].pageX,
34 | y: touches[0].pageY,
35 | };
36 | }
37 |
38 | return null;
39 | }
40 |
41 | export function calcGestureTouchDistance(
42 | e: GestureResponderEvent,
43 | gestureState: PanResponderGestureState
44 | ): number | null {
45 | const touches = e.nativeEvent.touches;
46 | if (gestureState.numberActiveTouches !== 2 || !touches[0] || !touches[1])
47 | return null;
48 |
49 | const dx = Math.abs(touches[0].pageX - touches[1].pageX);
50 | const dy = Math.abs(touches[0].pageY - touches[1].pageY);
51 | return Math.sqrt(dx * dx + dy * dy);
52 | }
53 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | applyContainResizeMode,
3 | getImageOriginOnTransformSubject,
4 | viewportPositionToImagePosition,
5 | } from './helper/coordinateConversion';
6 | import ReactNativeZoomableView from './ReactNativeZoomableView';
7 | import type {
8 | ReactNativeZoomableViewProps,
9 | ZoomableViewEvent,
10 | } from './typings';
11 |
12 | export {
13 | ReactNativeZoomableView,
14 | ReactNativeZoomableViewProps,
15 | ZoomableViewEvent,
16 | // Helper functions for coordinate conversion
17 | applyContainResizeMode,
18 | getImageOriginOnTransformSubject,
19 | viewportPositionToImagePosition,
20 | };
21 |
--------------------------------------------------------------------------------
/src/typings/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Animated,
3 | GestureResponderEvent,
4 | LayoutChangeEvent,
5 | PanResponderGestureState,
6 | ViewProps,
7 | } from 'react-native';
8 | import { ReactNode } from 'react';
9 |
10 | export enum SwipeDirection {
11 | SWIPE_UP = 'SWIPE_UP',
12 | SWIPE_DOWN = 'SWIPE_DOWN',
13 | SWIPE_LEFT = 'SWIPE_LEFT',
14 | SWIPE_RIGHT = 'SWIPE_RIGHT',
15 | }
16 |
17 | export interface ZoomableViewEvent {
18 | zoomLevel: number;
19 | offsetX: number;
20 | offsetY: number;
21 | originalHeight: number;
22 | originalWidth: number;
23 | originalPageX: number;
24 | originalPageY: number;
25 | }
26 |
27 | export interface ReactNativeZoomableViewProps {
28 | // options
29 | style?: ViewProps['style'];
30 | children?: ReactNode;
31 | zoomEnabled?: boolean;
32 | panEnabled?: boolean;
33 | initialZoom?: number;
34 | initialOffsetX?: number;
35 | initialOffsetY?: number;
36 | contentWidth?: number;
37 | contentHeight?: number;
38 | panBoundaryPadding?: number;
39 | maxZoom?: number;
40 | minZoom?: number;
41 | doubleTapDelay?: number;
42 | doubleTapZoomToCenter?: boolean;
43 | bindToBorders?: boolean;
44 | zoomStep?: number;
45 | pinchToZoomInSensitivity?: number;
46 | pinchToZoomOutSensitivity?: number;
47 | movementSensibility?: number;
48 | longPressDuration?: number;
49 | visualTouchFeedbackEnabled?: boolean;
50 | disablePanOnInitialZoom?: boolean;
51 |
52 | // Zoom animated value ref
53 | zoomAnimatedValue?: Animated.Value;
54 | panAnimatedValueXY?: Animated.ValueXY;
55 |
56 | // debug
57 | debug?: boolean;
58 |
59 | // callbacks
60 | onLayout?: (event: Pick) => void;
61 | onTransform?: (zoomableViewEventObject: ZoomableViewEvent) => void;
62 | onSingleTap?: (
63 | event: GestureResponderEvent,
64 | zoomableViewEventObject: ZoomableViewEvent
65 | ) => void;
66 | onDoubleTapBefore?: (
67 | event: GestureResponderEvent,
68 | zoomableViewEventObject: ZoomableViewEvent
69 | ) => void;
70 | onDoubleTapAfter?: (
71 | event: GestureResponderEvent,
72 | zoomableViewEventObject: ZoomableViewEvent
73 | ) => void;
74 | onShiftingBefore?: (
75 | event: GestureResponderEvent | null,
76 | gestureState: PanResponderGestureState | null,
77 | zoomableViewEventObject: ZoomableViewEvent
78 | ) => boolean;
79 | onShiftingAfter?: (
80 | event: GestureResponderEvent | null,
81 | gestureState: PanResponderGestureState | null,
82 | zoomableViewEventObject: ZoomableViewEvent
83 | ) => boolean;
84 | onShiftingEnd?: (
85 | event: GestureResponderEvent,
86 | gestureState: PanResponderGestureState,
87 | zoomableViewEventObject: ZoomableViewEvent
88 | ) => void;
89 | onZoomBefore?: (
90 | event: GestureResponderEvent | null,
91 | gestureState: PanResponderGestureState | null,
92 | zoomableViewEventObject: ZoomableViewEvent
93 | ) => boolean | undefined;
94 | onZoomAfter?: (
95 | event: GestureResponderEvent | null,
96 | gestureState: PanResponderGestureState | null,
97 | zoomableViewEventObject: ZoomableViewEvent
98 | ) => void;
99 | onZoomEnd?: (
100 | event: GestureResponderEvent,
101 | gestureState: PanResponderGestureState,
102 | zoomableViewEventObject: ZoomableViewEvent
103 | ) => void;
104 | onLongPress?: (
105 | event: GestureResponderEvent,
106 | gestureState: PanResponderGestureState,
107 | zoomableViewEventObject: ZoomableViewEvent
108 | ) => void;
109 | onStartShouldSetPanResponder?: (
110 | event: GestureResponderEvent,
111 | gestureState: PanResponderGestureState,
112 | zoomableViewEventObject: ZoomableViewEvent,
113 | baseComponentResult: boolean
114 | ) => boolean;
115 | onPanResponderGrant?: (
116 | event: GestureResponderEvent,
117 | gestureState: PanResponderGestureState,
118 | zoomableViewEventObject: ZoomableViewEvent
119 | ) => void;
120 | onPanResponderEnd?: (
121 | event: GestureResponderEvent,
122 | gestureState: PanResponderGestureState,
123 | zoomableViewEventObject: ZoomableViewEvent
124 | ) => void;
125 | onPanResponderMove?: (
126 | event: GestureResponderEvent,
127 | gestureState: PanResponderGestureState,
128 | zoomableViewEventObject: ZoomableViewEvent
129 | ) => boolean;
130 | onPanResponderTerminate?: (
131 | event: GestureResponderEvent,
132 | gestureState: PanResponderGestureState,
133 | zoomableViewEventObject: ZoomableViewEvent
134 | ) => void;
135 | onPanResponderTerminationRequest?: (
136 | event: GestureResponderEvent,
137 | gestureState: PanResponderGestureState,
138 | zoomableViewEventObject: ZoomableViewEvent
139 | ) => boolean;
140 | onShouldBlockNativeResponder?: (
141 | event: GestureResponderEvent,
142 | gestureState: PanResponderGestureState,
143 | zoomableViewEventObject: ZoomableViewEvent
144 | ) => boolean;
145 | onStartShouldSetPanResponderCapture?: (
146 | event: GestureResponderEvent,
147 | gestureState: PanResponderGestureState
148 | ) => boolean;
149 | onMoveShouldSetPanResponderCapture?: (
150 | event: GestureResponderEvent,
151 | gestureState: PanResponderGestureState
152 | ) => boolean;
153 | onStaticPinPress?: (event: GestureResponderEvent) => void;
154 | onStaticPinLongPress?: (event: GestureResponderEvent) => void;
155 | staticPinPosition?: Vec2D;
156 | staticPinIcon?: React.ReactElement;
157 | onStaticPinPositionChange?: (position: Vec2D) => void;
158 | onStaticPinPositionMove?: (position: Vec2D) => void;
159 | animatePin: boolean;
160 | pinProps?: ViewProps;
161 | disableMomentum?: boolean;
162 | }
163 |
164 | export interface Vec2D {
165 | x: number;
166 | y: number;
167 | }
168 |
169 | export interface Size2D {
170 | width: number;
171 | height: number;
172 | }
173 |
174 | export interface TouchPoint extends Vec2D {
175 | id: string;
176 | isSecondTap?: boolean;
177 | }
178 |
179 | export interface ReactNativeZoomableViewState {
180 | touches?: TouchPoint[];
181 | originalWidth: number;
182 | originalHeight: number;
183 | originalPageX: number;
184 | originalPageY: number;
185 | originalX: number;
186 | originalY: number;
187 | debugPoints?: undefined | Vec2D[];
188 | pinSize: Size2D;
189 | }
190 |
191 | export interface ReactNativeZoomableViewWithGesturesProps
192 | extends ReactNativeZoomableViewProps {
193 | swipeLengthThreshold?: number;
194 | swipeVelocityThreshold?: number;
195 | swipeDirectionalThreshold?: number;
196 | swipeMinZoom?: number;
197 | swipeMaxZoom?: number;
198 | swipeDisabled?: boolean;
199 | onSwipe?: (
200 | swipeDirection: SwipeDirection,
201 | gestureState: PanResponderGestureState
202 | ) => void;
203 | onSwipeUp?: (gestureState: PanResponderGestureState) => void;
204 | onSwipeDown?: (gestureState: PanResponderGestureState) => void;
205 | onSwipeLeft?: (gestureState: PanResponderGestureState) => void;
206 | onSwipeRight?: (gestureState: PanResponderGestureState) => void;
207 | }
208 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "extends": "./tsconfig",
4 | "exclude": ["example"]
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "@openspacelabs/react-native-zoomable-view": ["./src/index"]
6 | },
7 | "declaration": true,
8 | "module": "esnext",
9 | "target": "es6",
10 | "lib": ["es6", "dom", "es2016", "es2017"],
11 | "sourceMap": true,
12 | "jsx": "react",
13 | "moduleResolution": "node",
14 | "allowSyntheticDefaultImports": true,
15 | "esModuleInterop": true,
16 | "strict": true,
17 | "skipLibCheck": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------