@cxpinsight/react-native 0.5.0 → 0.8.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 (46) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +23 -0
  3. package/lib/CXPinsight.d.ts +33 -3
  4. package/lib/CXPinsight.d.ts.map +1 -1
  5. package/lib/CXPinsight.js +57 -3
  6. package/lib/CXPinsight.js.map +1 -1
  7. package/lib/CXPinsightProvider.d.ts.map +1 -1
  8. package/lib/CXPinsightProvider.js +13 -1
  9. package/lib/CXPinsightProvider.js.map +1 -1
  10. package/lib/consent/ConsentBanner.d.ts +18 -0
  11. package/lib/consent/ConsentBanner.d.ts.map +1 -0
  12. package/lib/consent/ConsentBanner.js +90 -0
  13. package/lib/consent/ConsentBanner.js.map +1 -0
  14. package/lib/core/consent.d.ts +16 -1
  15. package/lib/core/consent.d.ts.map +1 -1
  16. package/lib/core/consent.js +79 -32
  17. package/lib/core/consent.js.map +1 -1
  18. package/lib/index.d.ts +4 -0
  19. package/lib/index.d.ts.map +1 -1
  20. package/lib/index.js +5 -1
  21. package/lib/index.js.map +1 -1
  22. package/lib/survey/SurveyModal.d.ts.map +1 -1
  23. package/lib/survey/SurveyModal.js +94 -55
  24. package/lib/survey/SurveyModal.js.map +1 -1
  25. package/lib/survey/pagesFromV1.d.ts.map +1 -1
  26. package/lib/survey/pagesFromV1.js +30 -76
  27. package/lib/survey/pagesFromV1.js.map +1 -1
  28. package/lib/survey/questions/FileUploadQuestion.d.ts +32 -0
  29. package/lib/survey/questions/FileUploadQuestion.d.ts.map +1 -0
  30. package/lib/survey/questions/FileUploadQuestion.js +105 -0
  31. package/lib/survey/questions/FileUploadQuestion.js.map +1 -0
  32. package/lib/survey/questions/SignatureQuestion.d.ts +15 -0
  33. package/lib/survey/questions/SignatureQuestion.d.ts.map +1 -0
  34. package/lib/survey/questions/SignatureQuestion.js +138 -0
  35. package/lib/survey/questions/SignatureQuestion.js.map +1 -0
  36. package/lib/survey/validate.d.ts.map +1 -1
  37. package/lib/survey/validate.js +13 -163
  38. package/lib/survey/validate.js.map +1 -1
  39. package/lib/survey/voiceParsers.d.ts +12 -42
  40. package/lib/survey/voiceParsers.d.ts.map +1 -1
  41. package/lib/survey/voiceParsers.js +60 -284
  42. package/lib/survey/voiceParsers.js.map +1 -1
  43. package/lib/types.d.ts +24 -0
  44. package/lib/types.d.ts.map +1 -1
  45. package/lib/types.js.map +1 -1
  46. package/package.json +12 -4
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConsentBanner = ConsentBanner;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ /**
6
+ * The consent banner — "Accept all / Customize", and behind Customize a toggle
7
+ * per category.
8
+ *
9
+ * The same two views and the same categories as the web SDK's, so a company
10
+ * shipping both channels asks its users one question, not two versions of one
11
+ * question. Only the rendering differs, which is the whole point of keeping the
12
+ * decision rules in core/consent.ts.
13
+ *
14
+ * OPT-IN, per app: `consentBanner: true` in init(). Most apps have their own
15
+ * privacy flow and call CXPinsight.consent(...) from it, and showing ours
16
+ * unasked would give their users two.
17
+ */
18
+ const react_1 = require("react");
19
+ const react_native_1 = require("react-native");
20
+ const consent_1 = require("../core/consent");
21
+ /** What each category means in the user's terms, not ours. */
22
+ const COPY = {
23
+ analytics: {
24
+ label: 'Product analytics',
25
+ hint: 'Which screens you open and what you tap, so we can see what needs fixing.',
26
+ },
27
+ recording: {
28
+ label: 'Session replay',
29
+ hint: 'A timeline of your visit. Anything you type is hidden.',
30
+ },
31
+ network: {
32
+ label: 'Performance',
33
+ hint: 'How long our own requests take. Never their contents.',
34
+ },
35
+ surveys: {
36
+ label: 'Feedback surveys',
37
+ hint: 'Lets us ask you a short question now and then.',
38
+ },
39
+ };
40
+ const all = (granted) => consent_1.CONSENT_CATEGORIES.reduce((m, c) => { m[c] = granted; return m; }, {});
41
+ function ConsentBanner({ visible, accent = '#2b6cb0', text = {}, onDecide }) {
42
+ const [customizing, setCustomizing] = (0, react_1.useState)(false);
43
+ const [choices, setChoices] = (0, react_1.useState)(() => all(false));
44
+ // Recomputed only when the accent changes, because StyleSheet.create on every
45
+ // render defeats the caching it exists for.
46
+ const s = (0, react_1.useMemo)(() => styles(accent), [accent]);
47
+ const decide = (map) => {
48
+ setCustomizing(false);
49
+ setChoices(all(false));
50
+ onDecide(map);
51
+ };
52
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Modal, { visible: visible, transparent: true, animationType: "fade",
53
+ // Android's back button declines rather than dismissing. A banner that
54
+ // can be waved away without answering collects data from someone who
55
+ // never agreed, so the dismissal has to mean something and "no" is the
56
+ // only safe reading.
57
+ onRequestClose: () => decide(all(false)), children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: s.backdrop, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: s.panel, accessibilityViewIsModal: true, accessibilityRole: "alert", children: !customizing ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: s.title, children: text.title ?? 'Your privacy choices' }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: s.body, children: text.body ?? 'We collect a little information to understand how the app is used. You choose what.' }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: s.actions, children: [(0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: [s.btn, s.accept], accessibilityRole: "button", onPress: () => decide(all(true)), children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: s.acceptLabel, children: text.acceptAll ?? 'Accept all' }) }), (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: [s.btn, s.secondary], accessibilityRole: "button", onPress: () => setCustomizing(true), children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: s.secondaryLabel, children: text.customize ?? 'Customize' }) })] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: s.title, children: text.customizeTitle ?? 'Choose what to allow' }), (0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { style: s.list, children: consent_1.CONSENT_CATEGORIES.map((c) => ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: s.row, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: s.rowText, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: s.label, children: COPY[c].label }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: s.hint, children: COPY[c].hint })] }), (0, jsx_runtime_1.jsx)(react_native_1.Switch, { value: choices[c], onValueChange: (v) => setChoices((prev) => ({ ...prev, [c]: v })), accessibilityLabel: COPY[c].label, trackColor: { true: accent, false: '#cfcfcf' } })] }, c))) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: s.actions, children: [(0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: [s.btn, s.accept], accessibilityRole: "button", onPress: () => decide(choices), children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: s.acceptLabel, children: text.save ?? 'Save choices' }) }), (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: [s.btn, s.secondary], accessibilityRole: "button", onPress: () => decide(all(false)), children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: s.secondaryLabel, children: text.rejectAll ?? 'Reject all' }) })] })] })) }) }) }));
58
+ }
59
+ const styles = (accent) => react_native_1.StyleSheet.create({
60
+ backdrop: {
61
+ flex: 1, backgroundColor: 'rgba(0,0,0,0.35)',
62
+ justifyContent: 'flex-end', padding: 16,
63
+ },
64
+ panel: {
65
+ backgroundColor: '#fff', borderRadius: 14, padding: 22,
66
+ maxHeight: '86%',
67
+ shadowColor: '#000', shadowOpacity: 0.28, shadowRadius: 20, shadowOffset: { width: 0, height: 12 },
68
+ elevation: 12,
69
+ },
70
+ title: { fontSize: 18, fontWeight: '600', color: '#1a1a1a', marginBottom: 8 },
71
+ body: { fontSize: 14, lineHeight: 21, color: '#5c5c5c', marginBottom: 18 },
72
+ actions: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 },
73
+ btn: {
74
+ flexGrow: 1, flexBasis: 130, paddingVertical: 12, paddingHorizontal: 18,
75
+ borderRadius: 8, alignItems: 'center', borderWidth: 1, borderColor: 'transparent',
76
+ },
77
+ accept: { backgroundColor: accent },
78
+ acceptLabel: { color: '#fff', fontWeight: '600', fontSize: 15 },
79
+ secondary: { backgroundColor: 'transparent', borderColor: '#d4d4d4' },
80
+ secondaryLabel: { color: '#1a1a1a', fontWeight: '600', fontSize: 15 },
81
+ list: { marginBottom: 18 },
82
+ row: {
83
+ flexDirection: 'row', alignItems: 'flex-start', gap: 12,
84
+ paddingVertical: 12, borderBottomWidth: react_native_1.StyleSheet.hairlineWidth, borderBottomColor: '#ececec',
85
+ },
86
+ rowText: { flex: 1 },
87
+ label: { fontSize: 14.5, fontWeight: '600', color: '#1a1a1a' },
88
+ hint: { fontSize: 13, lineHeight: 19, color: '#5c5c5c', marginTop: 2 },
89
+ });
90
+ //# sourceMappingURL=ConsentBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConsentBanner.js","sourceRoot":"","sources":["../../src/consent/ConsentBanner.tsx"],"names":[],"mappings":"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"}
@@ -1,4 +1,8 @@
1
1
  export type ConsentState = 'granted' | 'denied' | null;
