@cxpinsight/react-native 0.21.0 → 0.23.0

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/lib/CXPinsight.d.ts +7 -2
  3. package/lib/CXPinsight.d.ts.map +1 -1
  4. package/lib/CXPinsight.js +32 -2
  5. package/lib/CXPinsight.js.map +1 -1
  6. package/lib/CXPinsightProvider.d.ts.map +1 -1
  7. package/lib/CXPinsightProvider.js +3 -0
  8. package/lib/CXPinsightProvider.js.map +1 -1
  9. package/lib/core/firebaseBridge.d.ts +49 -0
  10. package/lib/core/firebaseBridge.d.ts.map +1 -0
  11. package/lib/core/firebaseBridge.js +158 -0
  12. package/lib/core/firebaseBridge.js.map +1 -0
  13. package/lib/core/mobileRecorder.d.ts.map +1 -1
  14. package/lib/core/mobileRecorder.js +23 -1
  15. package/lib/core/mobileRecorder.js.map +1 -1
  16. package/lib/core/networkCapture.d.ts.map +1 -1
  17. package/lib/core/networkCapture.js +9 -4
  18. package/lib/core/networkCapture.js.map +1 -1
  19. package/lib/core/triggerEngine.d.ts.map +1 -1
  20. package/lib/core/triggerEngine.js +10 -3
  21. package/lib/core/triggerEngine.js.map +1 -1
  22. package/lib/survey/SurveyModal.d.ts +4 -1
  23. package/lib/survey/SurveyModal.d.ts.map +1 -1
  24. package/lib/survey/SurveyModal.js +36 -15
  25. package/lib/survey/SurveyModal.js.map +1 -1
  26. package/lib/survey/questions/CSATQuestion.js +1 -1
  27. package/lib/survey/questions/CSATQuestion.js.map +1 -1
  28. package/lib/survey/questions/MultipleChoiceQuestion.d.ts +14 -2
  29. package/lib/survey/questions/MultipleChoiceQuestion.d.ts.map +1 -1
  30. package/lib/survey/questions/MultipleChoiceQuestion.js +67 -7
  31. package/lib/survey/questions/MultipleChoiceQuestion.js.map +1 -1
  32. package/lib/survey/questions/RatingQuestion.d.ts +30 -2
  33. package/lib/survey/questions/RatingQuestion.d.ts.map +1 -1
  34. package/lib/survey/questions/RatingQuestion.js +127 -16
  35. package/lib/survey/questions/RatingQuestion.js.map +1 -1
  36. package/lib/survey/styleContract.d.ts +25 -0
  37. package/lib/survey/styleContract.d.ts.map +1 -1
  38. package/lib/survey/styleContract.js +37 -0
  39. package/lib/survey/styleContract.js.map +1 -1
  40. package/lib/survey/validate.d.ts +2 -1
  41. package/lib/survey/validate.d.ts.map +1 -1
  42. package/lib/survey/validate.js +3 -2
  43. package/lib/survey/validate.js.map +1 -1
  44. package/lib/types.d.ts +16 -0
  45. package/lib/types.d.ts.map +1 -1
  46. package/lib/types.js.map +1 -1
  47. package/lib/version.d.ts +1 -1
  48. package/lib/version.js +1 -1
  49. package/package.json +7 -3
@@ -49,7 +49,6 @@ const react_native_1 = require("react-native");
49
49
  const types_1 = require("../types");
50
50
  const storage = __importStar(require("../core/storage"));
51
51
  const NPSQuestion_1 = require("./questions/NPSQuestion");
52
- const CSATQuestion_1 = require("./questions/CSATQuestion");
53
52
  const TextQuestion_1 = require("./questions/TextQuestion");
54
53
  const MultipleChoiceQuestion_1 = require("./questions/MultipleChoiceQuestion");
55
54
  const RankingQuestion_1 = require("./questions/RankingQuestion");
@@ -59,6 +58,7 @@ const ImagePickerQuestion_1 = require("./questions/ImagePickerQuestion");
59
58
  const MatrixDropdownQuestion_1 = require("./questions/MatrixDropdownQuestion");
60
59
  const MatrixDynamicQuestion_1 = require("./questions/MatrixDynamicQuestion");
61
60
  const PanelDynamicQuestion_1 = require("./questions/PanelDynamicQuestion");
61
+ const RatingQuestion_1 = require("./questions/RatingQuestion");
62
62
  const DateQuestion_1 = require("./questions/DateQuestion");
63
63
  const survey_spec_1 = require("@cxpinsight/survey-spec");
64
64
  const mobileRecorder_1 = require("../core/mobileRecorder");
