react-native-my-survey-sdk 2.1.7 → 2.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-my-survey-sdk",
3
- "version": "2.1.7",
3
+ "version": "2.1.8",
4
4
  "description": "Xebo survey collection SDK for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -1,15 +1,14 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
5
5
  TouchableOpacity,
6
6
  StyleSheet,
7
- Animated,
8
7
  Dimensions,
9
8
  Platform,
10
9
  KeyboardAvoidingView,
11
- Modal,
12
10
  } from 'react-native';
11
+ import RNModal from 'react-native-modal';
13
12
  import { XeboSurveyManager, XEBO_EVENTS } from '../core/XeboSurveyManager';
14
13
  import {
15
14
  XeboSurvey,
@@ -31,10 +30,7 @@ import { XeboRatingView } from './XeboRatingView';
31
30
  import { XeboMultiRatingView } from './XeboMultiRatingView';
32
31
 
33
32
  // 'screen' includes the Android system navigation bar; 'window' excludes it.
34
- // We need 'screen' so the backdrop covers the full physical display.
35
33
  const SCREEN_HEIGHT = Dimensions.get('screen').height;
36
- const SCREEN_WIDTH = Dimensions.get('screen').width;
37
- const MODAL_MAX_HEIGHT = SCREEN_HEIGHT * 0.78; // used as initial off-screen translateY value
38
34
 
39
35
  function computeSheetHeight(question: XeboQuestion | null, screen: ModalScreen): number {
40
36
  if (screen === 'thankYou') return SCREEN_HEIGHT * 0.32;
@@ -44,13 +40,12 @@ function computeSheetHeight(question: XeboQuestion | null, screen: ModalScreen):
44
40
  case XeboQuestionType.multiNps:
45
41
  case XeboQuestionType.multiRating: {
46
42
  const rowCount = question.options?.length ?? 3;
47
- // base ~25% + ~15% per row, capped at 78%
48
43
  return Math.min(SCREEN_HEIGHT * 0.78, SCREEN_HEIGHT * (0.25 + rowCount * 0.15));
49
44
  }
50
45
  case XeboQuestionType.nps:
51
46
  return (question.conditionalFollowUps?.length ?? 0) > 0
52
- ? SCREEN_HEIGHT * 0.62 // NPS Pro — space for follow-up card
53
- : SCREEN_HEIGHT * 0.38; // simple NPS — scale + button only
47
+ ? SCREEN_HEIGHT * 0.62
48
+ : SCREEN_HEIGHT * 0.38;
54
49
  case XeboQuestionType.rating:
55
50
  return question.followUpQuestion ? SCREEN_HEIGHT * 0.56 : SCREEN_HEIGHT * 0.38;
56
51
  case XeboQuestionType.singleTextBox:
@@ -70,7 +65,6 @@ function computeSheetHeight(question: XeboQuestion | null, screen: ModalScreen):
70
65
  }
71
66
  }
72
67
 
73
- // Which screen we show inside the modal
74
68
  type ModalScreen = 'intro' | 'question' | 'thankYou';
75
69
 
