@cxpinsight/react-native 0.18.4 → 0.19.1
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/CHANGELOG.md +41 -0
- package/lib/CXPinsight.d.ts.map +1 -1
- package/lib/CXPinsight.js +2 -16
- package/lib/CXPinsight.js.map +1 -1
- package/lib/CXPinsightProvider.d.ts.map +1 -1
- package/lib/CXPinsightProvider.js +168 -5
- package/lib/CXPinsightProvider.js.map +1 -1
- package/lib/core/mobileRecorder.d.ts +0 -1
- package/lib/core/mobileRecorder.d.ts.map +1 -1
- package/lib/core/mobileRecorder.js +45 -53
- package/lib/core/mobileRecorder.js.map +1 -1
- package/lib/survey/SurveyModal.d.ts +7 -2
- package/lib/survey/SurveyModal.d.ts.map +1 -1
- package/lib/survey/SurveyModal.js +295 -56
- package/lib/survey/SurveyModal.js.map +1 -1
- package/lib/survey/questions/MultipleChoiceQuestion.d.ts.map +1 -1
- package/lib/survey/questions/MultipleChoiceQuestion.js +7 -1
- package/lib/survey/questions/MultipleChoiceQuestion.js.map +1 -1
- package/lib/types.d.ts +20 -6
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -2
|
@@ -239,7 +239,7 @@ function swipeRelease(g) {
|
|
|
239
239
|
function surfaceScrolls(interactionKind) {
|
|
240
240
|
return String(interactionKind || '').toLowerCase() !== 'notify';
|
|
241
241
|
}
|
|
242
|
-
function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
242
|
+
function SurveyModal({ survey, visible, onSubmit, onDismiss, reply = null }) {
|
|
243
243
|
const [currentPage, setCurrentPage] = (0, react_2.useState)(0);
|
|
244
244
|
const [responses, setResponses] = (0, react_2.useState)({});
|
|
245
245
|
const [showThankYou, setShowThankYou] = (0, react_2.useState)(false);
|
|
@@ -609,7 +609,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
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());
|
|
612
|
+
const ok = await onSubmit(buildSubmitPayload(), { via: viaRef.current });
|
|
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
|
|
@@ -621,6 +621,15 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
621
621
|
if (String(survey.interactionType || '')
|
|
622
622
|
.toLowerCase() === 'notify')
|
|
623
623
|
return;
|
|
624
|
+
// AND WHEN THE AUTHOR TURNED THE ENDING OFF.
|
|
625
|
+
//
|
|
626
|
+
// showsConfirmation gated the OUTCOME panel below and not this state, so
|
|
627
|
+
// switching the ending off did not remove the ending — it fell through to
|
|
628
|
+
// the generic "Thank you" instead, which is a worse result than leaving it
|
|
629
|
+
// alone: the author lost the reward panel and gained a line they never
|
|
630
|
+
// wrote. Skipped here, so nothing draws and the 2.5s window never opens.
|
|
631
|
+
if (!(0, survey_spec_1.showsConfirmation)(survey))
|
|
632
|
+
return;
|
|
624
633
|
setShowThankYou(true);
|
|
625
634
|
const duration = survey.settings?.thankYouDuration ?? 2500;
|
|
626
635
|
setTimeout(() => {
|
|
@@ -750,7 +759,19 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
750
759
|
* the interaction at all, because unlike web the card here was never
|
|
751
760
|
* pressable in the first place. Same defect, one step worse.
|
|
752
761
|
*/
|
|
753
|
-
|
|
762
|
+
// WHICH SURFACE WAS PRESSED — 'button' or 'card'.
|
|
763
|
+
//
|
|
764
|
+
// Both routes end in pressImplied, so by the time the answer is built the
|
|
765
|
+
// two are the same choice and nothing downstream can tell them apart. Web
|
|
766
|
+
// has carried this since bf0c4b55 (metadata.via); mobile gained whole-card
|
|
767
|
+
// press without it, so on the platform where nearly all the traffic is, the
|
|
768
|
+
// builder's "Clicked the card" outcome could never be populated.
|
|
769
|
+
//
|
|
770
|
+
// A ref, not state: it is read during the submit that the press itself
|
|
771
|
+
// triggers, and a re-render in between would be a wasted frame.
|
|
772
|
+
const viaRef = (0, react_2.useRef)('button');
|
|
773
|
+
const pressImplied = (0, react_2.useCallback)((key, a, via = 'button') => {
|
|
774
|
+
viaRef.current = via;
|
|
754
775
|
// "Remind me later" dismisses instead of answering, so the cadence records
|
|
755
776
|
// a SKIP and brings the interaction back after the author's wait.
|
|
756
777
|
// Answering would record a decision this person explicitly declined to
|
|
@@ -858,22 +879,16 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
858
879
|
// MultipleChoiceQuestion accepts. hasOther appends a synthetic
|
|
859
880
|
// "Other" entry — full free-text follow-up UX lands in Phase 4.5.
|
|
860
881
|
function normalizeChoices(question) {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
if (typeof c === 'string')
|
|
872
|
-
out.push({ value: c, text: c });
|
|
873
|
-
else if (c)
|
|
874
|
-
out.push({ value: String(c.value ?? ''), text: String(c.label ?? c.text ?? c.value ?? '') });
|
|
875
|
-
}
|
|
876
|
-
}
|
|
882
|
+
// WHICHEVER FIELD HAS SOMETHING IN IT — see questionChoices.
|
|
883
|
+
//
|
|
884
|
+
// This asked `Array.isArray(options)` first and took that branch. The
|
|
885
|
+
// server's schema declares `options`, so Mongoose defaults it to `[]` on
|
|
886
|
+
// every question: a question authored with `choices` arrived carrying an
|
|
887
|
+
// empty `options` beside it, this took the empty branch, and the phone
|
|
888
|
+
// drew a choice question with no choices at all. The web preview read
|
|
889
|
+
// `choices` first and was fine, so it looked like a mobile rendering bug
|
|
890
|
+
// and was a field-precedence one.
|
|
891
|
+
const out = (0, survey_spec_1.questionChoices)(question);
|
|
877
892
|
if (question.hasOther) {
|
|
878
893
|
out.push({ value: '__other__', text: question.otherLabel || 'Other' });
|
|
879
894
|
}
|
|
@@ -1074,7 +1089,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1074
1089
|
// The press is both the answer and the Next — the footer is
|
|
1075
1090
|
// hidden on this step. Its body is pressImplied, so the CARD
|
|
1076
1091
|
// can do exactly the same thing when this button is not drawn.
|
|
1077
|
-
onPress: () => pressImplied(key, a), accessibilityRole: "button", accessibilityState: { selected: val === a.value }, style: [
|
|
1092
|
+
onPress: () => pressImplied(key, a, 'button'), accessibilityRole: "button", accessibilityState: { selected: val === a.value }, style: [
|
|
1078
1093
|
styles.impliedBtn,
|
|
1079
1094
|
// BESIDE THE TEXT, THE BUTTON IS AS WIDE AS ITS LABEL.
|
|
1080
1095
|
//
|
|
@@ -1371,7 +1386,6 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1371
1386
|
const fs = baseScale !== 1;
|
|
1372
1387
|
const qTitleFS = fs ? { fontSize: scaled(15) } : undefined;
|
|
1373
1388
|
const qDescFS = fs ? { fontSize: scaled(13) } : undefined;
|
|
1374
|
-
const thankYouFS = fs ? { fontSize: scaled(18) } : undefined;
|
|
1375
1389
|
const buttonFS = fs ? { fontSize: scaled(15) } : undefined;
|
|
1376
1390
|
// Layout tokens (spec §24). String presets map to concrete pixel
|
|
1377
1391
|
// values; a number passes through verbatim so authors can set
|
|
@@ -1540,6 +1554,16 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1540
1554
|
JSON.stringify(surveyPaletteOverride), JSON.stringify(initPaletteOverride),
|
|
1541
1555
|
JSON.stringify(survey.branding?.style)]);
|
|
1542
1556
|
const styles = (0, react_2.useMemo)(() => makeStyles(contract), [contract]);
|
|
1557
|
+
// A picture band outside the header keeps the CARD'S padding. padBand exists only when a density
|
|
1558
|
+
// is set; without one the band had no horizontal padding at all and ran edge to edge, like
|
|
1559
|
+
// "Half the card", while the header and body below it kept theirs (styles.body carries the
|
|
1560
|
+
// contract's padding either way).
|
|
1561
|
+
const bandPad = (0, react_2.useMemo)(() => {
|
|
1562
|
+
if (padBand)
|
|
1563
|
+
return padBand;
|
|
1564
|
+
const b = react_native_1.StyleSheet.flatten(styles.body) || {};
|
|
1565
|
+
return { paddingLeft: b.paddingLeft, paddingRight: b.paddingRight };
|
|
1566
|
+
}, [padBand, styles]);
|
|
1543
1567
|
const themeCard = isLight ? { backgroundColor: palette.card, borderColor: palette.cardBorder } : undefined;
|
|
1544
1568
|
const themeTextPrimary = isLight ? { color: palette.textPrimary } : undefined;
|
|
1545
1569
|
const themeTextSecondary = isLight ? { color: palette.textSecondary } : undefined;
|
|
@@ -1750,10 +1774,12 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1750
1774
|
* The alternative was a Fade control that worked in the preview and did
|
|
1751
1775
|
* nothing here, which is the thing this codebase keeps having to undo.
|
|
1752
1776
|
*/
|
|
1753
|
-
|
|
1754
|
-
|
|
1777
|
+
// `fade` is a parameter now, because there are two pictures asking: the
|
|
1778
|
+
// card's and the confirmation's, each with its own slider.
|
|
1779
|
+
const fadeSteps = (towards, fade = imgD.fade) => {
|
|
1780
|
+
if (!fade)
|
|
1755
1781
|
return null;
|
|
1756
|
-
const span = Math.max(2, Math.min(96,
|
|
1782
|
+
const span = Math.max(2, Math.min(96, fade)) / 100;
|
|
1757
1783
|
const vertical = towards === 'top' || towards === 'bottom';
|
|
1758
1784
|
// A REAL GRADIENT WHERE THE APP HAS ONE. react-native-svg is an optional
|
|
1759
1785
|
// peer, already loaded this way by the signature question, and it draws
|
|
@@ -1879,6 +1905,96 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1879
1905
|
const halfPadR = contract.cardPaddingRight ?? contract.cardPadding;
|
|
1880
1906
|
const halfOuter = imgD.side === 'right' ? halfPadR : halfPadL;
|
|
1881
1907
|
const halfWidth = Math.round(((cardWidth - halfPadL - halfPadR) * imgD.split) / 100) + halfOuter;
|
|
1908
|
+
/**
|
|
1909
|
+
* THE ENDING'S OWN PICTURE, which is not the card's.
|
|
1910
|
+
*
|
|
1911
|
+
* An offer's card sells the thing; its ending confirms it, and those want
|
|
1912
|
+
* different images far more often than the same one. It stands IN FOR the
|
|
1913
|
+
* ✓ glyph rather than sitting above it — a tick over a photograph is two
|
|
1914
|
+
* things saying "done", and the author chose the photograph.
|
|
1915
|
+
*/
|
|
1916
|
+
// The card's height, as it was just before the ending replaced it — a floor
|
|
1917
|
+
// for the ending, never a cap, so a taller ending can still grow.
|
|
1918
|
+
const cardHeightRef = (0, react_2.useRef)(0);
|
|
1919
|
+
// A FLOOR AND A FILL, MINUS THE CARD'S OWN PADDING.
|
|
1920
|
+
//
|
|
1921
|
+
// The floor alone left the panel at its natural height inside a taller card,
|
|
1922
|
+
// so a picture told to stretch beside the words took the confirmation's few
|
|
1923
|
+
// lines rather than the card's height. And the floor was the CARD's height
|
|
1924
|
+
// — the outer view, padding included — applied to a panel that sits inside
|
|
1925
|
+
// that padding, which asks the card to grow by twice its own inset every
|
|
1926
|
+
// time the ending appears. The web measures the CONTENT; this is the same
|
|
1927
|
+
// number, arrived at by subtraction because the card is what onLayout sees.
|
|
1928
|
+
const endPadTop = contract.cardPaddingTop ?? contract.cardPadding ?? 0;
|
|
1929
|
+
const endPadBottom = contract.cardPaddingBottom ?? contract.cardPadding ?? 0;
|
|
1930
|
+
const endingHold = cardHeightRef.current > 0
|
|
1931
|
+
? {
|
|
1932
|
+
minHeight: Math.max(0, cardHeightRef.current - endPadTop - endPadBottom),
|
|
1933
|
+
flexGrow: 1,
|
|
1934
|
+
}
|
|
1935
|
+
: null;
|
|
1936
|
+
// THE CONFIRMATION'S OWN DESIGN, inheriting the card's until it has one —
|
|
1937
|
+
// see confirmationDesign in the spec. It used to read `contract.imageHeight`
|
|
1938
|
+
// and `contract.imageRadius` directly, which tied the icon on this screen to
|
|
1939
|
+
// the photograph on the card: shrinking one shrank the other.
|
|
1940
|
+
// WHERE THE CARD'S WORDS SIT. `titleAlign` was already a survey field; the
|
|
1941
|
+
// other two are new and follow it, because a centred headline over a
|
|
1942
|
+
// left-ragged paragraph is the commonest way a card looks accidental.
|
|
1943
|
+
const cardTitleAlign = (0, survey_spec_1.alignOf)(survey.titleAlign, 'left');
|
|
1944
|
+
const cardDescAlign = (0, survey_spec_1.alignOf)(survey.descriptionAlign, cardTitleAlign);
|
|
1945
|
+
const cardQuestionAlign = (0, survey_spec_1.alignOf)(survey.questionAlign, 'left');
|
|
1946
|
+
const confirmD = (0, survey_spec_1.confirmationDesign)(survey, 'mobile');
|
|
1947
|
+
const endingImgSrc = confirmD.image.src;
|
|
1948
|
+
const endingAlign = confirmD.heading.align === 'start' ? 'flex-start'
|
|
1949
|
+
: confirmD.heading.align === 'end' ? 'flex-end' : 'center';
|
|
1950
|
+
const endingTextAlign = confirmD.heading.align === 'start' ? 'left'
|
|
1951
|
+
: confirmD.heading.align === 'end' ? 'right' : 'center';
|
|
1952
|
+
// LINE HEIGHT MOVES WITH THE SIZE. The stylesheet pins 26 and 20, so a
|
|
1953
|
+
// heading taken to 34 would have been clipped by its own leading.
|
|
1954
|
+
// scaled() FIRST, so the accessibility font scale still multiplies an
|
|
1955
|
+
// authored size — it used to scale a hard-coded 18 and the authored number
|
|
1956
|
+
// would have replaced it outright.
|
|
1957
|
+
const endingHeadSize = scaled(confirmD.heading.size);
|
|
1958
|
+
const endingSubSize = scaled(confirmD.body.size);
|
|
1959
|
+
const endingBodyAlign = confirmD.body.align === 'start' ? 'left'
|
|
1960
|
+
: confirmD.body.align === 'end' ? 'right' : 'center';
|
|
1961
|
+
const endingHeadFS = {
|
|
1962
|
+
fontSize: endingHeadSize,
|
|
1963
|
+
lineHeight: Math.round(endingHeadSize * 1.45),
|
|
1964
|
+
textAlign: endingTextAlign,
|
|
1965
|
+
};
|
|
1966
|
+
const endingSubFS = {
|
|
1967
|
+
fontSize: endingSubSize,
|
|
1968
|
+
lineHeight: Math.round(endingSubSize * confirmD.body.lineHeight),
|
|
1969
|
+
textAlign: endingBodyAlign,
|
|
1970
|
+
};
|
|
1971
|
+
// THE CARD'S WHOLE IMAGE VOCABULARY, on the second screen. It was a band and
|
|
1972
|
+
// nothing else — no Fade, no Dim, no Alignment, no Layout — because the
|
|
1973
|
+
// screen was built as a stack and the design was a hand-picked five fields
|
|
1974
|
+
// of the card's. confirmationDesign now answers with a full ImageDesign, so
|
|
1975
|
+
// there is one set of layouts and both screens draw from it.
|
|
1976
|
+
const cI = confirmD.image;
|
|
1977
|
+
const endingLayout = endingImgSrc ? cI.layout : 'none';
|
|
1978
|
+
const endingSelf = cI.align === 'start' ? 'flex-start'
|
|
1979
|
+
: cI.align === 'end' ? 'flex-end' : 'center';
|
|
1980
|
+
const endingScrim = cI.scrim ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "none", style: { position: 'absolute', top: 0, right: 0, bottom: 0, left: 0,
|
|
1981
|
+
backgroundColor: contract.card, opacity: cI.scrim / 100 } })) : null;
|
|
1982
|
+
const endingPicture = (fill) => ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: endingImgSrc }, style: [{ width: '100%', height: '100%' }, fill], resizeMode: cI.fit, accessibilityIgnoresInvertColors: true }));
|
|
1983
|
+
/** The band — the picture as a strip, at its own width and alignment. */
|
|
1984
|
+
const endingImage = (endingLayout === 'above' || endingLayout === 'below') ? ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: {
|
|
1985
|
+
position: 'relative', overflow: 'hidden',
|
|
1986
|
+
width: `${cI.width}%`, height: cI.height, borderRadius: cI.radius,
|
|
1987
|
+
alignSelf: endingSelf, marginBottom: endingLayout === 'above' ? 16 : 0,
|
|
1988
|
+
marginTop: endingLayout === 'below' ? 16 : 0,
|
|
1989
|
+
}, children: [endingPicture({}), fadeSteps(endingLayout === 'below' ? 'top' : 'bottom', cI.fade), endingScrim] })) : null;
|
|
1990
|
+
// THE SECOND PICTURE, between the card's and the headline. Square, so one
|
|
1991
|
+
// number sizes it — and it replaces the ✓ rather than sitting under it.
|
|
1992
|
+
const endingIcon = confirmD.icon.src ? ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: confirmD.icon.src }, style: {
|
|
1993
|
+
width: confirmD.icon.size,
|
|
1994
|
+
height: confirmD.icon.size,
|
|
1995
|
+
borderRadius: confirmD.icon.radius,
|
|
1996
|
+
marginBottom: 16,
|
|
1997
|
+
}, resizeMode: confirmD.icon.fit, accessibilityIgnoresInvertColors: true })) : null;
|
|
1882
1998
|
const cardImageHalf = imgLayout === 'half' ? ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: {
|
|
1883
1999
|
position: 'relative',
|
|
1884
2000
|
overflow: 'hidden',
|
|
@@ -2227,7 +2343,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2227
2343
|
// uses, which is what keeps a deliberate flick from also counting as a tap.
|
|
2228
2344
|
if (!st.moved) {
|
|
2229
2345
|
if (cardTakesPress && cardImpliedList.length) {
|
|
2230
|
-
pressImplied(cardImpliedKey, cardImpliedList[0]);
|
|
2346
|
+
pressImplied(cardImpliedKey, cardImpliedList[0], 'card');
|
|
2231
2347
|
}
|
|
2232
2348
|
return;
|
|
2233
2349
|
}
|
|
@@ -2273,11 +2389,14 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2273
2389
|
const last = qs[qs.length - 1];
|
|
2274
2390
|
const id = last?.name ?? last?.id ?? '';
|
|
2275
2391
|
const action = id ? (responses[id] ?? null) : null;
|
|
2276
|
-
|
|
2392
|
+
// The SCREEN, not the outcome the host hears: an accepted offer with no
|
|
2393
|
+
// code is 'fulfil' to the host and the author's confirmation here — the
|
|
2394
|
+
// web renderer asks the same question.
|
|
2395
|
+
return (0, survey_spec_1.endingScreenFor)((0, survey_spec_1.outcomeFor)({
|
|
2277
2396
|
interactionType: sv.interactionType, action,
|
|
2278
2397
|
affirmative: String(action) === String(acts[0].value),
|
|
2279
2398
|
reward: sv.reward, compliance: sv.compliance,
|
|
2280
|
-
});
|
|
2399
|
+
}), sv);
|
|
2281
2400
|
}, [survey, responses]);
|
|
2282
2401
|
const dismissMs = (0, survey_spec_1.autoDismissMs)(display, survey.delivery);
|
|
2283
2402
|
const engagedRef = (0, react_2.useRef)(false);
|
|
@@ -2511,9 +2630,21 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2511
2630
|
// Announced politely: urgency a sighted respondent gets from watching the
|
|
2512
2631
|
// number should not be information only they receive.
|
|
2513
2632
|
accessibilityLiveRegion: "polite", children: ["Ends in ", countdown.text] })) : null;
|
|
2514
|
-
|
|
2633
|
+
// Across pages the bar counts pages. On a single page it counts answered
|
|
2634
|
+
// questions, as web and link do, so the setting never silently does nothing.
|
|
2635
|
+
const _countable = currentQuestions.filter((q) => {
|
|
2636
|
+
const t = String(q.type || '').toLowerCase();
|
|
2637
|
+
return t !== 'html' && t !== 'pagebreak' && t !== 'content' && t !== 'implied';
|
|
2638
|
+
});
|
|
2639
|
+
const _answeredN = _countable.filter((q) => {
|
|
2640
|
+
const v = responses[q.id || q.name];
|
|
2641
|
+
return v != null && v !== '' && !(Array.isArray(v) && v.length === 0);
|
|
2642
|
+
}).length;
|
|
2643
|
+
const progressNow = totalPages > 1 ? currentPage + 1 : _answeredN;
|
|
2644
|
+
const progressMax = totalPages > 1 ? totalPages : _countable.length;
|
|
2645
|
+
const progressEl = (totalPages > 1 || _countable.length > 1) ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [effShowPageNumbers && totalPages > 1 && ((0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.progress, children: [currentPage + 1, " / ", totalPages] })), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.progressBarTrack, accessible: true, accessibilityRole: "progressbar", accessibilityLabel: totalPages > 1 ? `Page ${currentPage + 1} of ${totalPages}` : `${progressNow} of ${progressMax} answered`, accessibilityValue: { min: 0, max: progressMax, now: progressNow }, accessibilityLiveRegion: "polite", children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
2515
2646
|
styles.progressBarFill,
|
|
2516
|
-
{ width: `${(
|
|
2647
|
+
{ width: `${(progressNow / progressMax) * 100}%`, backgroundColor: primaryColor },
|
|
2517
2648
|
] }) })] })) : null;
|
|
2518
2649
|
return ((0, jsx_runtime_1.jsx)(react_native_1.Modal, { visible: visible, transparent: true, animationType: modalAnimation,
|
|
2519
2650
|
// Android's back gesture. Wrapped rather than passed: the handler now
|
|
@@ -2527,7 +2658,28 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2527
2658
|
// The OVERLAY, not the card — a screen-shaped image with the card in
|
|
2528
2659
|
// its real position, rather than a card-shaped one the replay player
|
|
2529
2660
|
// would stretch across the whole device frame.
|
|
2530
|
-
ref: (v) => { cardViewRef.current = v; },
|
|
2661
|
+
ref: (v) => { cardViewRef.current = v; },
|
|
2662
|
+
// WITHOUT THIS THE VIEW MAY NOT EXIST NATIVELY, AND THE CAPTURE FAILS.
|
|
2663
|
+
//
|
|
2664
|
+
// React Native collapses views that have no native side effects. For a
|
|
2665
|
+
// POPUP this overlay is exactly that: backgroundColor transparent and
|
|
2666
|
+
// pointerEvents box-none, a container that only positions its child. So
|
|
2667
|
+
// it was optimised away, findNodeHandle had nothing to resolve,
|
|
2668
|
+
// captureRef rejected, captureView returned null, and the recorder fell
|
|
2669
|
+
// back to the host captureScreen — which on iOS returns the TOPMOST
|
|
2670
|
+
// UIWindow.
|
|
2671
|
+
//
|
|
2672
|
+
// That is why every offer and notification frame came back as whatever
|
|
2673
|
+
// native alert happened to be up, floating on flat grey, with the card
|
|
2674
|
+
// nowhere in it. Measured: three survey_shown frames at 73,548 / 73,512
|
|
2675
|
+
// / 73,528 bytes — the same wrong image each time — in a session where
|
|
2676
|
+
// the respondent demonstrably swiped one card away, closed another and
|
|
2677
|
+
// completed a third.
|
|
2678
|
+
//
|
|
2679
|
+
// It fails for popups specifically BECAUSE popups are the transparent
|
|
2680
|
+
// ones; a modal paints a scrim and survives collapsing, which is why
|
|
2681
|
+
// this looked like "offers are broken" rather than "capture is".
|
|
2682
|
+
collapsable: false, pointerEvents: isPopupOverlay ? 'box-none' : 'auto', style: [
|
|
2531
2683
|
styles.overlay,
|
|
2532
2684
|
// Transparent for a popup — the app behind it is not being taken over.
|
|
2533
2685
|
isPopupOverlay && { backgroundColor: 'transparent' },
|
|
@@ -2552,10 +2704,19 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2552
2704
|
// Feeds the authored-coordinate maths above. Only tracked when a
|
|
2553
2705
|
// coordinate is actually in play, and only on a real change, so the
|
|
2554
2706
|
// common case costs nothing and this cannot loop.
|
|
2555
|
-
|
|
2707
|
+
// MEASURED ALWAYS, not only for a coordinate. The second reader is
|
|
2708
|
+
// the confirmation: it replaces the card's whole content, and the
|
|
2709
|
+
// two screens hold different amounts — so the card shrank under a
|
|
2710
|
+
// respondent who had just pressed a button on it, which reads as a
|
|
2711
|
+
// different card arriving rather than the same one answering. The
|
|
2712
|
+
// last height measured BEFORE the ending becomes its floor.
|
|
2713
|
+
onLayout: (e) => {
|
|
2556
2714
|
const h = Math.round(e.nativeEvent.layout.height);
|
|
2557
|
-
|
|
2558
|
-
|
|
2715
|
+
if (!showThankYou && h > 0)
|
|
2716
|
+
cardHeightRef.current = h;
|
|
2717
|
+
if (hasPopupY)
|
|
2718
|
+
setMeasuredCardH((prev) => (Math.abs(prev - h) > 1 ? h : prev));
|
|
2719
|
+
}, style: [
|
|
2559
2720
|
styles.card,
|
|
2560
2721
|
themeCard,
|
|
2561
2722
|
enterStyle,
|
|
@@ -2575,34 +2736,96 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2575
2736
|
], children: [cardImageBack, closeShown && ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { ...swipeExempt, style: [
|
|
2576
2737
|
styles.closeButton,
|
|
2577
2738
|
isRtl ? { left: contract.closeInset } : { right: contract.closeInset },
|
|
2578
|
-
], onPress: () => handleDismiss('close'), activeOpacity: 0.7, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.closeGlyph, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.closeBar, { transform: [{ rotate: '45deg' }] }] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.closeBar, { transform: [{ rotate: '-45deg' }] }] })] }) })), showThankYou && endingKind !== 'thanks' && (0, survey_spec_1.showsConfirmation)(survey) ? (() => {
|
|
2579
|
-
const rw = (0, survey_spec_1.rewardToShow)(survey);
|
|
2739
|
+
], onPress: () => handleDismiss('close'), activeOpacity: 0.7, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.closeGlyph, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.closeBar, { transform: [{ rotate: '45deg' }] }] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.closeBar, { transform: [{ rotate: '-45deg' }] }] })] }) })), showThankYou && endingKind !== 'thanks' && endingKind !== 'fulfil' && (0, survey_spec_1.showsConfirmation)(survey) ? (() => {
|
|
2580
2740
|
const comp = survey.compliance;
|
|
2581
|
-
if (endingKind === 'fulfil' && rw) {
|
|
2582
|
-
const exp = rw.expiresAt ? new Date(rw.expiresAt) : null;
|
|
2583
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.thankYou, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouTitle, fontStyle, themeTextPrimary], children: (0, survey_spec_1.confirmationText)(survey, 'It’s yours') }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { selectable: true, style: [styles.rewardCode, fontStyle, themeTextPrimary], children: rw.value }), !!rw.whereValid && ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary], children: rw.whereValid })), !!exp && ((0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary], children: ["Expires ", exp.toLocaleString()] }))] }));
|
|
2584
|
-
}
|
|
2585
2741
|
if (endingKind === 'hold') {
|
|
2586
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.thankYou, accessibilityLiveRegion: "polite", children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouTitle, fontStyle, themeTextPrimary], children: comp?.reviewRequired ? 'Under review' : 'Not completed' }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary], children: comp?.blockedMessage
|
|
2742
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.thankYou, endingHold, { alignItems: endingAlign }], accessibilityLiveRegion: "polite", children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouTitle, fontStyle, themeTextPrimary, endingHeadFS], children: comp?.reviewRequired ? 'Under review' : 'Not completed' }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary, endingSubFS], children: comp?.blockedMessage
|
|
2587
2743
|
?? (comp?.reviewRequired
|
|
2588
2744
|
? 'We’re checking your details. You’ll be able to continue once that’s done.'
|
|
2589
2745
|
: 'You need to accept this before you can continue.') })] }));
|
|
2590
2746
|
}
|
|
2591
2747
|
if (endingKind === 'unblock') {
|
|
2592
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.thankYou, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouTitle, fontStyle, themeTextPrimary], children: "Recorded" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary], children: "You can carry on." })] }));
|
|
2748
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.thankYou, endingHold, { alignItems: endingAlign }], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouTitle, fontStyle, themeTextPrimary, endingHeadFS], children: "Recorded" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary, endingSubFS], children: "You can carry on." })] }));
|
|
2593
2749
|
}
|
|
2594
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.thankYou, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary], children: endingKind === 'navigate' ? 'Taking you there…' : 'Closed.' }) }));
|
|
2750
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.thankYou, endingHold, { alignItems: endingAlign }], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary, endingSubFS], children: endingKind === 'navigate' ? 'Taking you there…' : 'Closed.' }) }));
|
|
2751
|
+
})() : (showThankYou && (reply === 'pending' || reply === 'failed' || reply === 'later')) ? (() => {
|
|
2752
|
+
// WAITING FOR THE APP, or what came of it — the author's words for
|
|
2753
|
+
// each state (appReply), in the confirmation's own header style.
|
|
2754
|
+
const ar = (0, survey_spec_1.appReply)(survey);
|
|
2755
|
+
const words = reply === 'pending' ? ar.pending : reply === 'failed' ? ar.failed : ar.later;
|
|
2756
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.thankYou, endingHold, { alignItems: endingAlign }], accessibilityLiveRegion: reply === 'failed' ? 'assertive' : 'polite', children: [reply === 'pending' && (0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { color: primaryColor, style: { marginBottom: 12 } }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouTitle, dirText, fontStyle, themeTextPrimary, endingHeadFS], children: (0, interpolate_1.interpolatePlain)(words, evalCtx) })] }));
|
|
2595
2757
|
})() : showThankYou ? (() => {
|
|
2596
2758
|
// The thank-you message can be rich HTML from the web builder.
|
|
2597
2759
|
// Pull out an embedded <img> (often a data: URI icon) and render
|
|
2598
2760
|
// it as a real Image — stripHtml would otherwise drop it — then
|
|
2599
2761
|
// show the remaining text. When an image is present it stands in
|
|
2600
2762
|
// for the default ✓ glyph.
|
|
2763
|
+
// THE CONFIRMATION'S COMPONENTS, under its headline and body — the
|
|
2764
|
+
// schema's list, derived for anything made before v1.2 (a code
|
|
2765
|
+
// offer gets its code). Same rule the web renderer asks.
|
|
2766
|
+
const endParts = (0, survey_spec_1.confirmationQuestions)(survey);
|
|
2767
|
+
const endReward = (0, survey_spec_1.rewardToShow)(survey);
|
|
2768
|
+
const hasCode = !!endReward && endReward.type === 'code'
|
|
2769
|
+
&& endParts.some((q) => String(q.type).toLowerCase() === 'reward');
|
|
2601
2770
|
const rawThankYou = (0, interpolate_1.interpolatePlain)((0, translationLookup_1.surveyText)(survey, currentLanguage, 'thankYouMessage', survey.settings?.thankYouMessage)
|
|
2602
|
-
??
|
|
2771
|
+
?? (0, survey_spec_1.confirmationDefaults)(survey, hasCode).title, evalCtx);
|
|
2772
|
+
const partViews = endParts.map((q) => {
|
|
2773
|
+
const t = String(q.type).toLowerCase();
|
|
2774
|
+
if (t === 'reward') {
|
|
2775
|
+
if (!hasCode || !endReward)
|
|
2776
|
+
return null;
|
|
2777
|
+
const exp = endReward.expiresAt ? new Date(endReward.expiresAt) : null;
|
|
2778
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { marginTop: 14, alignItems: endingAlign }, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { selectable: true, style: [styles.rewardCode, fontStyle, themeTextPrimary], children: endReward.value }), !!endReward.whereValid && ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, dirText, fontStyle, themeTextSecondary, endingSubFS], children: endReward.whereValid })), !!exp && ((0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: [styles.thankYouSub, dirText, fontStyle, themeTextSecondary, endingSubFS], children: ["Expires ", exp.toLocaleString()] }))] }, q.id));
|
|
2779
|
+
}
|
|
2780
|
+
const words = stripHtml((0, interpolate_1.interpolatePlain)(String(q.html ?? q.text ?? ''), evalCtx));
|
|
2781
|
+
return words ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, dirText, fontStyle, themeTextPrimary, endingSubFS, { marginTop: 12 }], children: words }, q.id)) : null;
|
|
2782
|
+
});
|
|
2603
2783
|
const imgSrc = firstImageSrc(rawThankYou);
|
|
2604
2784
|
const thankYouText = stripHtml(rawThankYou);
|
|
2605
|
-
|
|
2785
|
+
// THE WORDS, AS ONE BLOCK — so a layout can put them beside the
|
|
2786
|
+
// picture, after it, or on top of it without three copies. The ✓
|
|
2787
|
+
// stands in only when NEITHER picture is set; it used to stand in
|
|
2788
|
+
// for the first alone, so a badge added to a card that already had
|
|
2789
|
+
// a photograph drew the photograph and swallowed the badge.
|
|
2790
|
+
const endWords = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [endingIcon || ((endingImgSrc || imgSrc || hasCode) ? null : ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouEmoji, { color: primaryColor }], children: "\u2713" }))), thankYouText ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouTitle, dirText, fontStyle, themeTextPrimary, endingHeadFS], children: thankYouText })) : null, (() => {
|
|
2791
|
+
const sub = (0, survey_spec_1.confirmationSubtext)(survey, (0, survey_spec_1.confirmationDefaults)(survey, hasCode).body);
|
|
2792
|
+
return sub ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, dirText, fontStyle, themeTextSecondary, endingSubFS], children: (0, interpolate_1.interpolatePlain)(sub, evalCtx) })) : null;
|
|
2793
|
+
})(), partViews] }));
|
|
2794
|
+
// THE CARD'S OWN PADDING, undone on the picture's side only. The
|
|
2795
|
+
// panel's 48 used to sit INSIDE the card's, so the same picture at
|
|
2796
|
+
// the same Height drew smaller here than on the card.
|
|
2797
|
+
const endPad = 0;
|
|
2798
|
+
if (endingLayout === 'beside') {
|
|
2799
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.thankYou, endingHold, {
|
|
2800
|
+
alignItems: 'stretch', flexDirection: 'row', gap: 12,
|
|
2801
|
+
}], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: {
|
|
2802
|
+
position: 'relative', overflow: 'hidden', alignSelf: endingSelf,
|
|
2803
|
+
width: cI.size, height: cI.size, borderRadius: cI.radius,
|
|
2804
|
+
}, children: [endingPicture({}), endingScrim] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: { flex: 1, minWidth: 0, alignItems: endingAlign, justifyContent: 'center' }, children: endWords })] }));
|
|
2805
|
+
}
|
|
2806
|
+
if (endingLayout === 'half') {
|
|
2807
|
+
const vert = cI.side === 'top';
|
|
2808
|
+
// Negative margins undo the panel's own padding on the picture's
|
|
2809
|
+
// side only — the words keep theirs, which is what "bleeds to
|
|
2810
|
+
// the edges" means on the card too.
|
|
2811
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.thankYou, endingHold, {
|
|
2812
|
+
alignItems: 'stretch',
|
|
2813
|
+
flexDirection: vert ? 'column' : (cI.side === 'right' ? 'row-reverse' : 'row'),
|
|
2814
|
+
gap: vert ? 0 : 14,
|
|
2815
|
+
}], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: {
|
|
2816
|
+
position: 'relative', overflow: 'hidden',
|
|
2817
|
+
...(vert
|
|
2818
|
+
? { marginTop: -endPad, marginLeft: -endPad, marginRight: -endPad,
|
|
2819
|
+
marginBottom: 16, height: cI.height }
|
|
2820
|
+
: { marginTop: -endPad, marginBottom: -endPad,
|
|
2821
|
+
[cI.side === 'right' ? 'marginRight' : 'marginLeft']: -endPad,
|
|
2822
|
+
width: `${cI.split}%` }),
|
|
2823
|
+
}, children: [endingPicture({}), fadeSteps(vert ? 'bottom' : (cI.side === 'right' ? 'left' : 'right'), cI.fade), endingScrim] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: { flex: 1, minWidth: 0, alignItems: endingAlign, justifyContent: 'center' }, children: endWords })] }));
|
|
2824
|
+
}
|
|
2825
|
+
if (endingLayout === 'background') {
|
|
2826
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.thankYou, endingHold, { alignItems: endingAlign }], children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { pointerEvents: "none", style: { position: 'absolute', top: 0, right: 0, bottom: 0, left: 0, overflow: 'hidden' }, children: [endingPicture({}), endingScrim] }), endWords] }));
|
|
2827
|
+
}
|
|
2828
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.thankYou, endingHold, { alignItems: endingAlign }], children: [endingLayout === 'above' ? endingImage : null, (!endingImgSrc && imgSrc) ? ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: imgSrc }, style: styles.thankYouImage, resizeMode: "contain" })) : null, endWords, endingLayout === 'below' ? endingImage : null] }));
|
|
2606
2829
|
})() : ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: halfSide
|
|
2607
2830
|
? { flexDirection: imgD.side === 'right' ? 'row-reverse' : 'row',
|
|
2608
2831
|
alignItems: 'stretch' }
|
|
@@ -2625,7 +2848,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2625
2848
|
? { marginRight: 12 - halfPadR }
|
|
2626
2849
|
: { marginLeft: 12 - halfPadL }),
|
|
2627
2850
|
}
|
|
2628
|
-
: undefined, children: [langSwitcher, imgLayout === 'above' ? (0, jsx_runtime_1.jsx)(react_native_1.View, { style:
|
|
2851
|
+
: undefined, children: [langSwitcher, imgLayout === 'above' && (besideImage || notifyBeside) ? (0, jsx_runtime_1.jsx)(react_native_1.View, { style: bandPad, children: cardImageBand }) : null, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
2629
2852
|
styles.header,
|
|
2630
2853
|
padHeader,
|
|
2631
2854
|
// No ✕ reservation here in either direction — the title carries
|
|
@@ -2644,7 +2867,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2644
2867
|
: survey.branding?.logoAlign === 'right'
|
|
2645
2868
|
? { justifyContent: 'flex-end' }
|
|
2646
2869
|
: null),
|
|
2647
|
-
], children: [survey.branding?.showLogo !== false && survey.branding?.logo ? ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: survey.branding.logo }, style: styles.brandLogo, resizeMode: "contain", accessible: !!survey.branding.brandName, accessibilityLabel: survey.branding.brandName })) : null, survey.branding?.brandName ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.brandName, dirText, fontStyle, themeTextSecondary], children: survey.branding.brandName })) : null] })), besideImage ? ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: cardImageQ?.imageUrl }, accessibilityLabel: cardImageQ?.alt || undefined, accessible: !!cardImageQ?.alt, style: {
|
|
2870
|
+
], children: [survey.branding?.showLogo !== false && survey.branding?.logo ? ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: survey.branding.logo }, style: styles.brandLogo, resizeMode: "contain", accessible: !!survey.branding.brandName, accessibilityLabel: survey.branding.brandName })) : null, survey.branding?.brandName ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.brandName, dirText, fontStyle, themeTextSecondary], children: survey.branding.brandName })) : null] })), imgLayout === 'above' && !(besideImage || notifyBeside) ? cardImageBand : null, besideImage ? ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: cardImageQ?.imageUrl }, accessibilityLabel: cardImageQ?.alt || undefined, accessible: !!cardImageQ?.alt, style: {
|
|
2648
2871
|
width: contract.imageSize,
|
|
2649
2872
|
height: contract.imageSize,
|
|
2650
2873
|
borderRadius: contract.imageRadius,
|
|
@@ -2656,9 +2879,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2656
2879
|
: imgD.align === 'end' ? 'flex-end' : 'center',
|
|
2657
2880
|
}, 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,
|
|
2658
2881
|
closeReserve,
|
|
2659
|
-
|
|
2660
|
-
? { textAlign: survey.titleAlign }
|
|
2661
|
-
: null], children: (0, interpolate_1.interpolatePlain)(
|
|
2882
|
+
{ textAlign: cardTitleAlign }], children: (0, interpolate_1.interpolatePlain)(
|
|
2662
2883
|
// Title is plain text; strip any stray rich-text markup +
|
|
2663
2884
|
// decode so it never shows literal HTML on native.
|
|
2664
2885
|
String((0, translationLookup_1.surveyText)(survey, currentLanguage, 'title', survey.title) || '')
|
|
@@ -2672,6 +2893,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2672
2893
|
const descRaw = (0, translationLookup_1.surveyText)(survey, currentLanguage, 'description', survey.description);
|
|
2673
2894
|
const desc = String(descRaw || '').replace(/<[^>]*>/g, '').replace(/ /gi, ' ').trim();
|
|
2674
2895
|
return desc ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.description, dirText, fontStyle, themeTextSecondary, descFS, notifyDescFS,
|
|
2896
|
+
{ textAlign: cardDescAlign },
|
|
2675
2897
|
// Takes the width the button leaves, and no more.
|
|
2676
2898
|
notifyBeside && { flex: 1, minWidth: 0, marginTop: 2 }], children: (0, interpolate_1.interpolatePlain)(desc, evalCtx) })) : null;
|
|
2677
2899
|
})(), notifyActionQ ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: { flexShrink: 0, marginLeft: 12 }, children: renderQuestion(notifyActionQ) })) : null] })] }), deadlineEl, (() => {
|
|
@@ -2681,7 +2903,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2681
2903
|
{ fontSize: Math.round((_num(_authoredStyle.fontSizeDescription) ?? 14) * 0.85),
|
|
2682
2904
|
marginTop: 6 },
|
|
2683
2905
|
], children: (0, interpolate_1.interpolatePlain)(terms, evalCtx) })) : null;
|
|
2684
|
-
})(), showProgressTop && progressEl] }), imgLayout === 'below' ? (0, jsx_runtime_1.jsx)(react_native_1.View, { style:
|
|
2906
|
+
})(), showProgressTop && progressEl] }), imgLayout === 'below' ? (0, jsx_runtime_1.jsx)(react_native_1.View, { style: bandPad, children: cardImageBand }) : null, (0, jsx_runtime_1.jsx)(BodyContainer, { style: [styles.body, padBody, fsContent], showsVerticalScrollIndicator: false, children: currentQuestions.filter((q) => {
|
|
2685
2907
|
// The card composes the picture itself — beside the text, in
|
|
2686
2908
|
// a band, or behind everything. Leaving it here too would
|
|
2687
2909
|
// render it twice, the second time full-bleed.
|
|
@@ -2727,7 +2949,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2727
2949
|
// have. Web spends 12px above the action and nothing
|
|
2728
2950
|
// below it (`.cxp-notify-action-below`); this matches.
|
|
2729
2951
|
isNotifyBanner && { marginTop: 12, marginBottom: 0 },
|
|
2730
|
-
], children: [!(0, survey_spec_1.showsQuestionLabel)(q) ? null : ((0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: [styles.questionTitle, dirText, fontStyle, themeTextPrimary, qTitleFS],
|
|
2952
|
+
], children: [!(0, survey_spec_1.showsQuestionLabel)(q) ? null : ((0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: [styles.questionTitle, dirText, fontStyle, themeTextPrimary, qTitleFS, { textAlign: cardQuestionAlign }],
|
|
2731
2953
|
// The asterisk below is decoration: a screen reader
|
|
2732
2954
|
// reads it as "star", or skips it, so a required
|
|
2733
2955
|
// question sounded exactly like an optional one until
|
|
@@ -2736,7 +2958,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2736
2958
|
if (isImplied)
|
|
2737
2959
|
return null;
|
|
2738
2960
|
const qDesc = (0, translationLookup_1.questionText)(survey, currentLanguage, rowKey, 'description', q.description);
|
|
2739
|
-
return qDesc ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.questionDesc, dirText, fontStyle, themeTextMuted, qDescFS], children: (0, interpolate_1.interpolatePlain)(qDesc, evalCtx) })) : null;
|
|
2961
|
+
return qDesc ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.questionDesc, dirText, fontStyle, themeTextMuted, qDescFS, { textAlign: cardQuestionAlign }], children: (0, interpolate_1.interpolatePlain)(qDesc, evalCtx) })) : null;
|
|
2740
2962
|
})(), renderQuestion(q), errors[rowKey] ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.errorText, dirText, fontStyle],
|
|
2741
2963
|
// Announced when it appears, and reachable afterwards:
|
|
2742
2964
|
// a message that is only spoken once leaves someone who
|
|
@@ -2766,7 +2988,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
2766
2988
|
// Right-align parity with web: Back hugs the left edge while
|
|
2767
2989
|
// Next/Submit sits on the right (web uses margin-right:auto).
|
|
2768
2990
|
buttonAlign === 'right' && { marginRight: 'auto' },
|
|
2769
|
-
], 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 ? null : (() => {
|
|
2991
|
+
], 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 : (() => {
|
|
2770
2992
|
// Reflect the actual outcome of pressing Next: if a
|
|
2771
2993
|
// branch resolves to __end__ we're submitting even
|
|
2772
2994
|
// when we're not on the last page. Cheap to evaluate
|
|
@@ -3139,7 +3361,12 @@ const makeStyles = (c) => react_native_1.StyleSheet.create({
|
|
|
3139
3361
|
fontWeight: '700',
|
|
3140
3362
|
},
|
|
3141
3363
|
thankYou: {
|
|
3142
|
-
|
|
3364
|
+
// NO PADDING OF ITS OWN. The card already pads its content; a second inset
|
|
3365
|
+
// here made the confirmation's box narrower and shorter than the card's,
|
|
3366
|
+
// so the same picture at the same Width and Height drew smaller on the
|
|
3367
|
+
// second screen with no control to explain it. The components space
|
|
3368
|
+
// themselves with their own margins, exactly as they do on the card.
|
|
3369
|
+
paddingVertical: 8,
|
|
3143
3370
|
alignItems: 'center',
|
|
3144
3371
|
justifyContent: 'center',
|
|
3145
3372
|
},
|
|
@@ -3149,8 +3376,9 @@ const makeStyles = (c) => react_native_1.StyleSheet.create({
|
|
|
3149
3376
|
marginBottom: 16,
|
|
3150
3377
|
},
|
|
3151
3378
|
thankYouImage: {
|
|
3152
|
-
|
|
3153
|
-
|
|
3379
|
+
// FULL WIDTH, and the height comes from the contract at the render site.
|
|
3380
|
+
// 72x72 was a glyph's footprint, not a picture's.
|
|
3381
|
+
width: '100%',
|
|
3154
3382
|
marginBottom: 16,
|
|
3155
3383
|
},
|
|
3156
3384
|
thankYouTitle: {
|
|
@@ -3161,6 +3389,15 @@ const makeStyles = (c) => react_native_1.StyleSheet.create({
|
|
|
3161
3389
|
lineHeight: 26,
|
|
3162
3390
|
},
|
|
3163
3391
|
thankYouSub: {
|
|
3392
|
+
// A COLOUR OF ITS OWN, because the override beside it is LIGHT-ONLY.
|
|
3393
|
+
//
|
|
3394
|
+
// themeTextSecondary is `isLight ? {...} : undefined`, on the assumption
|
|
3395
|
+
// that every style it decorates already carries the DARK colour. This one
|
|
3396
|
+
// carried none, so on a dark card it fell through to React Native's
|
|
3397
|
+
// default Text colour — black — and the confirmation's sub-line was dark
|
|
3398
|
+
// text on a dark background. The title above it was fine because it had
|
|
3399
|
+
// `c.headingColor`; that difference is the whole bug.
|
|
3400
|
+
color: c.textSecondary,
|
|
3164
3401
|
fontSize: 14,
|
|
3165
3402
|
textAlign: 'center',
|
|
3166
3403
|
lineHeight: 20,
|
|
@@ -3170,6 +3407,8 @@ const makeStyles = (c) => react_native_1.StyleSheet.create({
|
|
|
3170
3407
|
// button here because RN's clipboard is an optional peer, and a code you can
|
|
3171
3408
|
// select is better than a button that may not exist.
|
|
3172
3409
|
rewardCode: {
|
|
3410
|
+
// Same fault, and worse: this is the discount code itself.
|
|
3411
|
+
color: c.textPrimary,
|
|
3173
3412
|
fontSize: 22,
|
|
3174
3413
|
fontWeight: '700',
|
|
3175
3414
|
letterSpacing: 1.5,
|