@@ -502,7 +502,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss, reply = null }) {
502
502
  if (!q)
503
503
  return prev;
504
504
  // Same rule as the page pass: a locked question raises no error.
505
- const err = isEnabled(q) ? (0, validate_1.validateAnswer)(forValidation(q), value) : null;
505
+ const err = isEnabled(q) ? (0, validate_1.validateAnswer)(forValidation(q), value, currentLanguage) : null;
506
506
  if (err)
507
507
  return prev; // still invalid → keep the error
508
508
  const { [name]: _dropped, ...rest } = prev;
@@ -598,7 +598,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss, reply = null }) {
598
598
  if (!isEnabled(q))
599
599
  continue;
600
600
  const k = q.id || q.name;
601
- const err = (0, validate_1.validateAnswer)(forValidation(q), responses[k]);
601
+ const err = (0, validate_1.validateAnswer)(forValidation(q), responses[k], currentLanguage);
602
602
  if (err)
603
603
  out[k] = err;
604
604
  }
@@ -609,7 +609,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss, reply = null }) {
609
609
  // server rejection (survey deactivated, cap hit) surfaces a retry
610
610
  // message instead of a false thank-you.
611
611
  const submitAndReset = async () => {
612
- const ok = await onSubmit(buildSubmitPayload(), { via: viaRef.current });
612
+ const ok = await onSubmit(buildSubmitPayload(), { via: viaRef.current, language: currentLanguage || survey.defaultLanguage || undefined });
613
613
  if (ok === false) {
614
614
  setSubmitError('We couldn’t submit your response. Please try again.');
615
615
  return; // keep the survey open for a retry
@@ -949,7 +949,18 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss, reply = null }) {
949
949
  return ((0, jsx_runtime_1.jsx)(NPSQuestion_1.NPSQuestion, { value: val ?? null, onChange: (v) => updateResponse(key, v), contract: contract }));
950
950
  },
951
951
  'rating': () => {
952
- return ((0, jsx_runtime_1.jsx)(CSATQuestion_1.CSATQuestion, { value: val ?? null, onChange: (v) => updateResponse(key, v), maxStars: question.maxValue ?? question.rateMax ?? 5, contract: contract }));
952
+ // ONE scale for every ratingType. This drew CSATQuestion — stars only,
953
+ // always from 1 — so a number, smiley or thumb scale, a 0-based range
954
+ // and the end captions all rendered as five stars on the phone. The
955
+ // keys and the aliases are the web SDK's (_cxpQ_rating): minValue /
956
+ // maxValue (v1.1, also what render.props min/max become above), then
957
+ // the SurveyJS-era rateMin / rateMax; rateLabels' ends win as captions.
958
+ const qx = question;
959
+ const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : (typeof v === 'string' && v.trim() !== '' && Number.isFinite(Number(v)) ? Number(v) : undefined));
960
+ const labels = Array.isArray(qx.rateLabels) ? qx.rateLabels : [];
961
+ return ((0, jsx_runtime_1.jsx)(RatingQuestion_1.RatingQuestion, { value: val ?? null, onChange: (v) => updateResponse(key, v), rateMin: num(qx.minValue) ?? num(qx.rateMin) ?? 1, rateMax: num(qx.maxValue) ?? num(qx.rateMax) ?? 5,
962
+ // Unset is a star — the web SDK's default, and what this always drew.
963
+ ratingType: typeof qx.ratingType === 'string' && qx.ratingType ? qx.ratingType : 'star', minLabel: labels.length ? labels[0] : (qx.minLabel || qx.minRateDescription), maxLabel: labels.length ? labels[labels.length - 1] : (qx.maxLabel || qx.maxRateDescription), contract: contract, explicit: explicitFields }));
953
964
  },
954
965
  'csat': () => RENDERERS['rating'](),
955
966
  'ces': () => RENDERERS['rating'](),