2
+ /** Only what this SDK can actually do. Same list as the web SDK. */
3
+ export declare const CONSENT_CATEGORIES: readonly ['analytics', 'recording', 'network', 'surveys'];
4
+ export type ConsentCategory = (typeof CONSENT_CATEGORIES)[number];
5
+ export type ConsentMap = Record<ConsentCategory, boolean>;
2
6
  /** Restore any prior decision. Call once during init, before capture starts. */
3
7
  export declare function initConsent(requireConsent?: boolean): Promise<void>;
4
8
  /** Notified when consent flips, so the SDK can start or stop capture. */
@@ -9,11 +13,22 @@ export declare function setConsentHandler(cb: ((granted: boolean) => void) | nul
9
13
  * An explicit denial always wins, even when requireConsent is off — a host that
10
14
  * calls optOut() means it, regardless of how the SDK was configured.
11
15
  */
16
+ export declare function mayUse(category: ConsentCategory): boolean;
17
+ /** Kept for call sites that predate categories; analytics is the broadest. */
12
18
  export declare function mayCapture(): boolean;
13
19
  /** True when events should be held rather than sent (awaiting a decision). */
14
20
  export declare function isPending(): boolean;
15
21
  export declare function consentRequired(): boolean;
16
- export declare function getConsent(): ConsentState;
22
+ /**
23
+ * The per-category decision, or null when the visitor has not decided.
24
+ *
25
+ * Null rather than an all-false map: "declined" and "not asked yet" need
26
+ * different UI, and a banner that cannot tell them apart re-prompts someone who
27
+ * already said no.
28
+ */
29
+ export declare function getConsent(): ConsentMap | null;
30
+ /** Set some categories, leaving the rest as they are. Unknown keys are ignored. */
31
+ export declare function setConsent(partial: Partial<ConsentMap>): void;
17
32
  export declare function grantConsent(): void;
18
33
  export declare function revokeConsent(): void;
19
34
  /** Reset to "no decision yet" — used by CXPinsight.reset(). */
@@ -1 +1 @@
1
- {"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../../src/core/consent.ts"],"names":[],"mappings":"AA4BA,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;AAMvD,gFAAgF;AAChF,wBAAsB,WAAW,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzE;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAE/E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAGpC;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAED,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED,wBAAgB,UAAU,IAAI,YAAY,CAEzC;AAED,wBAAgB,YAAY,IAAI,IAAI,CAKnC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED,+DAA+D;AAC/D,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAGlD"}
1
+ {"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../../src/core/consent.ts"],"names":[],"mappings":"AAsCA,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEvD,oEAAoE;AACpE,eAAO,MAAM,kBAAkB,YAAI,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,CAAU,CAAC;AAC5F,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAClE,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AAS1D,gFAAgF;AAChF,wBAAsB,WAAW,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAczE;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAE/E;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAIzD;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAED,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,UAAU,GAAG,IAAI,CAM9C;AAED,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAiB7D;AAED,wBAAgB,YAAY,IAAI,IAAI,CAAqC;AACzE,wBAAgB,aAAa,IAAI,IAAI,CAAsC;AAE3E,+DAA+D;AAC/D,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAGlD"}
@@ -33,12 +33,15 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CONSENT_CATEGORIES = void 0;
36
37
  exports.initConsent = initConsent;
