@ealimardani/react-native-modal 14.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 React Native Community
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,328 @@
1
+ ### Announcements
2
+
3
+ - 📣 We're looking for maintainers and contributors! See [#598](https://github.com/react-native-modal/react-native-modal/discussions/598)
4
+ - 🙏 If you have a question, please [start a new discussion](https://github.com/react-native-modal/react-native-modal/discussions) instead of opening a new issue.
5
+
6
+ # react-native-modal
7
+
8
+ [![npm version](https://badge.fury.io/js/react-native-modal.svg)](https://badge.fury.io/js/react-native-modal)
9
+ [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
10
+
11
+ > If you're new to the React Native world, please notice that React Native itself offers a [<Modal /> component that works out-of-the-box](https://reactnative.dev/docs/modal).
12
+
13
+ An enhanced, animated, customizable React Native modal.
14
+
15
+ The goal of `react-native-modal` is expanding the original React Native `<Modal>` component by adding animations, style customization options, and new features, while still providing a simple API.
16
+
17
+ <p align="center">
18
+ <img src="/.github/images/example-modal.gif" height="500" />
19
+ </p>
20
+
21
+ ## Features
22
+
23
+ - Smooth enter/exit animations
24
+ - Plain simple and flexible APIs
25
+ - Customizable backdrop opacity, color and timing
26
+ - Listeners for the modal animations ending
27
+ - Resize itself correctly on device rotation
28
+ - Swipeable
29
+ - Scrollable
30
+
31
+ ## Setup
32
+
33
+ This library is available on npm, install it with: `npm i react-native-modal` or `yarn add react-native-modal`.
34
+
35
+ ## Usage
36
+
37
+ Since `react-native-modal` is an extension of the [original React Native modal](https://reactnative.dev/docs/modal.html), it works in a similar fashion.
38
+
39
+ 1. Import `react-native-modal`:
40
+
41
+ ```javascript
42
+ import Modal from 'react-native-modal';
43
+ ```
44
+
45
+ 2. Create a `<Modal>` component and nest its content inside of it:
46
+
47
+ ```javascript
48
+ function WrapperComponent() {
49
+ return (
50
+ <View>
51
+ <Modal>
52
+ <View style={{flex: 1}}>
53
+ <Text>I am the modal content!</Text>
54
+ </View>
55
+ </Modal>
56
+ </View>
57
+ );
58
+ }
59
+ ```
60
+
61
+ 3. Then, show the modal by setting the `isVisible` prop to `true`:
62
+
63
+ ```javascript
64
+ function WrapperComponent() {
65
+ return (
66
+ <View>
67
+ <Modal isVisible={true}>
68
+ <View style={{flex: 1}}>
69
+ <Text>I am the modal content!</Text>
70
+ </View>
71
+ </Modal>
72
+ </View>
73
+ );
74
+ }
75
+ ```
76
+
77
+ The `isVisible` prop is the only prop you'll really need to make the modal work: you should control this prop value by saving it in your wrapper component state and setting it to `true` or `false` when needed.
78
+
79
+ ## A complete example
80
+
81
+ The following example consists in a component (`ModalTester`) with a button and a modal.
82
+ The modal is controlled by the `isModalVisible` state variable and it is initially hidden, since its value is `false`.
83
+ Pressing the button sets `isModalVisible` to true, making the modal visible.
84
+ Inside the modal there is another button that, when pressed, sets `isModalVisible` to false, hiding the modal.
85
+
86
+ ```javascript
87
+ import React, {useState} from 'react';
88
+ import {Button, Text, View} from 'react-native';
89
+ import Modal from 'react-native-modal';
90
+
91
+ function ModalTester() {
92
+ const [isModalVisible, setModalVisible] = useState(false);
93
+
94
+ const toggleModal = () => {
95
+ setModalVisible(!isModalVisible);
96
+ };
97
+
98
+ return (
99
+ <View style={{flex: 1}}>
100
+ <Button title="Show modal" onPress={toggleModal} />
101
+
102
+ <Modal isVisible={isModalVisible}>
103
+ <View style={{flex: 1}}>
104
+ <Text>Hello!</Text>
105
+
106
+ <Button title="Hide modal" onPress={toggleModal} />
107
+ </View>
108
+ </Modal>
109
+ </View>
110
+ );
111
+ }
112
+
113
+ export default ModalTester;
114
+ ```
115
+
116
+ For a more complex example take a look at the `/example` directory.
117
+
118
+ ## Available props
119
+
120
+ | Name | Type | Default | Description |
121
+ | -------------------------------- | -------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
122
+ | `animationIn` | `string` or `object` | `"slideInUp"` | Modal show animation |
123
+ | `animationInTiming` | `number` | `300` | Timing for the modal show animation (in ms) |
124
+ | `animationOut` | `string` or `object` | `"slideOutDown"` | Modal hide animation |
125
+ | `animationOutTiming` | `number` | `300` | Timing for the modal hide animation (in ms) |
126
+ | `avoidKeyboard` | `bool` | `false` | Move the modal up if the keyboard is open |
127
+ | `coverScreen` | `bool` | `true` | Will use RN `Modal` component to cover the entire screen wherever the modal is mounted in the component hierarchy |
128
+ | `hasBackdrop` | `bool` | `true` | Render the backdrop |
129
+ | `backdropColor` | `string` | `"black"` | The backdrop background color |
130
+ | `backdropOpacity` | `number` | `0.70` | The backdrop opacity when the modal is visible |
131
+ | `backdropTransitionInTiming` | `number` | `300` | The backdrop show timing (in ms) |
132
+ | `backdropTransitionOutTiming` | `number` | `300` | The backdrop hide timing (in ms) |
133
+ | `customBackdrop` | `node` | `null` | The custom backdrop element |
134
+ | `children` | `node` | **REQUIRED** | The modal content |
135
+ | `deviceHeight` | `number` | `null` | Device height (useful on devices that can hide the navigation bar) |
136
+ | `deviceWidth` | `number` | `null` | Device width (useful on devices that can hide the navigation bar) |
137
+ | `isVisible` | `bool` | **REQUIRED** | Show the modal? |
138
+ | `onBackButtonPress` | `func` | `() => null` | Called when the Android back button is pressed |
139
+ | `onBackdropPress` | `func` | `() => null` | Called when the backdrop is pressed |
140
+ | `onModalWillHide` | `func` | `() => null` | Called before the modal hide animation begins |
141
+ | `onModalHide` | `func` | `() => null` | Called when the modal is completely hidden |
142
+ | `onModalWillShow` | `func` | `() => null` | Called before the modal show animation begins |
143
+ | `onModalShow` | `func` | `() => null` | Called when the modal is completely visible |
144
+ | `onSwipeStart` | `func` | `() => null` | Called when the swipe action started |
145
+ | `onSwipeMove` | `func` | `(percentageShown) => null` | Called on each swipe event |
146
+ | `onSwipeComplete` | `func` | `({ swipingDirection }) => null` | Called when the `swipeThreshold` has been reached |
147
+ | `onSwipeCancel` | `func` | `() => null` | Called when the `swipeThreshold` has not been reached |
148
+ | `panResponderThreshold` | `number` | `4` | The threshold for when the panResponder should pick up swipe events |
149
+ | `scrollOffset` | `number` | `0` | When > 0, disables swipe-to-close, in order to implement scrollable content |
150
+ | `scrollOffsetMax` | `number` | `0` | Used to implement overscroll feel when content is scrollable. See `/example` directory |
151
+ | `scrollTo` | `func` | `null` | Used to implement scrollable modal. See `/example` directory for reference on how to use it |
152
+ | `scrollHorizontal` | `bool` | `false` | Set to true if your scrollView is horizontal (for a correct scroll handling) |
153
+ | `swipeThreshold` | `number` | `100` | Swiping threshold that when reached calls `onSwipeComplete` |
154
+ | `swipeDirection` | `string` or `array` | `null` | Defines the direction where the modal can be swiped. Can be 'up', 'down', 'left, or 'right', or a combination of them like `['up','down']` |
155
+ | `useNativeDriver` | `bool` | `false` | Defines if animations should use native driver |
156
+ | `useNativeDriverForBackdrop` | `bool` | `null` | Defines if animations for backdrop should use native driver (to avoid flashing on android) |
157
+ | `hideModalContentWhileAnimating` | `bool` | `false` | Enhances the performance by hiding the modal content until the animations complete |
158
+ | `propagateSwipe` | `bool` or `func` | `false` | Allows swipe events to propagate to children components (eg a ScrollView inside a modal) |
159
+ | `style` | `any` | `null` | Style applied to the modal |
160
+
161
+ ## Frequently Asked Questions
162
+
163
+ ### The component is not working as expected
164
+
165
+ Under the hood `react-native-modal` uses react-native original [Modal component](https://reactnative.dev/docs/modal).
166
+ Before reporting a bug, try swapping `react-native-modal` with react-native original Modal component and, if the issue persists, check if it has already been reported as a [react-native issue](https://github.com/facebook/react-native/issues).
167
+
168
+ ### The backdrop is not completely filled/covered on some Android devices (Galaxy, for one)
169
+
170
+ React-Native has a few issues detecting the correct device width/height of some devices.
171
+ If you're experiencing this issue, you'll need to install [`react-native-extra-dimensions-android`](https://github.com/Sunhat/react-native-extra-dimensions-android).
172
+ Then, provide the real window height (obtained from `react-native-extra-dimensions-android`) to the modal:
173
+
174
+ ```javascript
175
+ const deviceWidth = Dimensions.get('window').width;
176
+ const deviceHeight =
177
+ Platform.OS === 'ios'
178
+ ? Dimensions.get('window').height
179
+ : require('react-native-extra-dimensions-android').get(
180
+ 'REAL_WINDOW_HEIGHT',
181
+ );
182
+
183
+ function WrapperComponent() {
184
+ const [isModalVisible, setModalVisible] = useState(true);
185
+
186
+ return (
187
+ <Modal
188
+ isVisible={isModalVisible}
189
+ deviceWidth={deviceWidth}
190
+ deviceHeight={deviceHeight}>
191
+ <View style={{flex: 1}}>
192
+ <Text>I am the modal content!</Text>
193
+ </View>
194
+ </Modal>
195
+ );
196
+ }
197
+ ```
198
+
199
+ ### How can I hide the modal by pressing outside of its content?
200
+
201
+ The prop `onBackdropPress` allows you to handle this situation:
202
+
203
+ ```javascript
204
+ <Modal
205
+ isVisible={isModalVisible}
206
+ onBackdropPress={() => setModalVisible(false)}>
207
+ <View style={{flex: 1}}>
208
+ <Text>I am the modal content!</Text>
209
+ </View>
210
+ </Modal>
211
+ ```
212
+
213
+ ### How can I hide the modal by swiping it?
214
+
215
+ The prop `onSwipeComplete` allows you to handle this situation (remember to set `swipeDirection` too!):
216
+
217
+ ```javascript
218
+ <Modal
219
+ isVisible={isModalVisible}
220
+ onSwipeComplete={() => setModalVisible(false)}
221
+ swipeDirection="left">
222
+ <View style={{flex: 1}}>
223
+ <Text>I am the modal content!</Text>
224
+ </View>
225
+ </Modal>
226
+ ```
227
+
228
+ Note that when using `useNativeDriver={true}` the modal won't drag correctly. This is a [known issue](https://github.com/react-native-community/react-native-modal/issues/163#issuecomment-409760695).
229
+
230
+ ### The modal flashes in a weird way when animating
231
+
232
+ Unfortunately this is a [known issue](https://github.com/react-native-community/react-native-modal/issues/92) that happens when `useNativeDriver=true` and must still be solved.
233
+ In the meanwhile as a workaround you can set the `hideModalContentWhileAnimating` prop to `true`: this seems to solve the issue.
234
+ Also, do not assign a `backgroundColor` property directly to the Modal. Prefer to set it on the child container.
235
+
236
+ ### The modal background doesn't animate properly
237
+
238
+ Are you sure you named the `isVisible` prop correctly? Make sure it is spelled correctly: `isVisible`, not `visible`.
239
+
240
+ ### The modal doesn't change orientation
241
+
242
+ Add a `supportedOrientations={['portrait', 'landscape']}` prop to the component, as described [in the React Native documentation](https://reactnative.dev/docs/modal.html#supportedorientations).
243
+
244
+ Also, if you're providing the `deviceHeight` and `deviceWidth` props you'll have to manually update them when the layout changes.
245
+
246
+ ### I can't show multiple modals one after another
247
+
248
+ Unfortunately right now react-native doesn't allow multiple modals to be displayed at the same time.
249
+ This means that, in `react-native-modal`, if you want to immediately show a new modal after closing one you must first make sure that the modal that your closing has completed its hiding animation by using the `onModalHide` prop.
250
+
251
+ ### I can't show multiple modals at the same time
252
+
253
+ See the question above.
254
+ Showing multiple modals (or even alerts/dialogs) at the same time is not doable because of a react-native bug.
255
+ That said, I would strongly advice against using multiple modals at the same time because, most often than not, this leads to a bad UX, especially on mobile (just my opinion).
256
+
257
+ ### The StatusBar style changes when the modal shows up
258
+
259
+ This issue has been discussed [here](https://github.com/react-native-community/react-native-modal/issues/50).
260
+ The TLDR is: it's a know React-Native issue with the Modal component 😞
261
+
262
+ ### The modal is not covering the entire screen
263
+
264
+ The modal style applied by default has a small margin.
265
+ If you want the modal to cover the entire screen you can easily override it this way:
266
+
267
+ ```js
268
+ <Modal style={{margin: 0}}>...</Modal>
269
+ ```
270
+
271
+ ### I can't scroll my ScrollView inside of the modal
272
+
273
+ Enable propagateSwipe to allow your child components to receive swipe events:
274
+
275
+ ```js
276
+ <Modal propagateSwipe>...</Modal>
277
+ ```
278
+
279
+ Please notice that this is still a WIP fix and might not fix your issue yet, see [issue #236](https://github.com/react-native-community/react-native-modal/issues/236).
280
+
281
+ ### The modal enter/exit animation flickers
282
+
283
+ Make sure your `animationIn` and `animationOut` are set correctly.
284
+ We noticed that, for example, using `fadeIn` as an exit animation makes the modal flicker (it should be `fadeOut`!).
285
+ Also, some users have noticed that setting backdropTransitionOutTiming={0} can fix the flicker without affecting the animation.
286
+
287
+ ### The custom backdrop doesn't fill the entire screen
288
+
289
+ You need to specify the size of your custom backdrop component. You can also make it expand to fill the entire screen by adding a `flex: 1` to its style:
290
+
291
+ ```javascript
292
+ <Modal isVisible={isModalVisible} customBackdrop={<View style={{flex: 1}} />}>
293
+ <View style={{flex: 1}}>
294
+ <Text>I am the modal content!</Text>
295
+ </View>
296
+ </Modal>
297
+ ```
298
+
299
+ ### The custom backdrop doesn't dismiss the modal on press
300
+
301
+ You can provide an event handler to the custom backdrop element to dismiss the modal. The prop `onBackdropPress` is not supported for a custom backdrop.
302
+
303
+ ```javascript
304
+ <Modal
305
+ isVisible={isModalVisible}
306
+ customBackdrop={
307
+ <TouchableWithoutFeedback onPress={dismissModalHandler}>
308
+ <View style={{flex: 1}} />
309
+ </TouchableWithoutFeedback>
310
+ }
311
+ />
312
+ ```
313
+
314
+ ## Available animations
315
+
316
+ Take a look at [react-native-animatable](https://github.com/oblador/react-native-animatable) to see the dozens of animations available out-of-the-box. You can also pass in custom animation definitions and have them automatically register with react-native-animatable. For more information on creating custom animations, see the react-native-animatable [animation definition schema](https://github.com/oblador/react-native-animatable#animation-definition-schema).
317
+
318
+ ## Alternatives
319
+
320
+ - [React Native's built-in `<Modal>` component](https://reactnative.dev/docs/modal.html)
321
+ - [React Native Paper `<Modal>` component](https://callstack.github.io/react-native-paper/modal.html)
322
+ - [React Native Modalfy](https://github.com/colorfy-software/react-native-modalfy)
323
+
324
+ ## Acknowledgements
325
+
326
+ Thanks [@oblador](https://github.com/oblador) for react-native-animatable, [@brentvatne](https://github.com/brentvatne) for the npm namespace and to anyone who contributed to this library!
327
+
328
+ Pull requests, feedbacks and suggestions are welcome!
@@ -0,0 +1,4 @@
1
+ import { ReactNativeModal } from './modal';
2
+ export { ModalProps, ReactNativeModal, OnSwipeCompleteParams } from './modal';
3
+ export { AnimationEvent, Animations, SupportedAnimation, Orientation, Direction, PresentationStyle, OnOrientationChange, GestureResponderEvent, } from './types';
4
+ export default ReactNativeModal;
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { ReactNativeModal } from './modal';
2
+ export { ReactNativeModal } from './modal';
3
+ export default ReactNativeModal;
@@ -0,0 +1,138 @@
1
+ import * as React from 'react';
2
+ import { Animated, EmitterSubscription, PanResponderGestureState, PanResponderInstance, ViewStyle, StyleProp, ViewProps } from 'react-native';
3
+ import { CustomAnimation, Animation } from 'react-native-animatable';
4
+ import { Direction, GestureResponderEvent, OnOrientationChange, Orientation, OrNull, PresentationStyle } from './types';
5
+ export type OnSwipeCompleteParams = {
6
+ swipingDirection: Direction;
7
+ };
8
+ type State = {
9
+ showContent: boolean;
10
+ isVisible: boolean;
11
+ deviceWidth: number;
12
+ deviceHeight: number;
13
+ isSwipeable: boolean;
14
+ pan: OrNull<Animated.ValueXY>;
15
+ };
16
+ declare const defaultProps: {
17
+ animationIn: Animation | CustomAnimation;
18
+ animationInTiming: number;
19
+ animationOut: Animation | CustomAnimation;
20
+ animationOutTiming: number;
21
+ avoidKeyboard: boolean;
22
+ coverScreen: boolean;
23
+ hasBackdrop: boolean;
24
+ backdropColor: string;
25
+ backdropOpacity: number;
26
+ backdropTransitionInTiming: number;
27
+ backdropTransitionOutTiming: number;
28
+ customBackdrop: React.ReactNode;
29
+ useNativeDriver: boolean;
30
+ deviceHeight: OrNull<number>;
31
+ deviceWidth: OrNull<number>;
32
+ hideModalContentWhileAnimating: boolean;
33
+ propagateSwipe: boolean | ((event: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean);
34
+ isVisible: boolean;
35
+ panResponderThreshold: number;
36
+ swipeThreshold: number;
37
+ onModalShow: () => void;
38
+ onModalWillShow: () => void;
39
+ onModalHide: () => void;
40
+ onModalWillHide: () => void;
41
+ onBackdropPress: () => void;
42
+ onBackButtonPress: () => void;
43
+ scrollTo: OrNull<(e: any) => void>;
44
+ scrollOffset: number;
45
+ scrollOffsetMax: number;
46
+ scrollHorizontal: boolean;
47
+ statusBarTranslucent: boolean;
48
+ supportedOrientations: Orientation[];
49
+ };
50
+ export type ModalProps = ViewProps & {
51
+ children: React.ReactNode;
52
+ onSwipeStart?: (gestureState: PanResponderGestureState) => void;
53
+ onSwipeMove?: (percentageShown: number, gestureState: PanResponderGestureState) => void;
54
+ onSwipeComplete?: (params: OnSwipeCompleteParams, gestureState: PanResponderGestureState) => void;
55
+ onSwipeCancel?: (gestureState: PanResponderGestureState) => void;
56
+ style?: StyleProp<ViewStyle>;
57
+ swipeDirection?: Direction | Array<Direction>;
58
+ onDismiss?: () => void;
59
+ onShow?: () => void;
60
+ hardwareAccelerated?: boolean;
61
+ onOrientationChange?: OnOrientationChange;
62
+ presentationStyle?: PresentationStyle;
63
+ useNativeDriverForBackdrop?: boolean;
64
+ } & typeof defaultProps;
65
+ export declare class ReactNativeModal extends React.Component<ModalProps, State> {
66
+ static defaultProps: {
67
+ animationIn: Animation | CustomAnimation;
68
+ animationInTiming: number;
69
+ animationOut: Animation | CustomAnimation;
70
+ animationOutTiming: number;
71
+ avoidKeyboard: boolean;
72
+ coverScreen: boolean;
73
+ hasBackdrop: boolean;
74
+ backdropColor: string;
75
+ backdropOpacity: number;
76
+ backdropTransitionInTiming: number;
77
+ backdropTransitionOutTiming: number;
78
+ customBackdrop: React.ReactNode;
79
+ useNativeDriver: boolean;
80
+ deviceHeight: OrNull<number>;
81
+ deviceWidth: OrNull<number>;
82
+ hideModalContentWhileAnimating: boolean;
83
+ propagateSwipe: boolean | ((event: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean);
84
+ isVisible: boolean;
85
+ panResponderThreshold: number;
86
+ swipeThreshold: number;
87
+ onModalShow: () => void;
88
+ onModalWillShow: () => void;
89
+ onModalHide: () => void;
90
+ onModalWillHide: () => void;
91
+ onBackdropPress: () => void;
92
+ onBackButtonPress: () => void;
93
+ scrollTo: OrNull<(e: any) => void>;
94
+ scrollOffset: number;
95
+ scrollOffsetMax: number;
96
+ scrollHorizontal: boolean;
97
+ statusBarTranslucent: boolean;
98
+ supportedOrientations: Orientation[];
99
+ };
100
+ backdropAnimatedOpacity: Animated.Value;
101
+ backHandler: {
102
+ remove: () => void;
103
+ } | null;
104
+ isTransitioning: boolean;
105
+ inSwipeClosingState: boolean;
106
+ currentSwipingDirection: OrNull<Direction>;
107
+ animationIn: string;
108
+ animationOut: string;
109
+ contentRef: any;
110
+ panResponder: PanResponderInstance | null;
111
+ didUpdateDimensionsEmitter: EmitterSubscription | null;
112
+ interactionHandle: number | null;
113
+ constructor(props: ModalProps);
114
+ static getDerivedStateFromProps(nextProps: ModalProps, state: State): {
115
+ isVisible: boolean;
116
+ showContent: boolean;
117
+ } | null;
118
+ componentDidMount(): void;
119
+ componentWillUnmount(): void;
120
+ componentDidUpdate(prevProps: ModalProps): void;
121
+ getDeviceHeight: () => number;
122
+ getDeviceWidth: () => number;
123
+ onBackButtonPress: () => boolean;
124
+ shouldPropagateSwipe: (evt: any, gestureState: PanResponderGestureState) => boolean;
125
+ buildPanResponder: () => void;
126
+ getAccDistancePerDirection: (gestureState: PanResponderGestureState) => number;
127
+ getSwipingDirection: (gestureState: PanResponderGestureState) => "up" | "down" | "left" | "right";
128
+ calcDistancePercentage: (gestureState: PanResponderGestureState) => number;
129
+ createAnimationEventForSwipe: () => ((...args: any[]) => void) | null;
130
+ isDirectionIncluded: (direction: Direction) => boolean;
131
+ isSwipeDirectionAllowed: ({ dy, dx }: PanResponderGestureState) => boolean;
132
+ handleDimensionsUpdate: () => void;
133
+ open: () => void;
134
+ close: () => void;
135
+ makeBackdrop: () => React.JSX.Element | null;
136
+ render(): React.JSX.Element;
137
+ }
138
+ export default ReactNativeModal;
package/dist/modal.js ADDED
@@ -0,0 +1,476 @@
1
+ import * as React from 'react';
2
+ import { BackHandler, Animated, DeviceEventEmitter, Dimensions, InteractionManager, KeyboardAvoidingView, Modal, PanResponder, Platform, TouchableWithoutFeedback, View, } from 'react-native';
3
+ import { buildAnimations, initializeAnimations, reversePercentage, } from './utils';
4
+ import styles from './modal.style';
5
+ import * as animatable from 'react-native-animatable';
6
+ initializeAnimations();
7
+ const defaultProps = {
8
+ animationIn: 'slideInUp',
9
+ animationInTiming: 300,
10
+ animationOut: 'slideOutDown',
11
+ animationOutTiming: 300,
12
+ avoidKeyboard: false,
13
+ coverScreen: true,
14
+ hasBackdrop: true,
15
+ backdropColor: 'black',
16
+ backdropOpacity: 0.7,
17
+ backdropTransitionInTiming: 300,
18
+ backdropTransitionOutTiming: 300,
19
+ customBackdrop: null,
20
+ useNativeDriver: false,
21
+ deviceHeight: null,
22
+ deviceWidth: null,
23
+ hideModalContentWhileAnimating: false,
24
+ propagateSwipe: false,
25
+ isVisible: false,
26
+ panResponderThreshold: 4,
27
+ swipeThreshold: 100,
28
+ onModalShow: (() => null),
29
+ onModalWillShow: (() => null),
30
+ onModalHide: (() => null),
31
+ onModalWillHide: (() => null),
32
+ onBackdropPress: (() => null),
33
+ onBackButtonPress: (() => null),
34
+ scrollTo: null,
35
+ scrollOffset: 0,
36
+ scrollOffsetMax: 0,
37
+ scrollHorizontal: false,
38
+ statusBarTranslucent: false,
39
+ supportedOrientations: ['portrait', 'landscape'],
40
+ };
41
+ const extractAnimationFromProps = (props) => ({
42
+ animationIn: props.animationIn,
43
+ animationOut: props.animationOut,
44
+ });
45
+ export class ReactNativeModal extends React.Component {
46
+ static defaultProps = defaultProps;
47
+ backdropAnimatedOpacity = new Animated.Value(0);
48
+ backHandler = null;
49
+ isTransitioning = false;
50
+ inSwipeClosingState = false;
51
+ // currentSwipingDirection: SwipeDirection | null = null;
52
+ currentSwipingDirection = null;
53
+ animationIn;
54
+ animationOut;
55
+ contentRef;
56
+ panResponder = null;
57
+ didUpdateDimensionsEmitter = null;
58
+ interactionHandle = null;
59
+ constructor(props) {
60
+ super(props);
61
+ const { animationIn, animationOut } = buildAnimations(extractAnimationFromProps(props));
62
+ this.animationIn = animationIn;
63
+ this.animationOut = animationOut;
64
+ this.state = {
65
+ showContent: false,
66
+ isVisible: false,
67
+ deviceWidth: Dimensions.get('window').width,
68
+ deviceHeight: Dimensions.get('window').height,
69
+ isSwipeable: !!props.swipeDirection,
70
+ pan: null,
71
+ };
72
+ if (this.state.isSwipeable) {
73
+ this.state = {
74
+ ...this.state,
75
+ pan: new Animated.ValueXY(),
76
+ };
77
+ this.buildPanResponder();
78
+ }
79
+ if (props.isVisible) {
80
+ this.state = {
81
+ ...this.state,
82
+ isVisible: true,
83
+ showContent: true,
84
+ };
85
+ }
86
+ const initial = props.isVisible ? props.backdropOpacity : 0;
87
+ this.backdropAnimatedOpacity = new Animated.Value(initial);
88
+ }
89
+ static getDerivedStateFromProps(nextProps, state) {
90
+ if (!state.isVisible && nextProps.isVisible) {
91
+ return { isVisible: true, showContent: false };
92
+ }
93
+ return null;
94
+ }
95
+ componentDidMount() {
96
+ if (this.props.onSwipe) {
97
+ console.warn('`<Modal onSwipe="..." />` is deprecated and will be removed starting from 13.0.0. Use `<Modal onSwipeComplete="..." />` instead.');
98
+ }
99
+ this.didUpdateDimensionsEmitter = DeviceEventEmitter.addListener('didUpdateDimensions', this.handleDimensionsUpdate);
100
+ if (this.state.isVisible) {
101
+ this.open();
102
+ }
103
+ this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPress);
104
+ }
105
+ componentWillUnmount() {
106
+ if (this.backHandler) {
107
+ this.backHandler.remove();
108
+ this.backHandler = null;
109
+ }
110
+ if (this.didUpdateDimensionsEmitter) {
111
+ this.didUpdateDimensionsEmitter.remove();
112
+ this.didUpdateDimensionsEmitter = null;
113
+ }
114
+ if (this.interactionHandle) {
115
+ InteractionManager.clearInteractionHandle(this.interactionHandle);
116
+ this.interactionHandle = null;
117
+ }
118
+ }
119
+ componentDidUpdate(prevProps) {
120
+ if (this.props.animationIn !== prevProps.animationIn ||
121
+ this.props.animationOut !== prevProps.animationOut) {
122
+ const { animationIn, animationOut } = buildAnimations(extractAnimationFromProps(this.props));
123
+ this.animationIn = animationIn;
124
+ this.animationOut = animationOut;
125
+ }
126
+ if (this.props.isVisible && !prevProps.isVisible) {
127
+ this.open();
128
+ }
129
+ else if (!this.props.isVisible && prevProps.isVisible) {
130
+ this.close();
131
+ }
132
+ }
133
+ getDeviceHeight = () => this.props.deviceHeight || this.state.deviceHeight;
134
+ getDeviceWidth = () => this.props.deviceWidth || this.state.deviceWidth;
135
+ onBackButtonPress = () => {
136
+ if (this.props.onBackButtonPress && this.props.isVisible) {
137
+ this.props.onBackButtonPress();
138
+ return true;
139
+ }
140
+ return false;
141
+ };
142
+ shouldPropagateSwipe = (evt, gestureState) => {
143
+ return typeof this.props.propagateSwipe === 'function'
144
+ ? this.props.propagateSwipe(evt, gestureState)
145
+ : this.props.propagateSwipe;
146
+ };
147
+ buildPanResponder = () => {
148
+ let animEvt = null;
149
+ this.panResponder = PanResponder.create({
150
+ onMoveShouldSetPanResponder: (evt, gestureState) => {
151
+ if (!this.shouldPropagateSwipe(evt, gestureState)) {
152
+ const shouldSetPanResponder = Math.abs(gestureState.dx) >=
153
+ (this.props.panResponderThreshold ?? 4) ||
154
+ Math.abs(gestureState.dy) >=
155
+ (this.props.panResponderThreshold ?? 4);
156
+ if (shouldSetPanResponder && this.props.onSwipeStart) {
157
+ this.props.onSwipeStart(gestureState);
158
+ }
159
+ this.currentSwipingDirection = this.getSwipingDirection(gestureState);
160
+ animEvt = this.createAnimationEventForSwipe();
161
+ return shouldSetPanResponder;
162
+ }
163
+ return false;
164
+ },
165
+ onStartShouldSetPanResponder: (e, gestureState) => {
166
+ const hasScrollableView = e?._dispatchInstances &&
167
+ e._dispatchInstances.some((instance) => /scrollview|flatlist/i.test(instance.type));
168
+ if (hasScrollableView &&
169
+ this.shouldPropagateSwipe(e, gestureState) &&
170
+ this.props.scrollTo &&
171
+ (this.props.scrollOffset ?? 0) > 0) {
172
+ return false;
173
+ }
174
+ if (this.props.onSwipeStart) {
175
+ this.props.onSwipeStart(gestureState);
176
+ }
177
+ this.currentSwipingDirection = null;
178
+ return true;
179
+ },
180
+ onPanResponderMove: (evt, gestureState) => {
181
+ if (!this.currentSwipingDirection) {
182
+ if (gestureState.dx === 0 && gestureState.dy === 0)
183
+ return;
184
+ this.currentSwipingDirection = this.getSwipingDirection(gestureState);
185
+ animEvt = this.createAnimationEventForSwipe();
186
+ }
187
+ if (this.isSwipeDirectionAllowed(gestureState)) {
188
+ const newOpacityFactor = 1 - this.calcDistancePercentage(gestureState);
189
+ animEvt?.(evt, gestureState);
190
+ if (this.props.onSwipeMove) {
191
+ this.props.onSwipeMove(newOpacityFactor, gestureState);
192
+ }
193
+ }
194
+ else {
195
+ if (this.props.scrollTo) {
196
+ if (this.props.scrollHorizontal) {
197
+ let offsetX = -gestureState.dx;
198
+ if (offsetX > (this.props.scrollOffsetMax ?? 0)) {
199
+ offsetX -= (offsetX - (this.props.scrollOffsetMax ?? 0)) / 2;
200
+ }
201
+ this.props.scrollTo({ x: offsetX, animated: false });
202
+ }
203
+ else {
204
+ let offsetY = -gestureState.dy;
205
+ if (offsetY > (this.props.scrollOffsetMax ?? 0)) {
206
+ offsetY -= (offsetY - (this.props.scrollOffsetMax ?? 0)) / 2;
207
+ }
208
+ this.props.scrollTo({ y: offsetY, animated: false });
209
+ }
210
+ }
211
+ }
212
+ },
213
+ onPanResponderRelease: (evt, gestureState) => {
214
+ const accDistance = this.getAccDistancePerDirection(gestureState);
215
+ if (accDistance > (this.props.swipeThreshold ?? 100) &&
216
+ this.isSwipeDirectionAllowed(gestureState)) {
217
+ if (this.props.onSwipeComplete) {
218
+ this.inSwipeClosingState = true;
219
+ this.props.onSwipeComplete({
220
+ swipingDirection: this.getSwipingDirection(gestureState),
221
+ }, gestureState);
222
+ return;
223
+ }
224
+ // Deprecated
225
+ if (this.props.onSwipe) {
226
+ this.inSwipeClosingState = true;
227
+ this.props.onSwipe();
228
+ return;
229
+ }
230
+ }
231
+ if (this.props.onSwipeCancel) {
232
+ this.props.onSwipeCancel(gestureState);
233
+ }
234
+ if (this.state.pan) {
235
+ Animated.spring(this.state.pan, {
236
+ toValue: { x: 0, y: 0 },
237
+ bounciness: 0,
238
+ useNativeDriver: false,
239
+ }).start();
240
+ }
241
+ if (this.props.scrollTo) {
242
+ if ((this.props.scrollOffset ?? 0) > (this.props.scrollOffsetMax ?? 0)) {
243
+ this.props.scrollTo({
244
+ y: this.props.scrollOffsetMax ?? 0,
245
+ animated: true,
246
+ });
247
+ }
248
+ }
249
+ },
250
+ });
251
+ };
252
+ getAccDistancePerDirection = (gestureState) => {
253
+ switch (this.currentSwipingDirection) {
254
+ case 'up':
255
+ return -gestureState.dy;
256
+ case 'down':
257
+ return gestureState.dy;
258
+ case 'right':
259
+ return gestureState.dx;
260
+ case 'left':
261
+ return -gestureState.dx;
262
+ default:
263
+ return 0;
264
+ }
265
+ };
266
+ getSwipingDirection = (gestureState) => {
267
+ if (Math.abs(gestureState.dx) > Math.abs(gestureState.dy)) {
268
+ return gestureState.dx > 0 ? 'right' : 'left';
269
+ }
270
+ return gestureState.dy > 0 ? 'down' : 'up';
271
+ };
272
+ calcDistancePercentage = (gestureState) => {
273
+ const deviceHeight = this.props.deviceHeight || this.state.deviceHeight;
274
+ const deviceWidth = this.props.deviceWidth || this.state.deviceWidth;
275
+ switch (this.currentSwipingDirection) {
276
+ case 'down':
277
+ return ((gestureState.moveY - gestureState.y0) /
278
+ (deviceHeight - gestureState.y0));
279
+ case 'up':
280
+ return reversePercentage(gestureState.moveY / gestureState.y0);
281
+ case 'left':
282
+ return reversePercentage(gestureState.moveX / gestureState.x0);
283
+ case 'right':
284
+ return ((gestureState.moveX - gestureState.x0) /
285
+ (deviceWidth - gestureState.x0));
286
+ default:
287
+ return 0;
288
+ }
289
+ };
290
+ createAnimationEventForSwipe = () => {
291
+ if (!this.state.pan)
292
+ return null;
293
+ if (this.currentSwipingDirection === 'right' ||
294
+ this.currentSwipingDirection === 'left') {
295
+ return Animated.event([null, { dx: this.state.pan.x }], {
296
+ useNativeDriver: false,
297
+ });
298
+ }
299
+ return Animated.event([null, { dy: this.state.pan.y }], {
300
+ useNativeDriver: false,
301
+ });
302
+ };
303
+ isDirectionIncluded = (direction) => {
304
+ return Array.isArray(this.props.swipeDirection)
305
+ ? this.props.swipeDirection.includes(direction)
306
+ : this.props.swipeDirection === direction;
307
+ };
308
+ isSwipeDirectionAllowed = ({ dy, dx }) => {
309
+ const draggedDown = dy > 0;
310
+ const draggedUp = dy < 0;
311
+ const draggedLeft = dx < 0;
312
+ const draggedRight = dx > 0;
313
+ if (this.currentSwipingDirection === 'up' &&
314
+ this.isDirectionIncluded('up') &&
315
+ draggedUp)
316
+ return true;
317
+ if (this.currentSwipingDirection === 'down' &&
318
+ this.isDirectionIncluded('down') &&
319
+ draggedDown)
320
+ return true;
321
+ if (this.currentSwipingDirection === 'right' &&
322
+ this.isDirectionIncluded('right') &&
323
+ draggedRight)
324
+ return true;
325
+ if (this.currentSwipingDirection === 'left' &&
326
+ this.isDirectionIncluded('left') &&
327
+ draggedLeft)
328
+ return true;
329
+ return false;
330
+ };
331
+ handleDimensionsUpdate = () => {
332
+ if (!this.props.deviceHeight && !this.props.deviceWidth) {
333
+ const deviceWidth = Dimensions.get('window').width;
334
+ const deviceHeight = Dimensions.get('window').height;
335
+ if (deviceWidth !== this.state.deviceWidth ||
336
+ deviceHeight !== this.state.deviceHeight) {
337
+ this.setState({ deviceWidth, deviceHeight });
338
+ }
339
+ }
340
+ };
341
+ open = () => {
342
+ if (this.isTransitioning)
343
+ return;
344
+ this.isTransitioning = true;
345
+ Animated.timing(this.backdropAnimatedOpacity, {
346
+ toValue: this.props.backdropOpacity ?? 0.7,
347
+ duration: this.props.backdropTransitionInTiming ?? 300,
348
+ useNativeDriver: true,
349
+ }).start();
350
+ if (this.contentRef) {
351
+ this.props.onModalWillShow?.();
352
+ if (this.interactionHandle == null) {
353
+ this.interactionHandle = InteractionManager.createInteractionHandle();
354
+ }
355
+ this.contentRef
356
+ .animate(this.animationIn, this.props.animationInTiming ?? 300)
357
+ .then(() => {
358
+ this.isTransitioning = false;
359
+ if (this.interactionHandle) {
360
+ InteractionManager.clearInteractionHandle(this.interactionHandle);
361
+ this.interactionHandle = null;
362
+ }
363
+ if (!this.props.isVisible) {
364
+ this.close();
365
+ }
366
+ else {
367
+ this.props.onModalShow?.();
368
+ }
369
+ });
370
+ }
371
+ };
372
+ close = () => {
373
+ if (this.isTransitioning)
374
+ return;
375
+ this.isTransitioning = true;
376
+ Animated.timing(this.backdropAnimatedOpacity, {
377
+ toValue: 0,
378
+ duration: this.props.backdropTransitionOutTiming ?? 300,
379
+ useNativeDriver: true,
380
+ }).start();
381
+ let animationOut = this.animationOut;
382
+ if (this.inSwipeClosingState) {
383
+ this.inSwipeClosingState = false;
384
+ if (this.currentSwipingDirection === 'up')
385
+ animationOut = 'slideOutUp';
386
+ else if (this.currentSwipingDirection === 'down')
387
+ animationOut = 'slideOutDown';
388
+ else if (this.currentSwipingDirection === 'right')
389
+ animationOut = 'slideOutRight';
390
+ else if (this.currentSwipingDirection === 'left')
391
+ animationOut = 'slideOutLeft';
392
+ }
393
+ if (this.contentRef) {
394
+ this.props.onModalWillHide?.();
395
+ if (this.interactionHandle == null) {
396
+ this.interactionHandle = InteractionManager.createInteractionHandle();
397
+ }
398
+ this.contentRef
399
+ .animate(animationOut, this.props.animationOutTiming ?? 300)
400
+ .then(() => {
401
+ this.isTransitioning = false;
402
+ if (this.interactionHandle) {
403
+ InteractionManager.clearInteractionHandle(this.interactionHandle);
404
+ this.interactionHandle = null;
405
+ }
406
+ if (this.props.isVisible) {
407
+ this.open();
408
+ }
409
+ else {
410
+ this.setState({ showContent: false }, () => {
411
+ this.setState({ isVisible: false }, () => {
412
+ this.props.onModalHide?.();
413
+ });
414
+ });
415
+ }
416
+ });
417
+ }
418
+ setTimeout(() => {
419
+ if (this.state.isSwipeable && this.state.pan) {
420
+ this.state.pan.setValue({ x: 0, y: 0 });
421
+ }
422
+ }, 150);
423
+ };
424
+ makeBackdrop = () => {
425
+ if (!this.props.hasBackdrop)
426
+ return null;
427
+ const { customBackdrop, onBackdropPress } = this.props;
428
+ const hasCustomBackdrop = !!customBackdrop;
429
+ const backdropWrapper = (React.createElement(Animated.View, { style: [
430
+ styles.backdrop,
431
+ {
432
+ width: this.getDeviceWidth(),
433
+ height: this.getDeviceHeight(),
434
+ backgroundColor: !hasCustomBackdrop
435
+ ? this.props.backdropColor
436
+ : 'transparent',
437
+ opacity: this.backdropAnimatedOpacity,
438
+ },
439
+ ] }, hasCustomBackdrop && customBackdrop));
440
+ if (hasCustomBackdrop)
441
+ return backdropWrapper;
442
+ return (React.createElement(TouchableWithoutFeedback, { onPress: onBackdropPress }, backdropWrapper));
443
+ };
444
+ render() {
445
+ const { animationIn, animationInTiming, animationOut, animationOutTiming, avoidKeyboard, coverScreen, hasBackdrop, backdropColor, backdropOpacity, backdropTransitionInTiming, backdropTransitionOutTiming, customBackdrop, children, isVisible, onModalShow, onBackButtonPress, useNativeDriver, propagateSwipe, style, ...otherProps } = this.props;
446
+ const computedStyle = [
447
+ { margin: this.getDeviceWidth() * 0.05 },
448
+ styles.content,
449
+ style,
450
+ ];
451
+ let panHandlers = {};
452
+ let panPosition = {};
453
+ if (this.state.isSwipeable && this.panResponder && this.state.pan) {
454
+ panHandlers = { ...this.panResponder.panHandlers };
455
+ if (useNativeDriver) {
456
+ panPosition = { transform: this.state.pan.getTranslateTransform() };
457
+ }
458
+ else {
459
+ panPosition = this.state.pan.getLayout();
460
+ }
461
+ }
462
+ const _children = this.props.hideModalContentWhileAnimating &&
463
+ this.props.useNativeDriver &&
464
+ !this.state.showContent ? (React.createElement(animatable.View, null)) : (children);
465
+ const containerView = (React.createElement(animatable.View, { ...panHandlers, ref: (ref) => (this.contentRef = ref), style: [panPosition, computedStyle], pointerEvents: "box-none", useNativeDriver: useNativeDriver, ...otherProps }, _children));
466
+ if (!coverScreen && this.state.isVisible) {
467
+ return (React.createElement(View, { pointerEvents: "box-none", style: [styles.backdrop, styles.containerBox] },
468
+ this.makeBackdrop(),
469
+ containerView));
470
+ }
471
+ return (React.createElement(Modal, { transparent: true, animationType: "none", visible: this.state.isVisible, onRequestClose: onBackButtonPress, ...otherProps },
472
+ this.makeBackdrop(),
473
+ avoidKeyboard ? (React.createElement(KeyboardAvoidingView, { behavior: Platform.OS === 'ios' ? 'padding' : undefined, pointerEvents: "box-none", style: computedStyle.concat([{ margin: 0 }]) }, containerView)) : (containerView)));
474
+ }
475
+ }
476
+ export default ReactNativeModal;
@@ -0,0 +1,21 @@
1
+ declare const _default: {
2
+ backdrop: {
3
+ position: "absolute";
4
+ top: number;
5
+ bottom: number;
6
+ left: number;
7
+ right: number;
8
+ opacity: number;
9
+ backgroundColor: string;
10
+ };
11
+ content: {
12
+ flex: number;
13
+ justifyContent: "center";
14
+ };
15
+ containerBox: {
16
+ zIndex: number;
17
+ opacity: number;
18
+ backgroundColor: string;
19
+ };
20
+ };
21
+ export default _default;
@@ -0,0 +1,21 @@
1
+ import { StyleSheet } from 'react-native';
2
+ export default StyleSheet.create({
3
+ backdrop: {
4
+ position: 'absolute',
5
+ top: 0,
6
+ bottom: 0,
7
+ left: 0,
8
+ right: 0,
9
+ opacity: 0,
10
+ backgroundColor: 'black',
11
+ },
12
+ content: {
13
+ flex: 1,
14
+ justifyContent: 'center',
15
+ },
16
+ containerBox: {
17
+ zIndex: 2,
18
+ opacity: 1,
19
+ backgroundColor: 'transparent',
20
+ },
21
+ });
@@ -0,0 +1,15 @@
1
+ import { Animation, CustomAnimation } from 'react-native-animatable';
2
+ import { NativeSyntheticEvent, NativeTouchEvent } from 'react-native';
3
+ export type OrNull<T> = null | T;
4
+ export type SupportedAnimation = Animation | CustomAnimation;
5
+ export type Animations = {
6
+ animationIn: string;
7
+ animationOut: string;
8
+ };
9
+ export type Orientation = 'portrait' | 'portrait-upside-down' | 'landscape' | 'landscape-left' | 'landscape-right';
10
+ export type Direction = 'up' | 'down' | 'left' | 'right';
11
+ export type AnimationEvent = (...args: any[]) => void;
12
+ export type PresentationStyle = 'fullScreen' | 'pageSheet' | 'formSheet' | 'overFullScreen';
13
+ export type OnOrientationChange = (orientation: NativeSyntheticEvent<any>) => void;
14
+ export interface GestureResponderEvent extends NativeSyntheticEvent<NativeTouchEvent> {
15
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { CustomAnimation, Animation } from 'react-native-animatable';
2
+ import { Animations } from './types';
3
+ export declare const initializeAnimations: () => void;
4
+ export declare const makeSlideTranslation: (translationType: string, fromValue: number, toValue: number) => {
5
+ from: {
6
+ [x: string]: number;
7
+ };
8
+ to: {
9
+ [x: string]: number;
10
+ };
11
+ };
12
+ export declare const buildAnimations: ({ animationIn, animationOut, }: {
13
+ animationIn: Animation | CustomAnimation;
14
+ animationOut: Animation | CustomAnimation;
15
+ }) => Animations;
16
+ export declare const reversePercentage: (x: number) => number;
package/dist/utils.js ADDED
@@ -0,0 +1,59 @@
1
+ import { Dimensions } from 'react-native';
2
+ import * as animatable from 'react-native-animatable';
3
+ const { height, width } = Dimensions.get('window');
4
+ export const initializeAnimations = () => {
5
+ // Since react-native-animatable applies by default a margin of 100 to its
6
+ // sliding animation, we reset them here overriding the margin to 0.
7
+ const animationDefinitions = {
8
+ slideInDown: makeSlideTranslation('translateY', -height, 0),
9
+ slideInUp: makeSlideTranslation('translateY', height, 0),
10
+ slideInLeft: makeSlideTranslation('translateX', -width, 0),
11
+ slideInRight: makeSlideTranslation('translateX', width, 0),
12
+ slideOutDown: makeSlideTranslation('translateY', 0, height),
13
+ slideOutUp: makeSlideTranslation('translateY', 0, -height),
14
+ slideOutLeft: makeSlideTranslation('translateX', 0, -width),
15
+ slideOutRight: makeSlideTranslation('translateX', 0, width),
16
+ };
17
+ animatable.initializeRegistryWithDefinitions(animationDefinitions);
18
+ };
19
+ export const makeSlideTranslation = (translationType, fromValue, toValue) => ({
20
+ from: {
21
+ [translationType]: fromValue,
22
+ },
23
+ to: {
24
+ [translationType]: toValue,
25
+ },
26
+ });
27
+ // User can define custom react-native-animatable animations, see PR #72
28
+ // Utility for creating our own custom react-native-animatable animations
29
+ export const buildAnimations = ({ animationIn, animationOut, }) => {
30
+ let updatedAnimationIn;
31
+ let updatedAnimationOut;
32
+ if (isObject(animationIn)) {
33
+ const animationName = JSON.stringify(animationIn);
34
+ makeAnimation(animationName, animationIn);
35
+ updatedAnimationIn = animationName;
36
+ }
37
+ else {
38
+ updatedAnimationIn = animationIn;
39
+ }
40
+ if (isObject(animationOut)) {
41
+ const animationName = JSON.stringify(animationOut);
42
+ makeAnimation(animationName, animationOut);
43
+ updatedAnimationOut = animationName;
44
+ }
45
+ else {
46
+ updatedAnimationOut = animationOut;
47
+ }
48
+ return {
49
+ animationIn: updatedAnimationIn,
50
+ animationOut: updatedAnimationOut,
51
+ };
52
+ };
53
+ export const reversePercentage = (x) => -(x - 1);
54
+ const makeAnimation = (name, obj) => {
55
+ animatable.registerAnimation(name, animatable.createAnimation(obj));
56
+ };
57
+ const isObject = (obj) => {
58
+ return obj !== null && typeof obj === 'object';
59
+ };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@ealimardani/react-native-modal",
3
+ "version": "14.3.0",
4
+ "description": "An enhanced React Native modal",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/"
9
+ ],
10
+ "scripts": {
11
+ "test": "yarn run lint",
12
+ "build": "tsc",
13
+ "dev": "tsc --watch",
14
+ "test:ts": "tsc --noEmit"
15
+ },
16
+ "husky": {
17
+ "hooks": {
18
+ "pre-commit": "pretty-quick --staged"
19
+ }
20
+ },
21
+ "keywords": [
22
+ "react-native",
23
+ "react",
24
+ "native",
25
+ "modal",
26
+ "android",
27
+ "ios",
28
+ "backdrop",
29
+ "simple",
30
+ "animated"
31
+ ],
32
+ "authors": [
33
+ "Mazzarolo Matteo",
34
+ "Anthony Cyrille"
35
+ ],
36
+ "license": "MIT",
37
+ "homepage": "https://github.com/react-native-modal/react-native-modal",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/react-native-modal/react-native-modal"
41
+ },
42
+ "dependencies": {
43
+ "react-native-animatable": "1.4.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/react": "^19.0.10",
47
+ "husky": "^3.0.9",
48
+ "postinstall": "^0.5.1",
49
+ "prettier": "^1.18.2",
50
+ "pretty-quick": "^2.0.0",
51
+ "react": "19.0.0",
52
+ "react-native": "0.78.0",
53
+ "typescript": "5.8.2"
54
+ },
55
+ "peerDependencies": {
56
+ "react": "*",
57
+ "react-native": ">=0.70.0"
58
+ },
59
+ "packageManager": "yarn@1.22.22"
60
+ }