react-native-my-survey-sdk 2.2.3 → 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.3",
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,15 +265,18 @@ export const XeboSurveyModal: React.FC = () => {
224
265
  hideModalContentWhileAnimating={true}
225
266
  coverScreen={true}
226
267
  propagateSwipe={false}
227
- avoidKeyboard={true}
228
- onModalHide={() => setSurvey(null)}
268
+ onModalHide={() => {
269
+ setSurvey(null);
270
+ shiftAnim.setValue(0); // reset in case keyboard was open when modal closed
271
+ }}
229
272
  >
230
- <View
273
+ <Animated.View
231
274
  style={[
232
275
  styles.sheet,
233
276
  {
234
277
  backgroundColor: theme.backgroundColor,
235
278
  height: sheetHeight,
279
+ transform: [{ translateY: shiftAnim }],
236
280
  },
237
281
  ]}
238
282
  >
@@ -255,7 +299,7 @@ export const XeboSurveyModal: React.FC = () => {
255
299
 
256
300
  {/* Safe area bottom padding — home indicator on iOS, gesture nav on Android */}
257
301
  <View style={{ height: bottomInset }} />
258
- </View>
302
+ </Animated.View>
259
303
  </RNModal>
260
304
  );
261
305
  };
@@ -7,6 +7,7 @@ import {
7
7
  StyleSheet,
8
8
  ScrollView,
9
9
  Animated,
10
+ Keyboard,
10
11
  } from 'react-native';
11
12
  import { XeboQuestion, XeboAnswer, XeboQuestionType } from '../models/XeboModels';
12
13
  import { getTheme } from '../theme/XeboTheme';
@@ -25,6 +26,14 @@ export const XeboTextBoxView: React.FC<Props> = ({ question, isLastQuestion, onA
25
26
  const [values, setValues] = useState<Record<string, string>>({});
26
27
  const [error, setError] = useState('');
27
28
  const shakeAnim = useRef(new Animated.Value(0)).current;
29
+ const scrollRef = useRef<ScrollView>(null);
30
+
31
+ // Scroll so the focused field is visible above the button when keyboard is up
32
+ const handleFocus = (idx: number) => {
33
+ setTimeout(() => {
34
+ scrollRef.current?.scrollTo({ y: idx * 80, animated: true });
35
+ }, 150);
36
+ };
28
37
 
29
38
  const shake = () => {
30
39
  Animated.sequence([
@@ -50,6 +59,7 @@ export const XeboTextBoxView: React.FC<Props> = ({ question, isLastQuestion, onA
50
59
  }
51
60
  }
52
61
 
62
+ Keyboard.dismiss();
53
63
  onAnswer({
54
64
  questionId: question.id,
55
65
  value: fields
@@ -59,10 +69,9 @@ export const XeboTextBoxView: React.FC<Props> = ({ question, isLastQuestion, onA
59
69
  };
60
70
 
61
71
  return (
62
- // Outer view fills modal content area; button pinned at bottom outside ScrollView.
63
- // react-native-modal's avoidKeyboard=true moves the whole modal up — no KAV needed.
64
72
  <View style={styles.outer}>
65
73
  <ScrollView
74
+ ref={scrollRef}
66
75
  style={styles.scroll}
67
76
  contentContainerStyle={styles.scrollContent}
68
77
  keyboardShouldPersistTaps="handled"
@@ -98,6 +107,7 @@ export const XeboTextBoxView: React.FC<Props> = ({ question, isLastQuestion, onA
98
107
  placeholderTextColor="#9CA3AF"
99
108
  value={values[field.id] ?? ''}
100
109
  onChangeText={text => handleChange(field.id, text)}
110
+ onFocus={() => handleFocus(idx)}
101
111
  multiline={!isMulti}
102
112
  numberOfLines={isMulti ? 1 : 4}
103
113
  textAlignVertical={isMulti ? 'center' : 'top'}
@@ -132,6 +142,7 @@ const styles = StyleSheet.create({
132
142
  },
133
143
  scroll: {
134
144
  flex: 1,
145
+ minHeight: 0,
135
146
  },
136
147
  scrollContent: {
137
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
  },