37
38
  exports.setConsentHandler = setConsentHandler;
39
+ exports.mayUse = mayUse;
38
40
  exports.mayCapture = mayCapture;
39
41
  exports.isPending = isPending;
40
42
  exports.consentRequired = consentRequired;
41
43
  exports.getConsent = getConsent;
44
+ exports.setConsent = setConsent;
42
45
  exports.grantConsent = grantConsent;
43
46
  exports.revokeConsent = revokeConsent;
44
47
  exports.clearConsent = clearConsent;
@@ -60,23 +63,48 @@ exports.clearConsent = clearConsent;
60
63
  * `requireConsent` is opt-in and defaults to false, so nothing changes for an
61
64
  * app that does not set it. With it on, capture stays OFF until the host grants.
62
65
  *
63
- * One deliberate difference from web, in the stricter direction. Web's gate
64
- * covers session recording and network capture but lets ordinary analytics
65
- * events through. Here, requireConsent also holds ANALYTICS events — they are
66
- * buffered in the event queue rather than dropped, so a visitor who accepts the
67
- * banner a few seconds in keeps their whole session rather than losing its
68
- * opening. Revoking clears that buffer instead of sending it.
66
+ * Consent is PER CATEGORY, on both channels. "Do you accept tracking?" is not
67
+ * one question: someone happy to be counted may not be happy to be recorded,
68
+ * and a single yes/no makes them refuse everything to refuse one thing.
69
+ *
70
+ * analytics screens, taps, scroll depth, custom events, lifecycle
71
+ * recording session replay (the mobile timeline recorder)
72
+ * network the shape of outbound requests: method, host, path, status
73
+ * surveys showing a survey and collecting the response
74
+ *
75
+ * There is deliberately no "push notifications" category — neither SDK has that
76
+ * capability, and a toggle for something that does nothing promises what the
77
+ * product cannot deliver.
78
+ *
79
+ * Analytics events are still BUFFERED rather than dropped while a decision is
80
+ * pending, so a visitor who accepts a few seconds in keeps the opening of their
81
+ * session. Revoking clears that buffer instead of sending it.
69
82
  */