76
70
  export const XeboSurveyModal: React.FC = () => {
@@ -88,10 +82,6 @@ export const XeboSurveyModal: React.FC = () => {
88
82
  [survey, questionIndex, screen]
89
83
  );
90
84
 
91
- // Spring slide-up animation
92
- const slideAnim = useRef(new Animated.Value(MODAL_MAX_HEIGHT)).current;
93
- const backdropAnim = useRef(new Animated.Value(0)).current;
94
-
95
85
  // ─── Subscribe to manager events ─────────────────────────────────────────
96
86
 
97
87
  useEffect(() => {
@@ -102,10 +92,17 @@ export const XeboSurveyModal: React.FC = () => {
102
92
 
103
93
  const onVisible = (v: boolean) => {
104
94
  if (v) {
105
- openModal();
106
- } else {
107
- closeModal();
95
+ // Sync survey into React state before showing so first frame isn't blank
96
+ const s = XeboSurveyManager.survey;
97
+ setSurvey(s);
98
+ if (s?.introPage?.enabled) {
99
+ setScreen('intro');
100
+ } else {
101
+ setScreen('question');
102
+ setQuestionIndex(XeboSurveyManager.currentQuestionIndex);
103
+ }
108
104
  }
105
+ setVisible(v);
109
106
  };
110
107
 
111
108
  const onQuestionChanged = (idx: number) => {
@@ -123,57 +120,6 @@ export const XeboSurveyModal: React.FC = () => {
123
120
  };
124
121
  }, []);
125
122
 
126
- // ─── Animations ───────────────────────────────────────────────────────────
127
-
128
- const openModal = useCallback(() => {
129
- // Sync survey into React state NOW so the modal never renders blank on first frame
130
- const s = XeboSurveyManager.survey;
131
- setSurvey(s);
132
- setVisible(true);
133
- slideAnim.setValue(MODAL_MAX_HEIGHT);
134
- backdropAnim.setValue(0);
135
-
136
- // Determine initial screen
137
- if (s?.introPage?.enabled) {
138
- setScreen('intro');
139
- } else {
140
- setScreen('question');
141
- setQuestionIndex(XeboSurveyManager.currentQuestionIndex);
142
- }
143
-
144
- Animated.parallel([
145
- Animated.spring(slideAnim, {
146
- toValue: 0,
147
- tension: 180,
148
- friction: 20,
149
- useNativeDriver: true,
150
- }),
151
- Animated.timing(backdropAnim, {
152
- toValue: 1,
153
- duration: 200,
154
- useNativeDriver: true,
155
- }),
156
- ]).start();
157
- }, []);
158
-
159
- const closeModal = useCallback(() => {
160
- Animated.parallel([
161
- Animated.timing(slideAnim, {
162
- toValue: MODAL_MAX_HEIGHT,
163
- duration: 300,
164
- useNativeDriver: true,
165
- }),
166
- Animated.timing(backdropAnim, {
167
- toValue: 0,
168
- duration: 300,
169
- useNativeDriver: true,
170
- }),
171
- ]).start(() => {
172
- setVisible(false);
173
- setSurvey(null);
174
- });
175
- }, []);
176
-
177
123
  // ─── User actions ─────────────────────────────────────────────────────────
178
124
 
179
125
  const handleSkip = () => {
@@ -190,8 +136,6 @@ export const XeboSurveyModal: React.FC = () => {
190
136
  const answers = Array.isArray(answer) ? answer : [answer];
191
137
  answers.forEach(a => XeboSurveyManager.recordAnswer(a));
192
138
  XeboSurveyManager.nextQuestion();
193
- // questionIndex will be updated via QUESTION_CHANGED event
194
- // But also check if we're now at the thank-you question
195
139
  const s = XeboSurveyManager.survey;
196
140
  const newIdx = XeboSurveyManager.currentQuestionIndex;
197
141
  if (s && s.questions[newIdx]?.id === 'thank_you_auto') {
@@ -203,7 +147,6 @@ export const XeboSurveyModal: React.FC = () => {
203
147
 
204
148
  const renderQuestion = (question: XeboQuestion) => {
205
149
  const questions = survey?.questions ?? [];
206
- // The last "real" question before thank-you
207
150
  const lastRealIndex = questions.findIndex(q => q.id === 'thank_you_auto') - 1;
208
151
  const isLast = questionIndex === lastRealIndex;
209
152
 
@@ -232,8 +175,6 @@ export const XeboSurveyModal: React.FC = () => {
232
175
  }
233
176
  };
234
177
 
235
- // ─── Content ──────────────────────────────────────────────────────────────
236
-
237
178
  const renderContent = () => {
238
179
  if (!survey) return null;
239
180
 
@@ -257,31 +198,35 @@ export const XeboSurveyModal: React.FC = () => {
257
198
  return null;
258
199
  };
259
200
 
260
- if (!visible) return null;
201
+ // ─── Render ───────────────────────────────────────────────────────────────
261
202
 
262
203
  return (
263
- <Modal
264
- transparent
265
- visible={visible}
266
- animationType="none"
267
- statusBarTranslucent
268
- hardwareAccelerated
269
- onRequestClose={handleSkip}
204
+ <RNModal
205
+ isVisible={visible}
206
+ onBackButtonPress={handleSkip}
207
+ backdropOpacity={0.5}
208
+ backdropColor="#000000"
209
+ style={styles.modal}
210
+ animationIn="slideInUp"
211
+ animationInTiming={350}
212
+ animationOut="slideOutDown"
213
+ animationOutTiming={300}
214
+ useNativeDriver={true}
215
+ useNativeDriverForBackdrop={true}
216
+ hideModalContentWhileAnimating={true}
217
+ coverScreen={true}
218
+ propagateSwipe={false}
219
+ onModalHide={() => setSurvey(null)}
270
220
  >
271
- {/* Backdrop — no dismiss on tap */}
272
- <Animated.View style={[styles.backdrop, { opacity: backdropAnim }]} />
273
-
274
221
  <KeyboardAvoidingView
275
- style={styles.kvContainer}
276
222
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
277
223
  keyboardVerticalOffset={0}
278
224
  >
279
- <Animated.View
225
+ <View
280
226
  style={[
281
227
  styles.sheet,
282
228
  {
283
229
  backgroundColor: theme.backgroundColor,
284
- transform: [{ translateY: slideAnim }],
285
230
  height: sheetHeight,
286
231
  },
287
232
  ]}
@@ -305,36 +250,23 @@ export const XeboSurveyModal: React.FC = () => {
305
250
 
306
251
  {/* Safe area bottom padding — home indicator on iOS, gesture nav on Android */}
307
252
  <View style={{ height: bottomInset }} />
308
- </Animated.View>
253
+ </View>
309
254
  </KeyboardAvoidingView>
310
- </Modal>
255
+ </RNModal>
311
256
  );
312
257
  };
313
258
 
314
259
  const styles = StyleSheet.create({
315
- backdrop: {
316
- position: 'absolute',
317
- top: 0,
318
- left: 0,
319
- // Explicit screen dimensions instead of absoluteFillObject so the backdrop
320
- // covers the Android system navigation bar area that window-relative
321
- // positioning would miss, preventing the host app's tab bar from bleeding through.
322
- width: SCREEN_WIDTH,
323
- height: SCREEN_HEIGHT,
324
- backgroundColor: 'rgba(0,0,0,0.5)',
325
- zIndex: 9998,
326
- },
327
- kvContainer: {
328
- flex: 1,
260
+ // react-native-modal wrapper: full screen, sheet aligned to bottom
261
+ modal: {
329
262
  justifyContent: 'flex-end',
330
- zIndex: 9999,
263
+ margin: 0,
331
264
  },
332
265
  sheet: {
333
266
  borderTopLeftRadius: 16,
334
267
  borderTopRightRadius: 16,
335
268
  overflow: 'hidden',
336
269
  elevation: 9999,
337
- zIndex: 9999,
338
270
  shadowColor: '#000',
339
271
  shadowOffset: { width: 0, height: -4 },
340
272
  shadowOpacity: 0.15,