@cxpinsight/react-native 0.8.0 → 0.10.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.
- package/lib/CXPinsightProvider.d.ts.map +1 -1
- package/lib/CXPinsightProvider.js +40 -1
- package/lib/CXPinsightProvider.js.map +1 -1
- package/lib/core/capabilities.d.ts +34 -0
- package/lib/core/capabilities.d.ts.map +1 -0
- package/lib/core/capabilities.js +85 -0
- package/lib/core/capabilities.js.map +1 -0
- package/lib/core/triggerEngine.d.ts +26 -0
- package/lib/core/triggerEngine.d.ts.map +1 -1
- package/lib/core/triggerEngine.js +79 -3
- package/lib/core/triggerEngine.js.map +1 -1
- package/lib/survey/SurveyModal.d.ts.map +1 -1
- package/lib/survey/SurveyModal.js +241 -7
- package/lib/survey/SurveyModal.js.map +1 -1
- package/lib/survey/expressionEval.d.ts +18 -0
- package/lib/survey/expressionEval.d.ts.map +1 -1
- package/lib/survey/expressionEval.js +63 -0
- package/lib/survey/expressionEval.js.map +1 -1
- package/lib/survey/interpolate.js +20 -2
- package/lib/survey/interpolate.js.map +1 -1
- package/lib/types.d.ts +36 -0
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js.map +1 -1
- package/package.json +4 -4
|
@@ -51,6 +51,7 @@ const MatrixDropdownQuestion_1 = require("./questions/MatrixDropdownQuestion");
|
|
|
51
51
|
const MatrixDynamicQuestion_1 = require("./questions/MatrixDynamicQuestion");
|
|
52
52
|
const PanelDynamicQuestion_1 = require("./questions/PanelDynamicQuestion");
|
|
53
53
|
const DateQuestion_1 = require("./questions/DateQuestion");
|
|
54
|
+
const survey_spec_1 = require("@cxpinsight/survey-spec");
|
|
54
55
|
const SignatureQuestion_1 = require("./questions/SignatureQuestion");
|
|
55
56
|
const FileUploadQuestion_1 = require("./questions/FileUploadQuestion");
|
|
56
57
|
const MultiTextQuestion_1 = require("./questions/MultiTextQuestion");
|
|
@@ -241,13 +242,22 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
241
242
|
// defaults to "visible". Fail-open on parse errors — see
|
|
242
243
|
// expressionEval.ts.
|
|
243
244
|
const isVisible = (q) => {
|
|
244
|
-
|
|
245
|
+
// Prefer the tree the server compiled; fall back to parsing the string.
|
|
246
|
+
// Guarded inside evaluateConditionCompiled, because an app on
|
|
247
|
+
// survey-spec@0.4.0 has no tree walker at all.
|
|
248
|
+
const qc = q;
|
|
249
|
+
if (q.showWhen && !(0, expressionEval_1.evaluateConditionCompiled)(q.showWhen, qc.showWhenCompiled, evalCtx))
|
|
245
250
|
return false;
|
|
246
|
-
if (q.hideWhen && (0, expressionEval_1.
|
|
251
|
+
if (q.hideWhen && (0, expressionEval_1.evaluateConditionCompiled)(q.hideWhen, qc.hideWhenCompiled, evalCtx))
|
|
247
252
|
return false;
|
|
248
253
|
return true;
|
|
249
254
|
};
|
|
250
255
|
const currentQuestions = (pages[currentPage]?.elements ?? []).filter(isVisible);
|
|
256
|
+
// A step ending in an implied question has no Next/Submit — its action
|
|
257
|
+
// buttons are the answer, and pressing one commits. Shared with the web SDK
|
|
258
|
+
// and the React renderer so a Notify/Offer/Comply template behaves the same
|
|
259
|
+
// on a phone as it does on a website. Suppresses the footer below.
|
|
260
|
+
const impliedNow = (0, survey_spec_1.impliedStepActions)(currentQuestions);
|
|
251
261
|
const updateResponse = (0, react_1.useCallback)((name, value) => {
|
|
252
262
|
setResponses((prev) => ({ ...prev, [name]: value }));
|
|
253
263
|
// Live-clear the error entry the moment the new answer passes
|
|
@@ -275,7 +285,8 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
275
285
|
if (!Array.isArray(q.branches) || q.branches.length === 0)
|
|
276
286
|
continue;
|
|
277
287
|
for (const b of q.branches) {
|
|
278
|
-
|
|
288
|
+
const bc = b;
|
|
289
|
+
if (b.when && !(0, expressionEval_1.evaluateConditionCompiled)(b.when, bc.whenCompiled, evalCtx))
|
|
279
290
|
continue;
|
|
280
291
|
if (b?.target === 'flow') {
|
|
281
292
|
if (!b.flowId)
|
|
@@ -382,6 +393,9 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
382
393
|
setForkStack([]);
|
|
383
394
|
}, duration);
|
|
384
395
|
};
|
|
396
|
+
// Kept fresh on every render so an implied action's deferred press runs the
|
|
397
|
+
// current closure rather than the one captured when the button mounted.
|
|
398
|
+
const handleNextRef = react_1.default.useRef(() => { });
|
|
385
399
|
const handleNext = () => {
|
|
386
400
|
const pageErrors = validateCurrentPage();
|
|
387
401
|
if (Object.keys(pageErrors).length > 0) {
|
|
@@ -437,6 +451,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
437
451
|
setHistory((h) => [...h, { pageIndex: currentPage, flow: currentFlow }]);
|
|
438
452
|
setCurrentPage(linearNext);
|
|
439
453
|
};
|
|
454
|
+
handleNextRef.current = handleNext;
|
|
440
455
|
const handleBack = () => {
|
|
441
456
|
// Non-linear navigation: pop the actual history stack, not just
|
|
442
457
|
// currentPage - 1 (which would skip over a jump). Any errors
|
|
@@ -462,6 +477,9 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
462
477
|
setCurrentPage((p) => p - 1);
|
|
463
478
|
}
|
|
464
479
|
};
|
|
480
|
+
// Kept fresh so the memoized pan responder never dismisses through a stale
|
|
481
|
+
// closure — the same reason handleNextRef exists.
|
|
482
|
+
const handleDismissRef = (0, react_1.useRef)(() => { });
|
|
465
483
|
const handleDismiss = () => {
|
|
466
484
|
setCurrentPage(0);
|
|
467
485
|
setResponses({});
|
|
@@ -473,6 +491,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
473
491
|
setForkStack([]);
|
|
474
492
|
onDismiss();
|
|
475
493
|
};
|
|
494
|
+
handleDismissRef.current = handleDismiss;
|
|
476
495
|
// Normalize the v1.1 options[] {value,label} and the legacy
|
|
477
496
|
// choices[] (string | {value,text}) into a single shape that
|
|
478
497
|
// MultipleChoiceQuestion accepts. hasOther appends a synthetic
|
|
@@ -499,7 +518,30 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
499
518
|
}
|
|
500
519
|
return out;
|
|
501
520
|
}
|
|
502
|
-
|
|
521
|
+
/**
|
|
522
|
+
* Compiled props, under the names the existing renderers already read.
|
|
523
|
+
*
|
|
524
|
+
* The renderers were written against the author's question shape
|
|
525
|
+
* (minValue/maxValue, options); the server's compiler emits the contract's
|
|
526
|
+
* names (min/max). Translating onto a COPY avoids touching thirty-nine
|
|
527
|
+
* renderers, and author fields win — the compiler derives csat's 1..5 because
|
|
528
|
+
* the author stated none, and must never override a range they did state.
|
|
529
|
+
*/
|
|
530
|
+
function applyRenderProps(q) {
|
|
531
|
+
const r = q.render;
|
|
532
|
+
if (!r || !r.props)
|
|
533
|
+
return q;
|
|
534
|
+
const out = { ...q };
|
|
535
|
+
const MAP = { min: 'minValue', max: 'maxValue' };
|
|
536
|
+
for (const [k, v] of Object.entries(r.props)) {
|
|
537
|
+
const target = MAP[k] || k;
|
|
538
|
+
if (out[target] === undefined || out[target] === null)
|
|
539
|
+
out[target] = v;
|
|
540
|
+
}
|
|
541
|
+
return out;
|
|
542
|
+
}
|
|
543
|
+
function renderQuestion(rawQuestion) {
|
|
544
|
+
const question = applyRenderProps(rawQuestion);
|
|
503
545
|
const key = question.id || question.name;
|
|
504
546
|
const val = responses[key];
|
|
505
547
|
// Shown when a question type needs an optional peer the host app did not
|
|
@@ -644,6 +686,51 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
644
686
|
// they render for real; when it did not, they fall back to the hint that
|
|
645
687
|
// used to be the ONLY behaviour. Nothing is forced into an app's bundle
|
|
646
688
|
// for a question type it never asks.
|
|
689
|
+
// A Notify, Offer or Comply interaction asks one question and the answer
|
|
690
|
+
// is which action was taken. The choices ARE the buttons, and their
|
|
691
|
+
// labels come from the shared spec so "Activate discount" reads the same
|
|
692
|
+
// here as on a website and on a link.
|
|
693
|
+
'implied': () => {
|
|
694
|
+
// Same resolver the thank-you text uses, so `myapp://cart/{{id}}`
|
|
695
|
+
// opens the customer's cart rather than a literal deep link nothing
|
|
696
|
+
// can handle.
|
|
697
|
+
const actions = (0, survey_spec_1.impliedActions)(question, (t) => (0, interpolate_1.interpolatePlain)(t, evalCtx));
|
|
698
|
+
const list = actions.length ? actions : [{ value: 'acknowledged', label: 'Continue' }];
|
|
699
|
+
// Consent choices render identically — see impliedEqualProminence.
|
|
700
|
+
const equal = (0, survey_spec_1.impliedEqualProminence)([question]);
|
|
701
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.impliedActions, children: list.map((a, i) => ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, {
|
|
702
|
+
// The press is both the answer and the Next — the footer is
|
|
703
|
+
// hidden on this step. Deferred a tick so setResponses has
|
|
704
|
+
// flushed before handleNext validates what it just replaced.
|
|
705
|
+
onPress: () => {
|
|
706
|
+
// "Remind me later" dismisses instead of answering, so the
|
|
707
|
+
// cadence records a SKIP and brings the interaction back
|
|
708
|
+
// after the author's wait. Answering would record a decision
|
|
709
|
+
// this person explicitly declined to make and retire it
|
|
710
|
+
// against maxSubmissions. It does not follow the action's
|
|
711
|
+
// link either — postponing is not a request to go anywhere.
|
|
712
|
+
if (a.defer) {
|
|
713
|
+
handleDismissRef.current();
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
updateResponse(key, a.value);
|
|
717
|
+
setTimeout(() => handleNextRef.current(), 0);
|
|
718
|
+
// Follow the action's deep link. `myapp://cart` is the point
|
|
719
|
+
// of this on a phone; Linking refuses a scheme the OS has no
|
|
720
|
+
// handler for, so a bad target fails quietly rather than
|
|
721
|
+
// crashing the survey. The href already passed
|
|
722
|
+
// safeActionHref in the shared spec.
|
|
723
|
+
if (a.href) {
|
|
724
|
+
react_native_1.Linking.openURL(a.href).catch(() => { });
|
|
725
|
+
}
|
|
726
|
+
}, accessibilityRole: "button", accessibilityState: { selected: val === a.value }, style: [
|
|
727
|
+
styles.impliedBtn,
|
|
728
|
+
// The first action is the affirmative one. An author who
|
|
729
|
+
// lists "Decline" first gets it emphasised — their call.
|
|
730
|
+
(i === 0 && !equal) ? styles.impliedPrimary : styles.impliedSecondary,
|
|
731
|
+
val === a.value && styles.impliedChosen,
|
|
732
|
+
], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: (i === 0 && !equal) ? styles.impliedPrimaryLabel : styles.impliedSecondaryLabel, children: a.label }) }, a.value || String(i)))) }));
|
|
733
|
+
},
|
|
647
734
|
'signature': () => {
|
|
648
735
|
if (!(0, SignatureQuestion_1.isSignatureAvailable)())
|
|
649
736
|
return renderDesktopHint();
|
|
@@ -661,6 +748,37 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
661
748
|
// still runs. Server-side validation will reject any junk.
|
|
662
749
|
return ((0, jsx_runtime_1.jsx)(TextQuestion_1.TextQuestion, { value: val ?? '', onChange: (v) => updateResponse(key, v), placeholder: question.placeholder, contract: contract }));
|
|
663
750
|
};
|
|
751
|
+
// ── Primitive dispatch ────────────────────────────────────────────
|
|
752
|
+
//
|
|
753
|
+
// The server compiles a question type to one of eight primitives plus a
|
|
754
|
+
// presentation hint. Dispatching on THAT means a question type this build
|
|
755
|
+
// has never heard of still renders — which matters more here than on the
|
|
756
|
+
// web, where a new type arrives with the next SDK fetch. On mobile it would
|
|
757
|
+
// otherwise need an App Store release and every respondent updating.
|
|
758
|
+
//
|
|
759
|
+
// The markup is the existing markup: this maps a primitive onto the
|
|
760
|
+
// renderer that already draws it. Anything without an entry — slider, and
|
|
761
|
+
// the grid/group/display/capture families — falls through to the type
|
|
762
|
+
// table exactly as before.
|
|
763
|
+
const PRIMITIVES = {
|
|
764
|
+
'choice_one:list': 'radio',
|
|
765
|
+
'choice_one:dropdown': 'dropdown',
|
|
766
|
+
'choice_one:toggle': 'binary',
|
|
767
|
+
'choice_one:images': 'image_picker',
|
|
768
|
+
'choice_one:scale': 'nps',
|
|
769
|
+
'choice_one:stars': 'rating',
|
|
770
|
+
'choice_one:actions': 'implied',
|
|
771
|
+
'choice_many:list': 'checkbox',
|
|
772
|
+
'choice_many:rank': 'ranking',
|
|
773
|
+
'text:single': 'text',
|
|
774
|
+
'text:multi': 'comment',
|
|
775
|
+
};
|
|
776
|
+
const render = question.render;
|
|
777
|
+
const primName = render && render.primitive
|
|
778
|
+
? PRIMITIVES[`${render.primitive}:${render.presentation || ''}`]
|
|
779
|
+
: undefined;
|
|
780
|
+
if (primName && RENDERERS[primName])
|
|
781
|
+
return RENDERERS[primName]();
|
|
664
782
|
const _fn = RENDERERS[String(question.type || '')] || renderUnsupported;
|
|
665
783
|
return _fn();
|
|
666
784
|
}
|
|
@@ -1138,6 +1256,75 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1138
1256
|
useNativeDriver: true,
|
|
1139
1257
|
}).start();
|
|
1140
1258
|
}, [visible, needsTopSlide, enterAnim]);
|
|
1259
|
+
// ── Swipe to dismiss ────────────────────────────────────────────────
|
|
1260
|
+
//
|
|
1261
|
+
// A push notification goes away when you flick it. The mobile display slice
|
|
1262
|
+
// drops the ✕ for exactly that reason, so this is the affordance that
|
|
1263
|
+
// replaces it — without it the banner would simply be stuck on screen.
|
|
1264
|
+
//
|
|
1265
|
+
// canSwipeToDismiss requires a popup and vetoes `mustShow`, which is what
|
|
1266
|
+
// keeps a compliance takeover — showClose:false for the OPPOSITE reason —
|
|
1267
|
+
// from ever becoming flickable.
|
|
1268
|
+
const swipeable = (0, survey_spec_1.canSwipeToDismiss)(display, survey.delivery);
|
|
1269
|
+
const swipeXY = (0, react_1.useRef)(new react_native_1.Animated.ValueXY({ x: 0, y: 0 })).current;
|
|
1270
|
+
const swipeResponder = (0, react_1.useMemo)(() => react_native_1.PanResponder.create({
|
|
1271
|
+
onStartShouldSetPanResponder: () => false,
|
|
1272
|
+
// Claim the gesture only once it is clearly a drag, so a tap on the action
|
|
1273
|
+
// button still presses the button.
|
|
1274
|
+
onMoveShouldSetPanResponder: (_e, g) => swipeable && (Math.abs(g.dx) > 8 || Math.abs(g.dy) > 8),
|
|
1275
|
+
onPanResponderMove: (_e, g) => {
|
|
1276
|
+
const horizontal = Math.abs(g.dx) > Math.abs(g.dy);
|
|
1277
|
+
// A top banner travels up, never down — dragging it down is a pull.
|
|
1278
|
+
const dy = placement === 'top' ? Math.min(0, g.dy) : placement === 'bottom' ? Math.max(0, g.dy) : g.dy;
|
|
1279
|
+
swipeXY.setValue(horizontal ? { x: g.dx, y: 0 } : { x: 0, y: dy });
|
|
1280
|
+
},
|
|
1281
|
+
onPanResponderRelease: (_e, g) => {
|
|
1282
|
+
const horizontal = Math.abs(g.dx) > Math.abs(g.dy);
|
|
1283
|
+
const moved = horizontal ? g.dx : g.dy;
|
|
1284
|
+
const fast = Math.abs(horizontal ? g.vx : g.vy) > 0.5;
|
|
1285
|
+
if (Math.abs(moved) >= 56 || fast) {
|
|
1286
|
+
const to = moved < 0 ? -SCREEN_HEIGHT : SCREEN_HEIGHT;
|
|
1287
|
+
react_native_1.Animated.timing(swipeXY, {
|
|
1288
|
+
toValue: horizontal ? { x: moved < 0 ? -SCREEN_WIDTH : SCREEN_WIDTH, y: 0 } : { x: 0, y: to },
|
|
1289
|
+
duration: 180, useNativeDriver: true,
|
|
1290
|
+
}).start(() => { swipeXY.setValue({ x: 0, y: 0 }); handleDismissRef.current(); });
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
react_native_1.Animated.spring(swipeXY, { toValue: { x: 0, y: 0 }, useNativeDriver: true, bounciness: 6 }).start();
|
|
1294
|
+
},
|
|
1295
|
+
}), [swipeable, placement, swipeXY]);
|
|
1296
|
+
// Auto-dismiss. Same rule as web (autoDismissMs vetoes fullscreen and
|
|
1297
|
+
// mustShow, clamps to 2s). Cancelled for good once the respondent touches
|
|
1298
|
+
// anything — a timer that fires after someone has started answering would
|
|
1299
|
+
// pull the survey out from under them.
|
|
1300
|
+
// Which ending this interaction gets — see the render block below.
|
|
1301
|
+
const endingKind = (0, react_1.useMemo)(() => {
|
|
1302
|
+
const sv = survey;
|
|
1303
|
+
const qs = (survey.rawQuestions ?? []);
|
|
1304
|
+
const acts = (0, survey_spec_1.impliedStepActions)(qs) || [];
|
|
1305
|
+
if (!acts.length)
|
|
1306
|
+
return 'thanks';
|
|
1307
|
+
const last = qs[qs.length - 1];
|
|
1308
|
+
const id = last?.name ?? last?.id ?? '';
|
|
1309
|
+
const action = id ? (responses[id] ?? null) : null;
|
|
1310
|
+
return (0, survey_spec_1.outcomeFor)({
|
|
1311
|
+
interactionType: sv.interactionType, action,
|
|
1312
|
+
affirmative: String(action) === String(acts[0].value),
|
|
1313
|
+
reward: sv.reward, compliance: sv.compliance,
|
|
1314
|
+
});
|
|
1315
|
+
}, [survey, responses]);
|
|
1316
|
+
const dismissMs = (0, survey_spec_1.autoDismissMs)(display, survey.delivery);
|
|
1317
|
+
const engagedRef = (0, react_1.useRef)(false);
|
|
1318
|
+
(0, react_1.useEffect)(() => {
|
|
1319
|
+
if (!visible || !dismissMs)
|
|
1320
|
+
return;
|
|
1321
|
+
engagedRef.current = false;
|
|
1322
|
+
const t = setTimeout(() => {
|
|
1323
|
+
if (!engagedRef.current)
|
|
1324
|
+
handleDismissRef.current();
|
|
1325
|
+
}, dismissMs);
|
|
1326
|
+
return () => clearTimeout(t);
|
|
1327
|
+
}, [visible, dismissMs]);
|
|
1141
1328
|
const enterStyle = needsTopSlide
|
|
1142
1329
|
? {
|
|
1143
1330
|
opacity: enterAnim,
|
|
@@ -1293,10 +1480,11 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1293
1480
|
// A sheet sits flush against its edge, so the overlay loses its gutter.
|
|
1294
1481
|
isSheet && { paddingHorizontal: 0, paddingTop: 0, paddingBottom: 0 },
|
|
1295
1482
|
isFullscreen && styles.overlayFullscreen,
|
|
1296
|
-
], behavior: react_native_1.Platform.OS === 'ios' ? 'padding' : 'height', children: [(0, jsx_runtime_1.jsxs)(react_native_1.Animated.View, { style: [
|
|
1483
|
+
], behavior: react_native_1.Platform.OS === 'ios' ? 'padding' : 'height', children: [(0, jsx_runtime_1.jsxs)(react_native_1.Animated.View, { onTouchStart: () => { engagedRef.current = true; }, ...(swipeable ? swipeResponder.panHandlers : {}), style: [
|
|
1297
1484
|
styles.card,
|
|
1298
1485
|
themeCard,
|
|
1299
1486
|
enterStyle,
|
|
1487
|
+
swipeable && { transform: swipeXY.getTranslateTransform() },
|
|
1300
1488
|
maxWidth !== undefined && !isFullscreen && { width: Math.min(SCREEN_WIDTH - 40, maxWidth), maxWidth },
|
|
1301
1489
|
cornerRadius !== undefined && !isFullscreen && { borderRadius: cornerRadius },
|
|
1302
1490
|
// cardFullscreen fills; an explicit content height/width below OVERRIDES
|
|
@@ -1309,7 +1497,24 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1309
1497
|
], children: [display?.showClose !== false && ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: [
|
|
1310
1498
|
styles.closeButton,
|
|
1311
1499
|
isRtl ? { left: 12 } : { right: 12 },
|
|
1312
|
-
], onPress: handleDismiss, activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.closeText, themeTextSecondary], children: "\u2715" }) })), showThankYou ? (() => {
|
|
1500
|
+
], onPress: handleDismiss, activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.closeText, themeTextSecondary], children: "\u2715" }) })), showThankYou && endingKind !== 'thanks' ? (() => {
|
|
1501
|
+
const rw = (0, survey_spec_1.rewardToShow)(survey);
|
|
1502
|
+
const comp = survey.compliance;
|
|
1503
|
+
if (endingKind === 'fulfil' && rw) {
|
|
1504
|
+
const exp = rw.expiresAt ? new Date(rw.expiresAt) : null;
|
|
1505
|
+
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: "It\u2019s yours" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary], children: "Use this at checkout." }), (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()] }))] }));
|
|
1506
|
+
}
|
|
1507
|
+
if (endingKind === 'hold') {
|
|
1508
|
+
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
|
|
1509
|
+
?? (comp?.reviewRequired
|
|
1510
|
+
? 'We’re checking your details. You’ll be able to continue once that’s done.'
|
|
1511
|
+
: 'You need to accept this before you can continue.') })] }));
|
|
1512
|
+
}
|
|
1513
|
+
if (endingKind === 'unblock') {
|
|
1514
|
+
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." })] }));
|
|
1515
|
+
}
|
|
1516
|
+
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.' }) }));
|
|
1517
|
+
})() : showThankYou ? (() => {
|
|
1313
1518
|
// The thank-you message can be rich HTML from the web builder.
|
|
1314
1519
|
// Pull out an embedded <img> (often a data: URI icon) and render
|
|
1315
1520
|
// it as a real Image — stripHtml would otherwise drop it — then
|
|
@@ -1392,7 +1597,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1392
1597
|
// Right-align parity with web: Back hugs the left edge while
|
|
1393
1598
|
// Next/Submit sits on the right (web uses margin-right:auto).
|
|
1394
1599
|
buttonAlign === 'right' && { marginRight: 'auto' },
|
|
1395
|
-
], onPress: handleBack, activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.backButtonText, fontStyle, themeTextSecondary, buttonFS], children: "Back" }) })), (() => {
|
|
1600
|
+
], 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 : (() => {
|
|
1396
1601
|
// Reflect the actual outcome of pressing Next: if a
|
|
1397
1602
|
// branch resolves to __end__ we're submitting even
|
|
1398
1603
|
// when we're not on the last page. Cheap to evaluate
|
|
@@ -1606,6 +1811,18 @@ const makeStyles = (c) => react_native_1.StyleSheet.create({
|
|
|
1606
1811
|
// "Please complete on desktop" hint shown for question types that
|
|
1607
1812
|
// require heavy native UX (matrix, panelDynamic, signature, etc.).
|
|
1608
1813
|
// Phase 4.5 will bring native implementations for the priority ones.
|
|
1814
|
+
// Implied actions: the buttons ARE the answer, so they get real button
|
|
1815
|
+
// weight rather than looking like an option list.
|
|
1816
|
+
impliedActions: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginTop: 4 },
|
|
1817
|
+
impliedBtn: {
|
|
1818
|
+
flexGrow: 1, flexBasis: 140, paddingVertical: 13, paddingHorizontal: 18,
|
|
1819
|
+
borderRadius: 8, alignItems: 'center', borderWidth: 1, borderColor: 'transparent',
|
|
1820
|
+
},
|
|
1821
|
+
impliedPrimary: { backgroundColor: '#2b6cb0' },
|
|
1822
|
+
impliedSecondary: { backgroundColor: 'transparent', borderColor: '#d4d4d4' },
|
|
1823
|
+
impliedChosen: { opacity: 0.75 },
|
|
1824
|
+
impliedPrimaryLabel: { color: '#fff', fontWeight: '600', fontSize: 15 },
|
|
1825
|
+
impliedSecondaryLabel: { color: '#1a1a1a', fontWeight: '600', fontSize: 15 },
|
|
1609
1826
|
desktopHint: {
|
|
1610
1827
|
color: c.hintText,
|
|
1611
1828
|
fontSize: 13,
|
|
@@ -1691,5 +1908,22 @@ const makeStyles = (c) => react_native_1.StyleSheet.create({
|
|
|
1691
1908
|
textAlign: 'center',
|
|
1692
1909
|
lineHeight: 26,
|
|
1693
1910
|
},
|
|
1911
|
+
thankYouSub: {
|
|
1912
|
+
fontSize: 14,
|
|
1913
|
+
textAlign: 'center',
|
|
1914
|
+
lineHeight: 20,
|
|
1915
|
+
marginTop: 6,
|
|
1916
|
+
},
|
|
1917
|
+
// Selectable so the code can be copied by long-press — there is no clipboard
|
|
1918
|
+
// button here because RN's clipboard is an optional peer, and a code you can
|
|
1919
|
+
// select is better than a button that may not exist.
|
|
1920
|
+
rewardCode: {
|
|
1921
|
+
fontSize: 22,
|
|
1922
|
+
fontWeight: '700',
|
|
1923
|
+
letterSpacing: 1.5,
|
|
1924
|
+
textAlign: 'center',
|
|
1925
|
+
marginTop: 14,
|
|
1926
|
+
marginBottom: 10,
|
|
1927
|
+
},
|
|
1694
1928
|
});
|
|
1695
1929
|
//# sourceMappingURL=SurveyModal.js.map
|