70
83
  const types_1 = require("../types");
71
84
  const storage = __importStar(require("./storage"));
85
+ /** Only what this SDK can actually do. Same list as the web SDK. */
86
+ exports.CONSENT_CATEGORIES = ['analytics', 'recording', 'network', 'surveys'];
72
87
  let required = false;
73
- let state = null;
88
+ let categories = null;
74
89
  let onChange = null;
90
+ const allCategories = (granted) => exports.CONSENT_CATEGORIES.reduce((m, c) => { m[c] = granted; return m; }, {});
75
91
  /** Restore any prior decision. Call once during init, before capture starts. */
76
92
  async function initConsent(requireConsent) {
77
93
  required = requireConsent === true;
78
94
  const saved = await storage.get(types_1.STORAGE_KEYS.CONSENT);
79
- state = saved === 'granted' || saved === 'denied' ? saved : null;
95
+ // Migrate the old scalar. A device storing 'granted' had consented to
96
+ // everything the SDK then did; reading it as undecided would re-prompt
97
+ // someone who has already answered, and as denied would stop collection they
98
+ // agreed to.
99
+ if (saved === 'granted' || saved === 'denied') {
100
+ categories = allCategories(saved === 'granted');
101
+ }
102
+ else if (saved && typeof saved === 'object') {
103
+ categories = saved;
104
+ }
105
+ else {
106
+ categories = null;
107
+ }
80
108
  }
81
109
  /** Notified when consent flips, so the SDK can start or stop capture. */
