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 +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 -5
- package/src/components/XeboTextBoxView.tsx +13 -2
- 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,15 +265,18 @@ export const XeboSurveyModal: React.FC = () => {
|
|
|
224
265
|
hideModalContentWhileAnimating={true}
|
|
225
266
|
coverScreen={true}
|
|
226
267
|
propagateSwipe={false}
|
|
227
|
-
|
|
228
|
-
|
|
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
|
},
|