react-native-my-survey-sdk 2.2.4 → 2.2.6

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.2.4",
3
+ "version": "2.2.6",
4
4
  "description": "Xebo survey collection SDK for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -48,9 +48,9 @@ const styles = StyleSheet.create({
48
48
  },
49
49
  scroll: {
50
50
  flex: 1,
51
+ minHeight: 0,
51
52
  },
52
53
  scrollContent: {
53
- flexGrow: 1,
54
54
  paddingBottom: 8,
55
55
  },
56
56
  content: {
@@ -145,7 +145,7 @@ const styles = StyleSheet.create({
145
145
  container: { flex: 1, paddingHorizontal: 20, paddingTop: 16, paddingBottom: 24 },
146
146
  title: { fontSize: 18, fontWeight: '700', marginBottom: 6 },
147
147
  subtitle: { fontSize: 14, opacity: 0.6, marginBottom: 16 },
148
- scroll: { flex: 1, marginBottom: 12 },
148
+ scroll: { flex: 1, minHeight: 0, marginBottom: 12 },
149
149
  rowWrap: { marginBottom: 20 },
150
150
  rowLabel: { fontSize: 15, fontWeight: '500', marginBottom: 8 },
151
151
  scaleRow: {
@@ -145,7 +145,7 @@ const styles = StyleSheet.create({
145
145
  container: { flex: 1, paddingHorizontal: 20, paddingTop: 16, paddingBottom: 24 },
146
146
  title: { fontSize: 18, fontWeight: '700', marginBottom: 6 },
147
147
  subtitle: { fontSize: 14, opacity: 0.6, marginBottom: 16 },
148
- scroll: { flex: 1, marginBottom: 12 },
148
+ scroll: { flex: 1, minHeight: 0, marginBottom: 12 },
149
149
  rowWrap: { marginBottom: 20 },
150
150
  rowLabel: { fontSize: 15, fontWeight: '500', marginBottom: 8 },
151
151
  iconsRow: { flexDirection: 'row', flexWrap: 'nowrap', justifyContent: 'space-between' },
@@ -138,6 +138,7 @@ const styles = StyleSheet.create({
138
138
  },
139
139
  optionsList: {
140
140
  flex: 1,
141
+ minHeight: 0,
141
142
  marginBottom: 12,
142
143
  },
143
144
  optionRow: {
@@ -7,6 +7,7 @@ import {
7
7
  ScrollView,
8
8
  Animated,
9
9
  TextInput,
10
+ Keyboard,
10
11
  } from 'react-native';
11
12
  import { XeboQuestion, XeboAnswer, XeboConditionalFollowUp, XeboQuestionType } from '../models/XeboModels';
12
13
  import { getTheme } from '../theme/XeboTheme';
@@ -85,6 +86,7 @@ export const XeboNPSView: React.FC<Props> = ({ question, isLastQuestion, onAnswe
85
86
  return;
86
87
  }
87
88
 
89
+ Keyboard.dismiss();
88
90
  const score = selectedScore ?? 0;
89
91
  const colId = question.columns?.[score]?.id ?? score.toString();
90
92
  const rowId = question.options?.[0]?.id ?? score.toString();
@@ -259,7 +261,7 @@ const styles = StyleSheet.create({
259
261
  paddingHorizontal: 20,
260
262
  paddingBottom: 16,
261
263
  },
262
- scroll: { flex: 1 },
264
+ scroll: { flex: 1, minHeight: 0 },
263
265
  scrollContent: {
264
266
  paddingTop: 16,
265
267
  paddingBottom: 8,
@@ -7,6 +7,7 @@ import {
7
7
  ScrollView,
8
8
  Animated,
9
9
  TextInput,
10
+ Keyboard,
10
11
  } from 'react-native';
11
12
  import { XeboQuestion, XeboAnswer } from '../models/XeboModels';
12
13
  import { getTheme } from '../theme/XeboTheme';
@@ -125,6 +126,7 @@ export const XeboRatingView: React.FC<Props> = ({ question, isLastQuestion, onAn
125
126
  shake();
126
127
  return;
127
128
  }
129
+ Keyboard.dismiss();
128
130
  const idx = selected ?? 0;
129
131
  const colId = question.columns?.[idx]?.id ?? idx.toString();
130
132
  const rowId = question.options?.[0]?.id ?? 'rating';
@@ -235,7 +237,7 @@ const styles = StyleSheet.create({
235
237
  paddingHorizontal: 20,
236
238
  paddingBottom: 16,
237
239
  },
238
- scroll: { flex: 1 },
240
+ scroll: { flex: 1, minHeight: 0 },
239
241
  scrollContent: { paddingTop: 16, paddingBottom: 8 },
240
242
  title: { fontSize: 18, fontWeight: '700', marginBottom: 6 },
241
243
  subtitle: { fontSize: 14, opacity: 0.6, marginBottom: 16 },
@@ -123,6 +123,7 @@ const styles = StyleSheet.create({
123
123
  },
124
124
  optionsList: {
125
125
  flex: 1,
126
+ minHeight: 0,
126
127
  marginBottom: 12,
127
128
  },
128
129
  optionRow: {
@@ -1,5 +1,7 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import {
3
+ Animated,
4
+ Keyboard,
3
5
  View,
4
6
  Text,
5
7
  TouchableOpacity,
@@ -78,6 +80,13 @@ export const XeboSurveyModal: React.FC = () => {
78
80
  // Bottom inset: iOS home indicator (34) or Android nav bar height (min 16)
79
81
  const bottomInset = Platform.OS === 'ios' ? 34 : Math.max(16, _androidNavBar);
80
82
 
83
+ // Slides the sheet above the keyboard when any TextInput is focused.
84
+ // Using translateY keeps the layout stable — no height re-calculation needed.
85
+ const shiftAnim = useRef(new Animated.Value(0)).current;
86
+ // Ref so the keyboard listener can always read the latest sheetHeight without
87
+ // being re-registered every time the question changes.
88
+ const sheetHeightRef = useRef(0);
89
+
81
90
  const [visible, setVisible] = useState(false);
82
91
  const [survey, setSurvey] = useState<XeboSurvey | null>(null);
83
92
  const [screen, setScreen] = useState<ModalScreen>('intro');
@@ -87,6 +96,38 @@ export const XeboSurveyModal: React.FC = () => {
87
96
  () => computeSheetHeight(survey?.questions[questionIndex] ?? null, screen),
88
97
  [survey, questionIndex, screen]
89
98
  );
99
+ // Keep ref in sync so keyboard listener always has the latest height
100
+ sheetHeightRef.current = sheetHeight;
101
+
102
+ // ─── Keyboard: slide sheet above keyboard when any TextInput focuses ──────
103
+
104
+ useEffect(() => {
105
+ const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
106
+ const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
107
+
108
+ const onShow = (e: { endCoordinates: { height: number } }) => {
109
+ const kh = e.endCoordinates.height;
110
+ // Cap so the sheet never slides above a 24px top margin
111
+ const maxShift = Math.max(0, SCREEN_HEIGHT - sheetHeightRef.current - 24);
112
+ Animated.timing(shiftAnim, {
113
+ toValue: -Math.min(kh, maxShift),
114
+ duration: Platform.OS === 'ios' ? 250 : 0,
115
+ useNativeDriver: true,
116
+ }).start();
117
+ };
118
+
119
+ const onHide = () => {
120
+ Animated.timing(shiftAnim, {
121
+ toValue: 0,
122
+ duration: Platform.OS === 'ios' ? 200 : 0,
123
+ useNativeDriver: true,
124
+ }).start();
125
+ };
126
+
127
+ const showSub = Keyboard.addListener(showEvent, onShow);
128
+ const hideSub = Keyboard.addListener(hideEvent, onHide);
129
+ return () => { showSub.remove(); hideSub.remove(); };
130
+ }, []); // mount-only — sheetHeightRef keeps the height current without re-subscribing
90
131
 
91
132
  // ─── Subscribe to manager events ─────────────────────────────────────────
92
133
 
@@ -224,14 +265,18 @@ export const XeboSurveyModal: React.FC = () => {
224
265
  hideModalContentWhileAnimating={true}
225
266
  coverScreen={true}
226
267
  propagateSwipe={false}
227
- onModalHide={() => setSurvey(null)}
268
+ onModalHide={() => {
269
+ setSurvey(null);
270
+ shiftAnim.setValue(0); // reset in case keyboard was open when modal closed
271
+ }}
228
272
  >
229
- <View
273
+ <Animated.View
230
274
  style={[
231
275
  styles.sheet,
232
276
  {
233
277
  backgroundColor: theme.backgroundColor,
234
278
  height: sheetHeight,
279
+ transform: [{ translateY: shiftAnim }],
235
280
  },
236
281
  ]}
237
282
  >
@@ -254,7 +299,7 @@ export const XeboSurveyModal: React.FC = () => {
254
299
 
255
300
  {/* Safe area bottom padding — home indicator on iOS, gesture nav on Android */}
256
301
  <View style={{ height: bottomInset }} />
257
- </View>
302
+ </Animated.View>
258
303
  </RNModal>
259
304
  );
260
305
  };
@@ -1,4 +1,4 @@
1
- import React, { useRef, useState, useEffect } from 'react';
1
+ import React, { useRef, useState } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
@@ -8,7 +8,6 @@ import {
8
8
  ScrollView,
9
9
  Animated,
10
10
  Keyboard,
11
- Platform,
12
11
  } from 'react-native';
13
12
  import { XeboQuestion, XeboAnswer, XeboQuestionType } from '../models/XeboModels';
14
13
  import { getTheme } from '../theme/XeboTheme';
@@ -26,21 +25,9 @@ export const XeboTextBoxView: React.FC<Props> = ({ question, isLastQuestion, onA
26
25
 
27
26
  const [values, setValues] = useState<Record<string, string>>({});
28
27
  const [error, setError] = useState('');
29
- const [keyboardHeight, setKeyboardHeight] = useState(0);
30
28
  const shakeAnim = useRef(new Animated.Value(0)).current;
31
29
  const scrollRef = useRef<ScrollView>(null);
32
30
 
33
- // Track keyboard height so the outer container can push button above keyboard.
34
- // KAV does not work reliably inside react-native-modal on Android — this is the
35
- // proven alternative: increase paddingBottom by keyboard height so content shifts up.
36
- useEffect(() => {
37
- const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
38
- const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
39
- const showSub = Keyboard.addListener(showEvent, (e: { endCoordinates: { height: number } }) => setKeyboardHeight(e.endCoordinates.height));
40
- const hideSub = Keyboard.addListener(hideEvent, () => setKeyboardHeight(0));
41
- return () => { showSub.remove(); hideSub.remove(); };
42
- }, []);
43
-
44
31
  // Scroll so the focused field is visible above the button when keyboard is up
45
32
  const handleFocus = (idx: number) => {
46
33
  setTimeout(() => {
@@ -72,6 +59,7 @@ export const XeboTextBoxView: React.FC<Props> = ({ question, isLastQuestion, onA
72
59
  }
73
60
  }
74
61
 
62
+ Keyboard.dismiss();
75
63
  onAnswer({
76
64
  questionId: question.id,
77
65
  value: fields
@@ -81,9 +69,7 @@ export const XeboTextBoxView: React.FC<Props> = ({ question, isLastQuestion, onA
81
69
  };
82
70
 
83
71
  return (
84
- // paddingBottom grows to keyboard height so the button is always above the keyboard.
85
- // The ScrollView above it shrinks accordingly; onFocus scrolls the active field into view.
86
- <View style={[styles.outer, { paddingBottom: keyboardHeight > 0 ? keyboardHeight + 8 : 16 }]}>
72
+ <View style={styles.outer}>
87
73
  <ScrollView
88
74
  ref={scrollRef}
89
75
  style={styles.scroll}
@@ -156,6 +142,7 @@ const styles = StyleSheet.create({
156
142
  },
157
143
  scroll: {
158
144
  flex: 1,
145
+ minHeight: 0,
159
146
  },
160
147
  scrollContent: {
161
148
  paddingTop: 16,
@@ -342,9 +342,9 @@ export async function submitResponse(
342
342
  questions: filteredAnswers.map(a => ({
343
343
  uuid: a.questionId,
344
344
  answer: a.value.map(v => ({
345
- row_id: v.rowId,
346
- col_id: v.colId ?? '',
347
- text: v.text ?? '',
345
+ row_id: v.rowId || null,
346
+ col_id: v.colId || null,
347
+ text: v.text || null,
348
348
  })),
349
349
  })),
350
350
  },