82
110
  function setConsentHandler(cb) {
@@ -88,44 +116,63 @@ function setConsentHandler(cb) {
88
116
  * An explicit denial always wins, even when requireConsent is off — a host that
89
117
  * calls optOut() means it, regardless of how the SDK was configured.
90
118
  */
119
+ function mayUse(category) {
120
+ const decided = categories?.[category];
121
+ if (typeof decided === 'boolean')
122
+ return decided;
123
+ return !required;
124
+ }
125
+ /** Kept for call sites that predate categories; analytics is the broadest. */
91
126
  function mayCapture() {
92
- if (state === 'denied')
93
- return false;
94
- return !required || state === 'granted';
127
+ return mayUse('analytics');
95
128
  }
96
129
  /** True when events should be held rather than sent (awaiting a decision). */
97
130
  function isPending() {
98
- return required && state === null;
131
+ return required && categories === null;
99
132
  }
100
133
  function consentRequired() {
101
134
  return required;
102
135
  }
136
+ /**
137
+ * The per-category decision, or null when the visitor has not decided.
138
+ *
139
+ * Null rather than an all-false map: "declined" and "not asked yet" need
140
+ * different UI, and a banner that cannot tell them apart re-prompts someone who
141
+ * already said no.
142
+ */
103
143
  function getConsent() {
104
- return state;
105
- }
106
- function grantConsent() {
107
- if (state === 'granted')
108
- return;
109
- state = 'granted';
110
- void storage.set(types_1.STORAGE_KEYS.CONSENT, state);
111
- try {
112
- onChange?.(true);
113
- }
114
- catch { /* host callback must not break the gate */ }
144
+ if (!categories)
145
+ return null;
146
+ return exports.CONSENT_CATEGORIES.reduce((m, c) => {
147
+ m[c] = categories?.[c] === true;
148
+ return m;
149
+ }, {});
115
150
  }
116
- function revokeConsent() {
117
- if (state === 'denied')
118
- return;
119
- state = 'denied';
120
- void storage.set(types_1.STORAGE_KEYS.CONSENT, state);
121
- try {
122
- onChange?.(false);
151
+ /** Set some categories, leaving the rest as they are. Unknown keys are ignored. */
152
+ function setConsent(partial) {
153
+ const next = exports.CONSENT_CATEGORIES.reduce((m, c) => {
154
+ m[c] = typeof partial[c] === 'boolean' ? partial[c] : categories?.[c] === true;
155
+ return m;
156
+ }, {});
157
+ // Notify when the MAP changed, not when a summary boolean did. Comparing a
158
+ // single boolean would miss turning recording off while analytics stays on —
159
+ // the exact case categories exist for — while comparing nothing at all would
160
+ // fire on a repeated grant that changed nothing.
161
+ const changed = !categories || exports.CONSENT_CATEGORIES.some((c) => (categories?.[c] === true) !== next[c]);
162
+ categories = next;
163
+ void storage.set(types_1.STORAGE_KEYS.CONSENT, next);
164
+ if (changed) {
165
+ try {
166
+ onChange?.(exports.CONSENT_CATEGORIES.some((c) => next[c]));
167
+ }
168
+ catch { /* a host callback must not break the gate */ }
123
169
  }
124
- catch { /* best-effort */ }
125
170
  }
171
+ function grantConsent() { setConsent(allCategories(true)); }
172
+ function revokeConsent() { setConsent(allCategories(false)); }
126
173
  /** Reset to "no decision yet" — used by CXPinsight.reset(). */
127
174
  async function clearConsent() {
128
- state = null;
175
+ categories = null;
129
176
  await storage.remove(types_1.STORAGE_KEYS.CONSENT);
130
177
  }
131
178
  //# sourceMappingURL=consent.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"consent.js","sourceRoot":"","sources":["../../src/core/consent.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,oCAAwC;AACxC,MAAY,OAAO,sCAAkB;AAIrC,IAAI,QAAQ,GAAG,KAAK,CAAC;AACrB,IAAI,KAAK,GAAiB,IAAI,CAAC;AAC/B,IAAI,QAAQ,GAAwC,IAAI,CAAC;AAEzD,gFAAgF;AACzE,KAAK,sBAAsB,cAAwB;IACxD,QAAQ,GAAG,cAAc,KAAK,IAAI,CAAC;IACnC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAe,oBAAY,CAAC,OAAO,CAAC,CAAC;IACpE,KAAK,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACnE,CAAC;AAED,yEAAyE;AACzE,2BAAkC,EAAuC;IACvE,QAAQ,GAAG,EAAE,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH;IACE,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACrC,OAAO,CAAC,QAAQ,IAAI,KAAK,KAAK,SAAS,CAAC;AAC1C,CAAC;AAED,8EAA8E;AAC9E;IACE,OAAO,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACpC,CAAC;AAED;IACE,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;IACE,OAAO,KAAK,CAAC;AACf,CAAC;AAED;IACE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO;IAChC,KAAK,GAAG,SAAS,CAAC;IAClB,KAAK,OAAO,CAAC,GAAG,CAAC,oBAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC9C,IAAI,CAAC;QAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,2CAA2C,CAAC,CAAC;AACjF,CAAC;AAED;IACE,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO;IAC/B,KAAK,GAAG,QAAQ,CAAC;IACjB,KAAK,OAAO,CAAC,GAAG,CAAC,oBAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC9C,IAAI,CAAC;QAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;AACxD,CAAC;AAED,+DAA+D;AACxD,KAAK;IACV,KAAK,GAAG,IAAI,CAAC;IACb,MAAM,OAAO,CAAC,MAAM,CAAC,oBAAY,CAAC,OAAO,CAAC,CAAC;AAC7C,CAAC"}
1
+ {"version":3,"file":"consent.js","sourceRoot":"","sources":["../../src/core/consent.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  export { CXPinsight } from './CXPinsight';
2
2
  export { CXPinsightProvider } from './CXPinsightProvider';
3
+ export { ConsentBanner } from './consent/ConsentBanner';
4
+ export type { ConsentBannerProps, ConsentBannerText } from './consent/ConsentBanner';
5
+ export { CONSENT_CATEGORIES } from './core/consent';
6
+ export type { ConsentCategory, ConsentMap } from './core/consent';
3
7
  export type { CXPinsightConfig, UserIdentity, CXPinsightCallbacks, FunnelStepOptions, ExperimentOptions, ConversionOptions, } from './types';
4
8
  export { useCXPElement } from './setup/useCXPElement';
5
9
  export type { ElementInfo, StoredElementIdentifier } from './setup/ElementIdentifier';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACtF,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACtF,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
package/lib/index.js CHANGED
@@ -1,10 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useCXPElement = exports.CXPinsightProvider = exports.CXPinsight = void 0;
3
+ exports.useCXPElement = exports.CONSENT_CATEGORIES = exports.ConsentBanner = exports.CXPinsightProvider = exports.CXPinsight = void 0;
4
4
  var CXPinsight_1 = require("./CXPinsight");
5
5
  Object.defineProperty(exports, "CXPinsight", { enumerable: true, get: function () { return CXPinsight_1.CXPinsight; } });
6
6
  var CXPinsightProvider_1 = require("./CXPinsightProvider");
7
7
  Object.defineProperty(exports, "CXPinsightProvider", { enumerable: true, get: function () { return CXPinsightProvider_1.CXPinsightProvider; } });
8
+ var ConsentBanner_1 = require("./consent/ConsentBanner");
9
+ Object.defineProperty(exports, "ConsentBanner", { enumerable: true, get: function () { return ConsentBanner_1.ConsentBanner; } });
10
+ var consent_1 = require("./core/consent");
11
+ Object.defineProperty(exports, "CONSENT_CATEGORIES", { enumerable: true, get: function () { return consent_1.CONSENT_CATEGORIES; } });
8
12
  // Setup mode exports
9
13
  var useCXPElement_1 = require("./setup/useCXPElement");
10
14
  Object.defineProperty(exports, "useCXPElement", { enumerable: true, get: function () { return useCXPElement_1.useCXPElement; } });
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,2DAA0D;AAAjD,wHAAA,kBAAkB,OAAA;AAU3B,qBAAqB;AACrB,uDAAsD;AAA7C,8GAAA,aAAa,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,2DAA0D;AAAjD,wHAAA,kBAAkB,OAAA;AAC3B,yDAAwD;AAA/C,8GAAA,aAAa,OAAA;AAEtB,0CAAoD;AAA3C,6GAAA,kBAAkB,OAAA;AAW3B,qBAAqB;AACrB,uDAAsD;AAA7C,8GAAA,aAAa,OAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"SurveyModal.d.ts","sourceRoot":"","sources":["../../src/survey/SurveyModal.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,UAAU,CAAC;AA+BjE,UAAU,KAAK;IACb,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IAIjB,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACjF,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAgCD,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,KAAK,2CAmnD1E"}
1
+ {"version":3,"file":"SurveyModal.d.ts","sourceRoot":"","sources":["../../src/survey/SurveyModal.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,UAAU,CAAC;AAiCjE,UAAU,KAAK;IACb,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IAIjB,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACjF,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAgCD,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,KAAK,2CAuqD1E"}
@@ -51,6 +51,8 @@ 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 SignatureQuestion_1 = require("./questions/SignatureQuestion");
55
+ const FileUploadQuestion_1 = require("./questions/FileUploadQuestion");
54
56
  const MultiTextQuestion_1 = require("./questions/MultiTextQuestion");
55
57
  const expressionEval_1 = require("./expressionEval");
56
58
  const interpolate_1 = require("./interpolate");
@@ -500,18 +502,35 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
500
502
  function renderQuestion(question) {
501
503
  const key = question.id || question.name;
502
504
  const val = responses[key];
503
- switch (question.type) {
504
- case 'nps':
505
+ // Shown when a question type needs an optional peer the host app did not
506
+ // install. Not a failure: the respondent can still finish the survey on a
507
+ // device that has it, which is better than a blank space or a crash.
508
+ const renderDesktopHint = () => ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.desktopHint, children: "This question type isn't supported on mobile yet \u2014 please complete this survey on desktop." }));
509
+ // ── Question renderers ────────────────────────────────────────────
510
+ //
511
+ // One entry per question type instead of a switch, matching the shape the
512
+ // React renderer uses: adding a type is one entry plus one view component.
513
+ //
514
+ // Built inside the component ON PURPOSE. Every body below closes over
515
+ // `responses`, `updateResponse`, `contract`, `styles`, `t` and the two
516
+ // local helpers; hoisting the table to module scope would mean threading
517
+ // all of that through a context object and rewriting 290 lines of JSX to
518
+ // read from it — a large edit with nothing to verify it against. Rebuilding
519
+ // an object literal per render costs nothing measurable next to the tree it
520
+ // returns.
521
+ const RENDERERS = {
522
+ 'nps': () => {
505
523
  return ((0, jsx_runtime_1.jsx)(NPSQuestion_1.NPSQuestion, { value: val ?? null, onChange: (v) => updateResponse(key, v), contract: contract }));
506
- case 'rating':
507
- case 'csat':
508
- case 'ces':
524
+ },
525
+ 'rating': () => {
509
526
  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 }));
510
- case 'comment':
527
+ },
528
+ 'csat': () => RENDERERS['rating'](),
529
+ 'ces': () => RENDERERS['rating'](),
530
+ 'comment': () => {
511
531
  return ((0, jsx_runtime_1.jsx)(TextQuestion_1.TextQuestion, { value: val ?? '', onChange: (v) => updateResponse(key, v), placeholder: question.placeholder, multiline: true, contract: contract }));
512
- case 'text':
513
- case 'email':
514
- case 'number':
532
+ },
533
+ 'text': () => {
515
534
  return ((0, jsx_runtime_1.jsx)(TextQuestion_1.TextQuestion, { value: val ?? '', onChange: (v) => updateResponse(key, v), placeholder: question.placeholder, multiline: false, contract: contract,
516
535
  // Two shapes reach here and both must produce the right keyboard:
517
536
  // the legacy one where the TYPE is 'email'/'number', and the current
@@ -519,20 +538,26 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
519
538
  // "Answer format" picker. Fall back to the type so the legacy shape
520
539
  // keeps working.
521
540
  inputType: question.inputType ?? (question.type === 'text' ? undefined : question.type) }));
522
- case 'radio':
523
- case 'radiogroup':
524
- case 'dropdown':
541
+ },
542
+ 'email': () => RENDERERS['text'](),
543
+ 'number': () => RENDERERS['text'](),
544
+ 'radio': () => {
525
545
  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 }));
526
- case 'checkbox':
546
+ },
547
+ 'radiogroup': () => RENDERERS['radio'](),
548
+ 'dropdown': () => RENDERERS['radio'](),
549
+ 'checkbox': () => {
527
550
  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 }));
