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 +1 -1
- package/src/components/XeboIntroView.tsx +1 -1
- package/src/components/XeboMultiNPSView.tsx +1 -1
- package/src/components/XeboMultiRatingView.tsx +1 -1
- package/src/components/XeboMultipleChoiceView.tsx +1 -0
- package/src/components/XeboNPSView.tsx +3 -1
- package/src/components/XeboRatingView.tsx +3 -1
- package/src/components/XeboSingleChoiceView.tsx +1 -0
- package/src/components/XeboSurveyModal.tsx +49 -4
- package/src/components/XeboTextBoxView.tsx +4 -17
- package/src/core/XeboNetworkService.ts +3 -3
package/package.json
CHANGED
|
@@ -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' },
|
|
@@ -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 },
|
|
@@ -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={() =>
|
|
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
|
|
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
|
-
|
|
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
|
},
|