@@ -968,18 +979,18 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss, reply = null }) {
968
979
  'email': () => RENDERERS['text'](),
969
980
  'number': () => RENDERERS['text'](),
970
981
  'radio': () => {
971
- return ((0, jsx_runtime_1.jsx)(MultipleChoiceQuestion_1.MultipleChoiceQuestion, { choices: normalizeChoices(question), value: val ?? '', onChange: (v) => updateResponse(key, v), mode: "radiogroup", isRtl: isRtl, contract: contract }));
982
+ return ((0, jsx_runtime_1.jsx)(MultipleChoiceQuestion_1.MultipleChoiceQuestion, { choices: normalizeChoices(question), value: val ?? '', onChange: (v) => updateResponse(key, v), mode: "radiogroup", isRtl: isRtl, contract: contract, optionLayout: question.optionLayout, explicit: explicitFields }));
972
983
  },
973
984
  'radiogroup': () => RENDERERS['radio'](),
974
985
  'dropdown': () => RENDERERS['radio'](),
975
986
  'checkbox': () => {
976
- return ((0, jsx_runtime_1.jsx)(MultipleChoiceQuestion_1.MultipleChoiceQuestion, { choices: normalizeChoices(question), value: val ?? [], onChange: (v) => updateResponse(key, v), mode: "checkbox", isRtl: isRtl, contract: contract }));
987
+ return ((0, jsx_runtime_1.jsx)(MultipleChoiceQuestion_1.MultipleChoiceQuestion, { choices: normalizeChoices(question), value: val ?? [], onChange: (v) => updateResponse(key, v), mode: "checkbox", isRtl: isRtl, contract: contract, optionLayout: question.optionLayout, explicit: explicitFields }));
977
988
  },
978
989
  'binary': () => {
979
990
  return ((0, jsx_runtime_1.jsx)(MultipleChoiceQuestion_1.MultipleChoiceQuestion, { choices: [
980
- { value: 'true', text: question.yesLabel || 'Yes' },
981
- { value: 'false', text: question.noLabel || 'No' },
982
- ], value: val ?? '', onChange: (v) => updateResponse(key, v), mode: "radiogroup", isRtl: isRtl, contract: contract }));
991
+ { value: 'true', text: question.yesLabel || (0, survey_spec_1.chromeWord)('yes', currentLanguage) },
992
+ { value: 'false', text: question.noLabel || (0, survey_spec_1.chromeWord)('no', currentLanguage) },
993
+ ], value: val ?? '', onChange: (v) => updateResponse(key, v), mode: "radiogroup", isRtl: isRtl, contract: contract, hideIndicator: !(0, survey_spec_1.showsYesNoIndicator)(display) }));
983
994
  },
984
995
  'boolean': () => RENDERERS['binary'](),
985
996
  'yesno': () => RENDERERS['binary'](),
@@ -1561,6 +1572,12 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss, reply = null }) {
1561
1572
  [resolvedTheme, primaryColor,
1562
1573
  JSON.stringify(surveyPaletteOverride), JSON.stringify(initPaletteOverride),
1563
1574
  JSON.stringify(survey.branding?.style)]);
1575
+ // The fields set BY NAME, same layers and order as the contract above
1576
+ // (palette < style slice < host init). Answer surfaces that had no box or
1577
+ // shape before — a bare star, a chip — read these, so a value the contract
1578
+ // only holds as a default cannot redraw them.
1579
+ const explicitFields = (0, react_2.useMemo)(() => (0, styleContract_1.explicitContractFields)(surveyPaletteOverride, stylePlatformSlice(survey.branding?.style), initPaletteOverride), [JSON.stringify(surveyPaletteOverride), JSON.stringify(initPaletteOverride),
1580
+ JSON.stringify(survey.branding?.style)]);
1564
1581
  const styles = (0, react_2.useMemo)(() => makeStyles(contract), [contract]);
1565
1582
  // A picture band outside the header keeps the CARD'S padding. padBand exists only when a density
1566
1583
  // is set; without one the band had no horizontal padding at all and ran edge to edge, like