528
- case 'binary':
529
- case 'boolean':
530
- case 'yesno':
551
+ },
552
+ 'binary': () => {
531
553
  return ((0, jsx_runtime_1.jsx)(MultipleChoiceQuestion_1.MultipleChoiceQuestion, { choices: [
532
554
  { value: 'true', text: question.yesLabel || 'Yes' },
533
555
  { value: 'false', text: question.noLabel || 'No' },
534
556
  ], value: val ?? '', onChange: (v) => updateResponse(key, v), mode: "radiogroup", isRtl: isRtl, contract: contract }));
535
- case 'date':
557
+ },
558
+ 'boolean': () => RENDERERS['binary'](),
559
+ 'yesno': () => RENDERERS['binary'](),
560
+ 'date': () => {
536
561
  // Native picker when the optional peer dep
537
562
  // (@react-native-community/datetimepicker) is installed; falls
538
563
  // back to a text input with an ISO-8601 hint otherwise. Consumer
@@ -542,18 +567,18 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
542
567
  return ((0, jsx_runtime_1.jsx)(DateQuestion_1.DateQuestion, { value: val ?? '', onChange: (v) => updateResponse(key, v), min: typeof question.min === 'string' ? question.min : undefined, max: typeof question.max === 'string' ? question.max : undefined, placeholder: question.placeholder || 'Select a date', contract: contract, isRtl: isRtl }));
543
568
  }
544
569
  return ((0, jsx_runtime_1.jsx)(TextQuestion_1.TextQuestion, { value: val ?? '', onChange: (v) => updateResponse(key, v), placeholder: question.placeholder || 'YYYY-MM-DD', multiline: false, contract: contract }));
545
- case 'content':
546
- case 'html':
547
- case 'description':
548
- case 'pageBreak':
549
- case 'expression':
570
+ },
571
+ 'content': () => {
550
572
  // Display-only — the title/description block already covers
551
573
  // section headers and static content. pageBreak items are
552
574
  // filtered out of pages before we get here; guarding anyway.
553
575
  return null;
554
- case 'multiText':
555
- case 'multi_text':
556
- case 'multipletext': {
576
+ },
577
+ 'html': () => RENDERERS['content'](),
578
+ 'description': () => RENDERERS['content'](),
579
+ 'pageBreak': () => RENDERERS['content'](),
580
+ 'expression': () => RENDERERS['content'](),
581
+ 'multiText': () => {
557
582
  // Multi-text — labeled sub-inputs, one answer object keyed by item
558
583
  // id. Matches the web renderer's MultiTextRenderer shape. Note the
559
584
  // builder emits camelCase 'multiText'; the earlier snake_case-only
@@ -561,10 +586,13 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
561
586
  // raw (unlike SurveyModal's other type-family switches which lower).
562
587
  const items = (Array.isArray(question.items) ? question.items : []);
563
588
  return ((0, jsx_runtime_1.jsx)(MultiTextQuestion_1.MultiTextQuestion, { items: items, value: val ?? {}, onChange: (v) => updateResponse(key, v), contract: contract, isRtl: isRtl }));
564
- }
565
- case 'ranking':
589
+ },
590
+ 'multi_text': () => RENDERERS['multiText'](),
591
+ 'multipletext': () => RENDERERS['multiText'](),
592
+ 'ranking': () => {
566
593
  return ((0, jsx_runtime_1.jsx)(RankingQuestion_1.RankingQuestion, { options: question.options || [], value: val ?? null, onChange: (v) => updateResponse(key, v), contract: contract, isRtl: isRtl }));
567
- case 'matrix':
594
+ },
595
+ 'matrix': () => {
568
596
  return ((0, jsx_runtime_1.jsx)(MatrixQuestion_1.MatrixQuestion
569
597
  /* SurveyQuestion.rows is typed `number` (textarea line count) and
570
598
  `columns` isn't on the type at all — matrix was never modelled on
@@ -580,50 +608,61 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
580
608
  them through a shape-specific view; MatrixQuestion guards with
581
609
  Array.isArray, so a wrong shape renders empty rather than throws. */
582
610
  rows: (question.rows) || [], columns: (question.columns) || [], value: val ?? null, onChange: (v) => updateResponse(key, v), contract: contract, isRtl: isRtl }));
583
- case 'image': {
611
+ },
612
+ 'image': () => {
584
613
  // Display-only; collects no answer. Reads through a shape-specific view
585
614
  // because these fields aren't on SurveyQuestion (same gap as matrix).
586
615
  const img = question;
587
616
  return ((0, jsx_runtime_1.jsx)(ImageQuestion_1.ImageQuestion, { imageUrl: img.imageUrl, alt: img.alt, width: img.width, height: img.height, contract: contract }));
588
- }
589
- case 'image_picker':
590
- case 'imagePicker': {
617
+ },
618
+ 'image_picker': () => {
591
619
  const ip = question;
592
620
  return ((0, jsx_runtime_1.jsx)(ImagePickerQuestion_1.ImagePickerQuestion, { options: question.options || [], multiSelect: ip.multiSelect, value: val, onChange: (v) => updateResponse(key, v), contract: contract, isRtl: isRtl }));
593
- }
621
+ },
622
+ 'imagePicker': () => RENDERERS['image_picker'](),
594
623
  // The server's canonical names are camelCase (survey.routes.js:77-81).