@@ -2103,6 +2120,10 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss, reply = null }) {
2103
2120
  // plain merge that leaves the value undefined — never enabled the swipe
2104
2121
  // either. One authored survey, dismissable two different ways depending on
2105
2122
  // which screen you were looking at.
2123
+ // The headline and body switches — the same rule the web card and the link
2124
+ // page ask (survey-spec showsTitle / showsDescription).
2125
+ const titleShown = (0, survey_spec_1.showsTitle)(display);
2126
+ const descriptionShown = (0, survey_spec_1.showsDescription)(display);
2106
2127
  const closeShown = (0, survey_spec_1.showsClose)(display, interactionKind, survey.delivery);
2107
2128
  const closeReserve = !closeShown ? null : {
2108
2129
  [isRtl ? 'paddingLeft' : 'paddingRight']: contract.closeSize + contract.closeInset + 8,
@@ -2885,13 +2906,13 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss, reply = null }) {
2885
2906
  // it belongs to unless the author says otherwise.
2886
2907
  alignSelf: imgD.align === 'start' ? 'flex-start'
2887
2908
  : imgD.align === 'end' ? 'flex-end' : 'center',
2888
- }, resizeMode: imgD.fit })) : null, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: (besideImage || notifyBeside) ? { flex: 1, minWidth: 0 } : undefined, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.title, dirText, fontStyle, themeTextPrimary, titleFS, notifyTitleFS,
2909
+ }, resizeMode: imgD.fit })) : null, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: (besideImage || notifyBeside) ? { flex: 1, minWidth: 0 } : undefined, children: [titleShown ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.title, dirText, fontStyle, themeTextPrimary, titleFS, notifyTitleFS,
2889
2910
  closeReserve,
2890
2911
  { textAlign: cardTitleAlign }], children: (0, interpolate_1.interpolatePlain)(
2891
2912
  // Title is plain text; strip any stray rich-text markup +
2892
2913
  // decode &nbsp; so it never shows literal HTML on native.
2893
2914
  String((0, translationLookup_1.surveyText)(survey, currentLanguage, 'title', survey.title) || '')
2894
- .replace(/<[^>]*>/g, '').replace(/&nbsp;/gi, ' '), evalCtx) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: notifyBeside ? {
2915
+ .replace(/<[^>]*>/g, '').replace(/&nbsp;/gi, ' '), evalCtx) })) : null, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: notifyBeside ? {
2895
2916
  flexDirection: 'row',
2896
2917
  alignItems: 'center',
2897
2918
  justifyContent: 'space-between',
@@ -2900,7 +2921,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss, reply = null }) {
2900
2921
  // markup, so strip tags + decode &nbsp; to plain text.
2901
2922
  const descRaw = (0, translationLookup_1.surveyText)(survey, currentLanguage, 'description', survey.description);
2902
2923
  const desc = String(descRaw || '').replace(/<[^>]*>/g, '').replace(/&nbsp;/gi, ' ').trim();
2903
- return desc ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.description, dirText, fontStyle, themeTextSecondary, descFS, notifyDescFS,
2924
+ return desc && descriptionShown ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.description, dirText, fontStyle, themeTextSecondary, descFS, notifyDescFS,
2904
2925
  { textAlign: cardDescAlign },
2905
2926
  // Takes the width the button leaves, and no more.
2906
2927
  notifyBeside && { flex: 1, minWidth: 0, marginTop: 2 }], children: (0, interpolate_1.interpolatePlain)(desc, evalCtx) })) : null;
@@ -2996,7 +3017,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss, reply = null }) {
2996
3017
  // Right-align parity with web: Back hugs the left edge while
2997
3018
  // Next/Submit sits on the right (web uses margin-right:auto).
2998
3019
  buttonAlign === 'right' && { marginRight: 'auto' },
2999
- ], onPress: handleBack, activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.backButtonText, fontStyle, themeTextSecondary, buttonFS], children: "Back" }) })), impliedNow.length > 0 || !(0, survey_spec_1.drawsOwnSubmit)(survey.interactionType) ? null : (() => {
3020
+ ], onPress: handleBack, activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.backButtonText, fontStyle, themeTextSecondary, buttonFS], children: (0, survey_spec_1.chromeWord)('back', currentLanguage) }) })), impliedNow.length > 0 || !(0, survey_spec_1.drawsOwnSubmit)(survey.interactionType) ? null : (() => {
3000
3021
  // Reflect the actual outcome of pressing Next: if a
3001
3022
  // branch resolves to __end__ we're submitting even
3002
3023
  // when we're not on the last page. Cheap to evaluate
@@ -3014,7 +3035,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss, reply = null }) {
3014
3035
  // explicitly 'full'. When the author picked right/center/left,
3015
3036
  // respect it even on the first page (was forced full-width,
3016
3037
  // so mobile ignored the setting on page 1 — unlike web).
3017
- style: [styles.nextButton, ((isSingleButton && !alignsAuto) || buttonAlign === 'full') && styles.nextButtonFull, btnAutoWidth, { backgroundColor: primaryColor }], onPress: handleNext, activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.nextButtonText, fontStyle, buttonFS], children: willSubmit ? 'Submit' : 'Next' }) }));
3038
+ style: [styles.nextButton, ((isSingleButton && !alignsAuto) || buttonAlign === 'full') && styles.nextButtonFull, btnAutoWidth, { backgroundColor: primaryColor }], onPress: handleNext, activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.nextButtonText, fontStyle, buttonFS], children: willSubmit ? (0, survey_spec_1.chromeWord)('submit', currentLanguage) : (0, survey_spec_1.chromeWord)('next', currentLanguage) }) }));
3018
3039
  })()] })] })] }))] }), (0, jsx_runtime_1.jsx)(handsFreeVoice_1.VoiceOverlay, { visible: voiceActive, phase: voicePhase, transcript: voiceTranscript, onStop: () => (0, handsFreeVoice_1.stopVoice)() })] }) }));
3019
3040
  }
3020
3041
  const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = react_native_1.Dimensions.get('window');