595
624
  // Only the snake_case aliases were listed here, so these cases never
596
625
  // matched a real payload and fell through to `default` — which renders a
597
626
  // TEXT INPUT. A signature or file-upload question showed a text box the
598
627
  // respondent could type anything into. Both spellings are handled now.
599
- case 'matrixDropdown':
600
- case 'matrix_dropdown': {
628
+ 'matrixDropdown': () => {
601
629
  const md = question;
602
630
  return ((0, jsx_runtime_1.jsx)(MatrixDropdownQuestion_1.MatrixDropdownQuestion, { rows: md.rows || [], columns: md.columns || [], value: val ?? null, onChange: (v) => updateResponse(key, v), contract: contract, isRtl: isRtl }));
603
- }
604
- case 'matrixDynamic':
605
- case 'matrix_dynamic': {
631
+ },
632
+ 'matrix_dropdown': () => RENDERERS['matrixDropdown'](),
633
+ 'matrixDynamic': () => {
606
634
  const mdy = question;
607
635
  return ((0, jsx_runtime_1.jsx)(MatrixDynamicQuestion_1.MatrixDynamicQuestion, { columns: mdy.columns || [], value: val ?? null, onChange: (v) => updateResponse(key, v), minRowCount: mdy.minRowCount, maxRowCount: mdy.maxRowCount, addRowLabel: mdy.addRowLabel, removeRowLabel: mdy.removeRowLabel, contract: contract, isRtl: isRtl }));
608
- }
609
- case 'panelDynamic':
610
- case 'panel_dynamic': {
636
+ },
637
+ 'matrix_dynamic': () => RENDERERS['matrixDynamic'](),
638
+ 'panelDynamic': () => {
611
639
  const pd = question;
612
640
  return ((0, jsx_runtime_1.jsx)(PanelDynamicQuestion_1.PanelDynamicQuestion, { template: pd.template, value: val ?? null, onChange: (v) => updateResponse(key, v), minPanelCount: pd.minPanelCount, maxPanelCount: pd.maxPanelCount, addPanelLabel: pd.addPanelLabel, removePanelLabel: pd.removePanelLabel, panelTitle: pd.panelTitle, contract: contract, isRtl: isRtl }));
613
- }
614
- case 'signature':
615
- case 'fileUpload':
616
- case 'file_upload':
617
- // Phase 4.5 targets. Native mobile UX for these is heavy
618
- // (multi-column grids on small screens, camera integration,
619
- // canvas). Graceful degrade to a "please complete on
620
- // desktop" hint per ADR §12.
621
- return ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.desktopHint, children: "This question type isn't supported on mobile yet \u2014 please complete this survey on desktop." }));
622
- default:
623
- // Unknown type — fall back to a text input so the survey
624
- // still runs. Server-side validation will reject any junk.
625
- return ((0, jsx_runtime_1.jsx)(TextQuestion_1.TextQuestion, { value: val ?? '', onChange: (v) => updateResponse(key, v), placeholder: question.placeholder, contract: contract }));
626
- }
641
+ },
642
+ 'panel_dynamic': () => RENDERERS['panelDynamic'](),
643
+ // Both of these need an optional peer. When the host app installed it
644
+ // they render for real; when it did not, they fall back to the hint that
645
+ // used to be the ONLY behaviour. Nothing is forced into an app's bundle
646
+ // for a question type it never asks.
647
+ 'signature': () => {
648
+ if (!(0, SignatureQuestion_1.isSignatureAvailable)())
649
+ return renderDesktopHint();
650
+ return ((0, jsx_runtime_1.jsx)(SignatureQuestion_1.SignatureQuestion, { value: val ?? '', onChange: (v) => updateResponse(key, v || null), contract: contract }));
651
+ },
652
+ 'fileUpload': () => {
653
+ if (!(0, FileUploadQuestion_1.isFileUploadAvailable)())
654
+ return renderDesktopHint();
655
+ return ((0, jsx_runtime_1.jsx)(FileUploadQuestion_1.FileUploadQuestion, { value: val ?? null, onChange: (v) => updateResponse(key, v), multiple: question.multiple === true, accept: question.accept, contract: contract }));
656
+ },
657
+ 'file_upload': () => RENDERERS['fileUpload'](),
658
+ };
659
+ const renderUnsupported = () => {
660
+ // Unknown type — fall back to a text input so the survey
661
+ // still runs. Server-side validation will reject any junk.
662
+ return ((0, jsx_runtime_1.jsx)(TextQuestion_1.TextQuestion, { value: val ?? '', onChange: (v) => updateResponse(key, v), placeholder: question.placeholder, contract: contract }));
663
+ };
664
+ const _fn = RENDERERS[String(question.type || '')] || renderUnsupported;
665
+ return _fn();
627
666
  }
628
667
  // Device-conditional palette variations. Author defines an ordered list
629
668
  // of `{when: [{attr, op, value}], overrides: {theme?, primaryColor?}}`;