@explorer02/cfm-survey-sdk 0.3.9 → 0.4.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/dist/cli/index.js +64 -64
- package/dist/cli/index.mjs +63 -63
- package/package.json +1 -1
- package/templates/docs/templates/CsatMatrixScale.tsx +105 -36
- package/templates/docs/templates/CustomSliderTrack.tsx +2 -2
- package/templates/docs/templates/Footer.tsx +3 -2
- package/templates/docs/templates/Header.tsx +14 -14
- package/templates/docs/templates/LikertMatrixScale.tsx +2 -2
- package/templates/docs/templates/Question.tsx +30 -23
- package/templates/docs/templates/RankOrderScale.tsx +10 -2
- package/templates/docs/templates/SliderMatrixScale.tsx +1 -1
- package/templates/docs/templates/labelStyles.ts +27 -0
- package/templates/docs/templates/uiConfig.ts +100 -0
- package/templates/preview-harness/preview-bridge.inline.js +55 -17
- package/templates/preview-harness/previewPages.js +2 -2
- package/templates/preview-harness/previewPages.ts +1 -1
- package/templates/preview-harness/vite-app/src/PreviewConfigContext.tsx +60 -7
- package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +17 -96
- package/templates/preview-harness/vite-app/src/SurveyPagePreview.tsx +32 -8
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +53 -25
- package/templates/preview-harness/vite-app/src/globals.css +6 -0
- package/templates/preview-harness/vite-app/src/mergePreviewQuestion.ts +233 -0
- package/templates/preview-harness/vite-app/src/preview-live-overrides.css +30 -0
- package/templates/previewBridge.ts +61 -18
- package/templates/survey-theme.css +16 -0
- package/templates/wizard-dist/assets/{PreviewMock-CS4WqhDB.js → PreviewMock-MsONyaq9.js} +1 -1
- package/templates/wizard-dist/assets/TypePanel-D24BxbHk.js +1 -0
- package/templates/wizard-dist/assets/index-C7RSJr09.js +34 -0
- package/templates/wizard-dist/index.html +1 -1
- package/templates/wizard-dist/assets/TypePanel-BgTW2c74.js +0 -1
- package/templates/wizard-dist/assets/index-hjm-fxWO.js +0 -34
|
@@ -17,10 +17,12 @@
|
|
|
17
17
|
delete pending[k];
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
applyThemeSideEffects();
|
|
21
|
+
syncCompanyNameVisibility();
|
|
22
|
+
syncQuestionChromeVisibility();
|
|
23
|
+
}
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
function queue(vars) {
|
|
24
26
|
for (var k in vars) {
|
|
25
27
|
if (Object.prototype.hasOwnProperty.call(vars, k)) pending[k] = vars[k];
|
|
26
28
|
}
|
|
@@ -34,10 +36,12 @@
|
|
|
34
36
|
root.style.setProperty(k, vars[k]);
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
applyThemeSideEffects();
|
|
40
|
+
syncCompanyNameVisibility();
|
|
41
|
+
syncQuestionChromeVisibility();
|
|
42
|
+
}
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
function setDisplay(selector, visible) {
|
|
41
45
|
document.querySelectorAll(selector).forEach(function (el) {
|
|
42
46
|
el.style.display = visible ? '' : 'none';
|
|
43
47
|
});
|
|
@@ -81,6 +85,46 @@
|
|
|
81
85
|
applyDropzoneStyle();
|
|
82
86
|
}
|
|
83
87
|
|
|
88
|
+
function isHeaderLogoVisible() {
|
|
89
|
+
var logoImg = document.querySelector('header [data-cfm-logo], .cfm-header [data-cfm-logo]');
|
|
90
|
+
return Boolean(
|
|
91
|
+
logoImg &&
|
|
92
|
+
logoImg.style.display !== 'none' &&
|
|
93
|
+
logoImg.getAttribute('src'),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function syncCompanyNameVisibility(layoutShowCompany) {
|
|
98
|
+
var showFromCss = readVar('--cfm-header-show-company', '1') !== '0';
|
|
99
|
+
var showToggle =
|
|
100
|
+
layoutShowCompany !== undefined ? !!layoutShowCompany : showFromCss;
|
|
101
|
+
|
|
102
|
+
document.querySelectorAll('[data-cfm-company]').forEach(function (el) {
|
|
103
|
+
var hasName = Boolean((el.textContent || '').trim());
|
|
104
|
+
var show = showToggle && hasName && isHeaderLogoVisible();
|
|
105
|
+
el.style.display = show ? '' : 'none';
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function syncQuestionChromeVisibility(layoutShowNumbers, layoutShowRequired) {
|
|
110
|
+
var showNumbers =
|
|
111
|
+
layoutShowNumbers !== undefined
|
|
112
|
+
? !!layoutShowNumbers
|
|
113
|
+
: readVar('--cfm-show-question-numbers', '1') !== '0';
|
|
114
|
+
var showRequired =
|
|
115
|
+
layoutShowRequired !== undefined
|
|
116
|
+
? !!layoutShowRequired
|
|
117
|
+
: readVar('--cfm-show-required-asterisk', '1') !== '0';
|
|
118
|
+
|
|
119
|
+
document.querySelectorAll('[data-cfm-question-number]').forEach(function (el) {
|
|
120
|
+
var hasNumber = Boolean((el.textContent || '').trim());
|
|
121
|
+
el.style.display = showNumbers && hasNumber ? '' : 'none';
|
|
122
|
+
});
|
|
123
|
+
document.querySelectorAll('[data-cfm-required]').forEach(function (el) {
|
|
124
|
+
el.style.display = showRequired ? '' : 'none';
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
84
128
|
function applyContent(patch) {
|
|
85
129
|
if (!patch) return;
|
|
86
130
|
if (patch.logoUrl !== undefined) {
|
|
@@ -103,19 +147,11 @@
|
|
|
103
147
|
el.classList.remove('hidden');
|
|
104
148
|
}
|
|
105
149
|
});
|
|
106
|
-
document.querySelectorAll('[data-cfm-company]').forEach(function (el) {
|
|
107
|
-
if (patch.logoUrl && !el.textContent) {
|
|
108
|
-
el.style.display = 'none';
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
150
|
}
|
|
112
151
|
if (patch.companyName !== undefined) {
|
|
113
152
|
var name = patch.companyName || '';
|
|
114
153
|
document.querySelectorAll('[data-cfm-company]').forEach(function (el) {
|
|
115
154
|
el.textContent = name;
|
|
116
|
-
if (!name) {
|
|
117
|
-
el.style.display = 'none';
|
|
118
|
-
}
|
|
119
155
|
});
|
|
120
156
|
document.querySelectorAll('[data-cfm-logo-text]').forEach(function (el) {
|
|
121
157
|
var logoImg = document.querySelector('[data-cfm-logo]');
|
|
@@ -176,7 +212,7 @@
|
|
|
176
212
|
if (patch.layoutFlags) {
|
|
177
213
|
var flags = patch.layoutFlags;
|
|
178
214
|
if (flags.showCompanyName !== undefined) {
|
|
179
|
-
|
|
215
|
+
syncCompanyNameVisibility(flags.showCompanyName);
|
|
180
216
|
}
|
|
181
217
|
if (flags.showHeader !== undefined) {
|
|
182
218
|
setDisplay('.cfm-header, header.cfm-header', flags.showHeader);
|
|
@@ -197,10 +233,10 @@
|
|
|
197
233
|
setDisplay('[data-cfm-btn-back]', flags.showBackButton);
|
|
198
234
|
}
|
|
199
235
|
if (flags.showQuestionNumbers !== undefined) {
|
|
200
|
-
|
|
236
|
+
syncQuestionChromeVisibility(flags.showQuestionNumbers, undefined);
|
|
201
237
|
}
|
|
202
238
|
if (flags.showRequiredAsterisk !== undefined) {
|
|
203
|
-
|
|
239
|
+
syncQuestionChromeVisibility(undefined, flags.showRequiredAsterisk);
|
|
204
240
|
}
|
|
205
241
|
if (flags.showFooterLogo !== undefined) {
|
|
206
242
|
setDisplay('[data-cfm-footer-logo]', flags.showFooterLogo);
|
|
@@ -234,6 +270,8 @@
|
|
|
234
270
|
applyMultiStatement(!!patch.previewState.multiStatement);
|
|
235
271
|
}
|
|
236
272
|
}
|
|
273
|
+
syncCompanyNameVisibility();
|
|
274
|
+
syncQuestionChromeVisibility();
|
|
237
275
|
applyThemeSideEffects();
|
|
238
276
|
}
|
|
239
277
|
|
|
@@ -97,8 +97,8 @@ const PREVIEW_PAGE_DEFS = [
|
|
|
97
97
|
{ key: 'TEXTFIELD_short', title: 'Text Short', body: `<p style="font-weight:600;margin-bottom:8px">Your name</p><input class="cfm-input" data-cfm-textfield placeholder="Type here..." />` },
|
|
98
98
|
{ key: 'TEXTFIELD_long', title: 'Text Long', body: `<p style="font-weight:600;margin-bottom:8px">Comments</p><textarea class="cfm-input" data-cfm-textfield style="height:120px;padding:12px" placeholder="Type your response..."></textarea>` },
|
|
99
99
|
{ key: 'NPS_SCALE', title: 'NPS', body: npsPreview() },
|
|
100
|
-
{ key: 'CFM_likert', title: 'CFM Likert', body: `${matrixPreview('Statement item 1', 'radio')}<div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label">Statement item 2</div><div class="cfm-matrix-cells">${cells(5)}</div></div>` },
|
|
101
|
-
{ key: 'CFM_bipolar', title: 'CFM Bipolar', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%)">
|
|
100
|
+
{ key: 'CFM_likert', title: 'CFM Likert', body: `${matrixPreview('Statement item 1', 'radio')}<div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label">Statement item 2</div><div class="cfm-matrix-cells">${cells(5)}</div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label">Statement item 3</div><div class="cfm-matrix-cells">${cells(5)}</div></div>` },
|
|
101
|
+
{ key: 'CFM_bipolar', title: 'CFM Bipolar', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%)">Statement 1</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%);text-align:right">Opposite 1</div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%)">Statement 2</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%);text-align:right">Opposite 2</div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%)">Statement 3</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%);text-align:right">Opposite 3</div></div>` },
|
|
102
102
|
{ key: 'CFM_transpose', title: 'CFM Transpose', body: matrixPreview('Scale item (transposed row)', 'radio') },
|
|
103
103
|
{ key: 'CFM_carousel', title: 'CFM Carousel', body: `${matrixPreview('Row 1 of 3', 'radio')}<div style="text-align:center;margin-top:12px"><span style="color:var(--cfm-matrix-selected)">●</span> ○ ○</div>` },
|
|
104
104
|
{ key: 'CFM_dropdown', title: 'CFM Dropdown', body: `<div class="cfm-matrix-label" style="width:100%;margin-bottom:8px">Statement 1</div><select class="cfm-input"><option>Select...</option><option>Col 1</option><option>Col 2</option></select>` },
|
|
@@ -141,7 +141,7 @@ export const PREVIEW_PAGE_DEFS: PreviewPageDef[] = [
|
|
|
141
141
|
{
|
|
142
142
|
key: 'CFM_bipolar',
|
|
143
143
|
title: 'CFM Bipolar',
|
|
144
|
-
body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width)">
|
|
144
|
+
body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width)">Statement 1</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width);text-align:right">Opposite 1</div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width)">Statement 2</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width);text-align:right">Opposite 2</div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width)">Statement 3</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width);text-align:right">Opposite 3</div></div>`,
|
|
145
145
|
},
|
|
146
146
|
{
|
|
147
147
|
key: 'CFM_transpose',
|
|
@@ -15,6 +15,7 @@ export type PreviewStatePatch = {
|
|
|
15
15
|
|
|
16
16
|
export const PREVIEW_BRIDGE_SNAPSHOT = 'CFM_UI_CONFIG_SNAPSHOT';
|
|
17
17
|
export const PREVIEW_BRIDGE_DELTA = 'CFM_UI_CONFIG_DELTA';
|
|
18
|
+
export const PREVIEW_BRIDGE_READY = 'CFM_PREVIEW_READY';
|
|
18
19
|
|
|
19
20
|
const TRAFFIC = [
|
|
20
21
|
'#ef4444', '#f97316', '#eab308', '#84cc16', '#22c55e', '#14b8a6',
|
|
@@ -51,6 +52,26 @@ function applyThemeSideEffects(): void {
|
|
|
51
52
|
applyDropzoneStyle();
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
function previewStateFromCssVars(
|
|
56
|
+
cssVars: Record<string, string>,
|
|
57
|
+
prev: PreviewStatePatch,
|
|
58
|
+
): PreviewStatePatch {
|
|
59
|
+
const next: PreviewStatePatch = { ...prev };
|
|
60
|
+
if (cssVars['--cfm-matrix-multi-statement'] !== undefined) {
|
|
61
|
+
next.multiStatement = cssVars['--cfm-matrix-multi-statement'] === '1';
|
|
62
|
+
}
|
|
63
|
+
if (cssVars['--cfm-nps-button-style']) {
|
|
64
|
+
next.npsButtonStyle = cssVars['--cfm-nps-button-style'];
|
|
65
|
+
}
|
|
66
|
+
if (cssVars['--cfm-hint-min-text']) {
|
|
67
|
+
next.hintMinText = cssVars['--cfm-hint-min-text'];
|
|
68
|
+
}
|
|
69
|
+
if (cssVars['--cfm-hint-max-text']) {
|
|
70
|
+
next.hintMaxText = cssVars['--cfm-hint-max-text'];
|
|
71
|
+
}
|
|
72
|
+
return next;
|
|
73
|
+
}
|
|
74
|
+
|
|
54
75
|
type PreviewConfigContextValue = {
|
|
55
76
|
previewState: PreviewStatePatch;
|
|
56
77
|
configRevision: number;
|
|
@@ -66,26 +87,58 @@ export function PreviewConfigProvider({ children }: { children: ReactNode }) {
|
|
|
66
87
|
const [configRevision, setConfigRevision] = useState(0);
|
|
67
88
|
|
|
68
89
|
useEffect(() => {
|
|
90
|
+
const requestSnapshot = () => {
|
|
91
|
+
try {
|
|
92
|
+
window.parent.postMessage({ type: PREVIEW_BRIDGE_READY }, '*');
|
|
93
|
+
} catch {
|
|
94
|
+
/* ignore */
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
requestSnapshot();
|
|
99
|
+
|
|
69
100
|
const handler = (event: MessageEvent) => {
|
|
70
101
|
const data = event.data;
|
|
71
102
|
if (!data?.type) return;
|
|
72
103
|
if (data.type !== PREVIEW_BRIDGE_SNAPSHOT && data.type !== PREVIEW_BRIDGE_DELTA) return;
|
|
73
|
-
|
|
104
|
+
|
|
105
|
+
const isSnapshot = data.type === PREVIEW_BRIDGE_SNAPSHOT;
|
|
106
|
+
let shouldBumpRevision = isSnapshot;
|
|
107
|
+
|
|
108
|
+
if (data.cssVars) {
|
|
109
|
+
const root = document.documentElement;
|
|
110
|
+
for (const [key, value] of Object.entries(data.cssVars)) {
|
|
111
|
+
root.style.setProperty(key, value as string);
|
|
112
|
+
}
|
|
113
|
+
shouldBumpRevision = true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (isSnapshot) {
|
|
117
|
+
const patchState = (data.contentPatch?.previewState ?? {}) as PreviewStatePatch;
|
|
118
|
+
const nextState = data.cssVars
|
|
119
|
+
? previewStateFromCssVars(data.cssVars, patchState)
|
|
120
|
+
: patchState;
|
|
121
|
+
setPreviewState(nextState);
|
|
122
|
+
shouldBumpRevision = true;
|
|
123
|
+
} else if (data.contentPatch?.previewState) {
|
|
74
124
|
setPreviewState((prev) => ({ ...prev, ...data.contentPatch.previewState }));
|
|
125
|
+
shouldBumpRevision = true;
|
|
126
|
+
} else if (data.cssVars) {
|
|
127
|
+
setPreviewState((prev) => previewStateFromCssVars(data.cssVars, prev));
|
|
128
|
+
shouldBumpRevision = true;
|
|
75
129
|
}
|
|
130
|
+
|
|
76
131
|
if (data.contentPatch?.domAttributes) {
|
|
77
132
|
const root = document.documentElement;
|
|
78
133
|
for (const [key, value] of Object.entries(data.contentPatch.domAttributes)) {
|
|
79
134
|
root.setAttribute(key, value as string);
|
|
80
135
|
}
|
|
136
|
+
shouldBumpRevision = true;
|
|
81
137
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
root.style.setProperty(key, value as string);
|
|
86
|
-
}
|
|
138
|
+
|
|
139
|
+
if (shouldBumpRevision) {
|
|
140
|
+
setConfigRevision((r) => r + 1);
|
|
87
141
|
}
|
|
88
|
-
setConfigRevision((r) => r + 1);
|
|
89
142
|
applyThemeSideEffects();
|
|
90
143
|
};
|
|
91
144
|
|
|
@@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
|
|
|
2
2
|
import type { AnswerValue, SurveyQuestion } from '@explorer02/cfm-survey-sdk';
|
|
3
3
|
import Question from '@/components/Question';
|
|
4
4
|
import { PreviewConfigProvider, usePreviewConfig } from './PreviewConfigContext';
|
|
5
|
+
import { mergePreviewQuestion } from './mergePreviewQuestion';
|
|
5
6
|
|
|
6
7
|
type QuestionPreviewProps = {
|
|
7
8
|
question: SurveyQuestion;
|
|
@@ -27,103 +28,14 @@ function resolveNumberLabelColor(index: number): string {
|
|
|
27
28
|
return '';
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
function applyMatrixRowCount<T extends SurveyQuestion & { statementRows?: { id: string; statementText: string }[] }>(
|
|
31
|
-
q: T,
|
|
32
|
-
multiStatement: boolean | undefined,
|
|
33
|
-
): T {
|
|
34
|
-
if (!('statementRows' in q) || !Array.isArray(q.statementRows)) return q;
|
|
35
|
-
const rows = q.statementRows;
|
|
36
|
-
if (multiStatement) {
|
|
37
|
-
if (rows.length >= 2) return q;
|
|
38
|
-
return {
|
|
39
|
-
...q,
|
|
40
|
-
statementRows: [
|
|
41
|
-
...rows,
|
|
42
|
-
{ id: 'r2', statementText: 'Statement Item 2' },
|
|
43
|
-
...(rows.length === 1 ? [{ id: 'r3', statementText: 'Statement Item 3' }] : []),
|
|
44
|
-
],
|
|
45
|
-
} as T;
|
|
46
|
-
}
|
|
47
|
-
return { ...q, statementRows: rows.slice(0, 1) } as T;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
31
|
function QuestionPreviewInner({ question, initialValue }: QuestionPreviewProps) {
|
|
51
32
|
const { previewState, configRevision } = usePreviewConfig();
|
|
52
33
|
const [value, setValue] = useState<AnswerValue | undefined>(initialValue);
|
|
53
34
|
|
|
54
|
-
const mergedQuestion = useMemo(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (previewState.textfieldPlaceholder && q.type === 'TEXTFIELD') {
|
|
59
|
-
q = { ...q, placeholder: previewState.textfieldPlaceholder };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (previewState.npsButtonStyle && q.type === 'NPS_SCALE') {
|
|
63
|
-
const style = previewState.npsButtonStyle;
|
|
64
|
-
const buttonStyle =
|
|
65
|
-
style === 'numbered'
|
|
66
|
-
? 'numbered'
|
|
67
|
-
: style === 'pill'
|
|
68
|
-
? 'pill'
|
|
69
|
-
: style === 'emoji'
|
|
70
|
-
? 'emoji'
|
|
71
|
-
: 'standard';
|
|
72
|
-
q = { ...q, buttonStyle };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (previewState.hintMinText !== undefined || previewState.hintMaxText !== undefined) {
|
|
76
|
-
if (q.type === 'NPS_SCALE') {
|
|
77
|
-
q = {
|
|
78
|
-
...q,
|
|
79
|
-
minLabel: previewState.hintMinText ?? q.minLabel,
|
|
80
|
-
maxLabel: previewState.hintMaxText ?? q.maxLabel,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
if (q.type === 'SLIDER_MATRIX') {
|
|
84
|
-
q = {
|
|
85
|
-
...q,
|
|
86
|
-
scaleAnchorLabels: [
|
|
87
|
-
previewState.hintMinText ?? q.scaleAnchorLabels?.[0] ?? '',
|
|
88
|
-
previewState.hintMaxText ?? q.scaleAnchorLabels?.[1] ?? '',
|
|
89
|
-
],
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (q.type === 'CFM_MATRIX') {
|
|
95
|
-
if (previewKey === 'CFM_bipolar') {
|
|
96
|
-
q = {
|
|
97
|
-
...q,
|
|
98
|
-
statementLayout: 'bipolar',
|
|
99
|
-
showColumnHeaders: false,
|
|
100
|
-
gridLayout: 'standard',
|
|
101
|
-
transposeTable: false,
|
|
102
|
-
};
|
|
103
|
-
} else if (previewKey === 'CFM_transpose') {
|
|
104
|
-
q = { ...q, statementLayout: 'likert', transposeTable: true, gridLayout: 'standard' };
|
|
105
|
-
} else if (previewKey === 'CFM_carousel') {
|
|
106
|
-
q = { ...q, statementLayout: 'likert', gridLayout: 'carousel', transposeTable: false };
|
|
107
|
-
} else if (previewKey === 'CFM_dropdown') {
|
|
108
|
-
q = { ...q, statementLayout: 'likert', gridLayout: 'dropdown', transposeTable: false };
|
|
109
|
-
} else {
|
|
110
|
-
q = { ...q, statementLayout: 'likert', gridLayout: 'standard', transposeTable: false };
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (q.type === 'RANK_ORDER') {
|
|
115
|
-
if (previewState.rankVariant === 'drag' || previewKey === 'RANK_drag') {
|
|
116
|
-
q = { ...q, interactionMode: 'dragAndDrop', optionDisplay: 'textAndImage' };
|
|
117
|
-
}
|
|
118
|
-
if (previewState.rankVariant === 'dropdown' || previewKey === 'RANK_dropdown') {
|
|
119
|
-
q = { ...q, interactionMode: 'dropdown', optionDisplay: 'textAndImage' };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
q = applyMatrixRowCount(q, previewState.multiStatement);
|
|
124
|
-
|
|
125
|
-
return q;
|
|
126
|
-
}, [question, previewState]);
|
|
35
|
+
const mergedQuestion = useMemo(
|
|
36
|
+
() => mergePreviewQuestion(question, previewState),
|
|
37
|
+
[question, previewState, configRevision],
|
|
38
|
+
);
|
|
127
39
|
|
|
128
40
|
const heatmapInitial =
|
|
129
41
|
previewState.heatmapPreviewPin && mergedQuestion.type === 'HEATMAP'
|
|
@@ -136,16 +48,25 @@ function QuestionPreviewInner({ question, initialValue }: QuestionPreviewProps)
|
|
|
136
48
|
]
|
|
137
49
|
: initialValue;
|
|
138
50
|
|
|
51
|
+
const rankDropdownInitial =
|
|
52
|
+
mergedQuestion.type === 'RANK_ORDER' &&
|
|
53
|
+
(mergedQuestion as { interactionMode?: string }).interactionMode === 'dropdown' &&
|
|
54
|
+
value === undefined
|
|
55
|
+
? { o1: 1 }
|
|
56
|
+
: undefined;
|
|
57
|
+
|
|
58
|
+
const resolvedValue = heatmapInitial ?? rankDropdownInitial ?? value;
|
|
59
|
+
|
|
139
60
|
return (
|
|
140
61
|
<div
|
|
141
|
-
className="mx-auto max-w-
|
|
142
|
-
style={{ fontFamily: 'var(--cfm-font-family)' }}
|
|
62
|
+
className="mx-auto w-full min-w-0 max-w-full overflow-x-auto px-4 py-6 box-border"
|
|
63
|
+
style={{ fontFamily: 'var(--cfm-font-family)', maxWidth: 'min(100%, var(--cfm-max-width, 48rem))' }}
|
|
143
64
|
data-cfm-question-area
|
|
144
65
|
>
|
|
145
66
|
<Question
|
|
146
67
|
key={configRevision}
|
|
147
68
|
question={mergedQuestion}
|
|
148
|
-
selectedValue={
|
|
69
|
+
selectedValue={resolvedValue}
|
|
149
70
|
allAnswers={{}}
|
|
150
71
|
allQuestions={[mergedQuestion]}
|
|
151
72
|
onSelect={setValue}
|
|
@@ -1,26 +1,43 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import type { AnswerValue } from '@explorer02/cfm-survey-sdk';
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import type { AnswerValue, MatrixRowAnswers } from '@explorer02/cfm-survey-sdk';
|
|
3
3
|
import SurveyStickyChrome from '@/components/SurveyStickyChrome';
|
|
4
4
|
import LanguageSelector from '@/components/LanguageSelector';
|
|
5
5
|
import Question from '@/components/Question';
|
|
6
6
|
import Footer from '@/components/Footer';
|
|
7
7
|
import { PreviewConfigProvider, usePreviewConfig } from './PreviewConfigContext';
|
|
8
|
+
import { mergeSurveyPageQuestion } from './mergePreviewQuestion';
|
|
8
9
|
import {
|
|
9
10
|
FIXTURE_SURVEY_PAGE_MCQ,
|
|
11
|
+
FIXTURE_SURVEY_PAGE_CFM_LIKERT,
|
|
10
12
|
FIXTURE_SURVEY_PAGE_NPS,
|
|
11
13
|
PREVIEW_LANGUAGES,
|
|
12
14
|
} from './fixtures/questions';
|
|
13
15
|
|
|
14
16
|
function SurveyPagePreviewInner() {
|
|
15
|
-
const { configRevision } = usePreviewConfig();
|
|
17
|
+
const { previewState, configRevision } = usePreviewConfig();
|
|
16
18
|
const [lang, setLang] = useState('en');
|
|
17
19
|
const [mcqAnswer, setMcqAnswer] = useState<AnswerValue>('a');
|
|
20
|
+
const [matrixAnswer, setMatrixAnswer] = useState<MatrixRowAnswers>({});
|
|
18
21
|
const [npsAnswer, setNpsAnswer] = useState<AnswerValue>(8);
|
|
19
22
|
|
|
20
|
-
const
|
|
23
|
+
const mcqQuestion = useMemo(
|
|
24
|
+
() => mergeSurveyPageQuestion(FIXTURE_SURVEY_PAGE_MCQ, previewState),
|
|
25
|
+
[previewState, configRevision],
|
|
26
|
+
);
|
|
27
|
+
const matrixQuestion = useMemo(
|
|
28
|
+
() => mergeSurveyPageQuestion(FIXTURE_SURVEY_PAGE_CFM_LIKERT, previewState),
|
|
29
|
+
[previewState, configRevision],
|
|
30
|
+
);
|
|
31
|
+
const npsQuestion = useMemo(
|
|
32
|
+
() => mergeSurveyPageQuestion(FIXTURE_SURVEY_PAGE_NPS, previewState),
|
|
33
|
+
[previewState, configRevision],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const allQuestions = [mcqQuestion, matrixQuestion, npsQuestion];
|
|
21
37
|
const allAnswers = {
|
|
22
|
-
[
|
|
23
|
-
[
|
|
38
|
+
[mcqQuestion.id]: mcqAnswer,
|
|
39
|
+
[matrixQuestion.id]: matrixAnswer,
|
|
40
|
+
[npsQuestion.id]: npsAnswer,
|
|
24
41
|
};
|
|
25
42
|
|
|
26
43
|
return (
|
|
@@ -45,14 +62,21 @@ function SurveyPagePreviewInner() {
|
|
|
45
62
|
|
|
46
63
|
<div className="space-y-8" key={configRevision}>
|
|
47
64
|
<Question
|
|
48
|
-
question={
|
|
65
|
+
question={mcqQuestion}
|
|
49
66
|
selectedValue={mcqAnswer}
|
|
50
67
|
allAnswers={allAnswers}
|
|
51
68
|
allQuestions={allQuestions}
|
|
52
69
|
onSelect={setMcqAnswer}
|
|
53
70
|
/>
|
|
54
71
|
<Question
|
|
55
|
-
question={
|
|
72
|
+
question={matrixQuestion}
|
|
73
|
+
selectedValue={matrixAnswer}
|
|
74
|
+
allAnswers={allAnswers}
|
|
75
|
+
allQuestions={allQuestions}
|
|
76
|
+
onSelect={setMatrixAnswer}
|
|
77
|
+
/>
|
|
78
|
+
<Question
|
|
79
|
+
question={npsQuestion}
|
|
56
80
|
selectedValue={npsAnswer}
|
|
57
81
|
allAnswers={allAnswers}
|
|
58
82
|
allQuestions={allQuestions}
|
|
@@ -19,14 +19,37 @@ const LIKERT_COLUMNS = [
|
|
|
19
19
|
{ id: 'c5', label: 'Strongly Agree' },
|
|
20
20
|
];
|
|
21
21
|
|
|
22
|
-
const MATRIX_ROW_SINGLE = [{ id: 'r1', statementText: 'Statement Item 1' }];
|
|
22
|
+
export const MATRIX_ROW_SINGLE = [{ id: 'r1', statementText: 'Statement Item 1' }];
|
|
23
23
|
|
|
24
|
-
const MATRIX_ROWS_MULTI = [
|
|
24
|
+
export const MATRIX_ROWS_MULTI = [
|
|
25
25
|
{ id: 'r1', statementText: 'Statement Item 1' },
|
|
26
26
|
{ id: 'r2', statementText: 'Statement Item 2' },
|
|
27
27
|
{ id: 'r3', statementText: 'Statement Item 3' },
|
|
28
28
|
];
|
|
29
29
|
|
|
30
|
+
/** Bipolar matrix preview rows — left statement + matching right opposite per row. */
|
|
31
|
+
export const BIPOLAR_ROWS_SINGLE = [
|
|
32
|
+
{ id: 'r1', statementText: 'Statement 1', oppositeStatementText: 'Opposite 1' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export const BIPOLAR_ROWS_MULTI = [
|
|
36
|
+
{ id: 'r1', statementText: 'Statement 1', oppositeStatementText: 'Opposite 1' },
|
|
37
|
+
{ id: 'r2', statementText: 'Statement 2', oppositeStatementText: 'Opposite 2' },
|
|
38
|
+
{ id: 'r3', statementText: 'Statement 3', oppositeStatementText: 'Opposite 3' },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const SLIDER_ROW_DEFAULTS = { min: 0, max: 10, step: 1 };
|
|
42
|
+
|
|
43
|
+
export const SLIDER_ROWS_SINGLE = [
|
|
44
|
+
{ id: 'r1', statementText: 'Statement Item 1', ...SLIDER_ROW_DEFAULTS },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
export const SLIDER_ROWS_MULTI = [
|
|
48
|
+
{ id: 'r1', statementText: 'Statement Item 1', ...SLIDER_ROW_DEFAULTS },
|
|
49
|
+
{ id: 'r2', statementText: 'Statement Item 2', ...SLIDER_ROW_DEFAULTS },
|
|
50
|
+
{ id: 'r3', statementText: 'Statement Item 3', ...SLIDER_ROW_DEFAULTS },
|
|
51
|
+
];
|
|
52
|
+
|
|
30
53
|
const SLIDER_TICKS = Array.from({ length: 11 }, (_, i) => ({
|
|
31
54
|
tickPosition: i,
|
|
32
55
|
tickLabel: String(i),
|
|
@@ -79,7 +102,7 @@ export const FIXTURE_NPS = base({
|
|
|
79
102
|
type: 'NPS_SCALE',
|
|
80
103
|
questionText:
|
|
81
104
|
'Based on your experience, how likely are you to recommend [Company/Product/Service] to your friends and family?',
|
|
82
|
-
buttonStyle: '
|
|
105
|
+
buttonStyle: 'numbered',
|
|
83
106
|
minLabel: 'Not Likely At All',
|
|
84
107
|
maxLabel: 'Extremely Likely',
|
|
85
108
|
options: Array.from({ length: 11 }, (_, i) => ({
|
|
@@ -104,8 +127,8 @@ export const FIXTURE_SURVEY_PAGE_MCQ = base({
|
|
|
104
127
|
export const FIXTURE_SURVEY_PAGE_NPS = base({
|
|
105
128
|
id: 'preview-survey-nps',
|
|
106
129
|
type: 'NPS_SCALE',
|
|
107
|
-
questionNumber:
|
|
108
|
-
buttonStyle: '
|
|
130
|
+
questionNumber: 3,
|
|
131
|
+
buttonStyle: 'numbered',
|
|
109
132
|
questionText:
|
|
110
133
|
'Based on your experience, how likely are you to recommend [Company/Product/Service] to your friends and family?',
|
|
111
134
|
minLabel: 'Not Likely At All',
|
|
@@ -116,6 +139,23 @@ export const FIXTURE_SURVEY_PAGE_NPS = base({
|
|
|
116
139
|
})),
|
|
117
140
|
});
|
|
118
141
|
|
|
142
|
+
/** Likert matrix with 3 statements — used on survey_page preview for zebra row styling. */
|
|
143
|
+
export const FIXTURE_SURVEY_PAGE_CFM_LIKERT = base({
|
|
144
|
+
id: 'preview-survey-cfm-likert',
|
|
145
|
+
type: 'CFM_MATRIX',
|
|
146
|
+
questionNumber: 2,
|
|
147
|
+
questionText: 'Please rate each statement below.',
|
|
148
|
+
statementLayout: 'likert',
|
|
149
|
+
gridLayout: 'standard',
|
|
150
|
+
transposeTable: false,
|
|
151
|
+
selectionMode: 'single',
|
|
152
|
+
showColumnHeaders: true,
|
|
153
|
+
repeatColumnHeaders: false,
|
|
154
|
+
hasNotApplicableOption: false,
|
|
155
|
+
statementRows: MATRIX_ROWS_MULTI,
|
|
156
|
+
scaleColumns: LIKERT_COLUMNS,
|
|
157
|
+
});
|
|
158
|
+
|
|
119
159
|
export const FIXTURE_TEXT_SHORT = base({
|
|
120
160
|
type: 'TEXTFIELD',
|
|
121
161
|
questionText: 'Question',
|
|
@@ -141,7 +181,7 @@ export const FIXTURE_CFM_LIKERT = base({
|
|
|
141
181
|
showColumnHeaders: true,
|
|
142
182
|
repeatColumnHeaders: false,
|
|
143
183
|
hasNotApplicableOption: false,
|
|
144
|
-
statementRows:
|
|
184
|
+
statementRows: MATRIX_ROW_SINGLE,
|
|
145
185
|
scaleColumns: LIKERT_COLUMNS,
|
|
146
186
|
});
|
|
147
187
|
|
|
@@ -154,13 +194,7 @@ export const FIXTURE_CFM_BIPOLAR = base({
|
|
|
154
194
|
showColumnHeaders: false,
|
|
155
195
|
repeatColumnHeaders: false,
|
|
156
196
|
hasNotApplicableOption: false,
|
|
157
|
-
statementRows:
|
|
158
|
-
{
|
|
159
|
-
id: 'r1',
|
|
160
|
-
statementText: 'Difficult',
|
|
161
|
-
oppositeStatementText: 'Easy',
|
|
162
|
-
},
|
|
163
|
-
],
|
|
197
|
+
statementRows: BIPOLAR_ROWS_SINGLE,
|
|
164
198
|
scaleColumns: Array.from({ length: 5 }, (_, i) => ({ id: `c${i}`, label: '' })),
|
|
165
199
|
scaleAnchorLabels: ['Hard', '', '', '', 'Easy'],
|
|
166
200
|
});
|
|
@@ -187,7 +221,7 @@ export const FIXTURE_CFM_CAROUSEL = base({
|
|
|
187
221
|
showColumnHeaders: true,
|
|
188
222
|
repeatColumnHeaders: false,
|
|
189
223
|
hasNotApplicableOption: false,
|
|
190
|
-
statementRows:
|
|
224
|
+
statementRows: MATRIX_ROW_SINGLE,
|
|
191
225
|
scaleColumns: LIKERT_COLUMNS,
|
|
192
226
|
});
|
|
193
227
|
|
|
@@ -233,7 +267,7 @@ export const FIXTURE_CSAT_NUMBERED = base({
|
|
|
233
267
|
type: 'CSAT_MATRIX',
|
|
234
268
|
displayStyle: 'numbered',
|
|
235
269
|
gridLayout: 'standard',
|
|
236
|
-
statementRows:
|
|
270
|
+
statementRows: MATRIX_ROW_SINGLE,
|
|
237
271
|
scaleColumns: LIKERT_COLUMNS,
|
|
238
272
|
scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
|
|
239
273
|
});
|
|
@@ -273,7 +307,7 @@ export const FIXTURE_RATING_STAR = base({
|
|
|
273
307
|
displayStyle: 'star',
|
|
274
308
|
gridLayout: 'standard',
|
|
275
309
|
showColumnHeaders: true,
|
|
276
|
-
statementRows:
|
|
310
|
+
statementRows: MATRIX_ROW_SINGLE,
|
|
277
311
|
scaleColumns: RATING_TEN_COLUMNS,
|
|
278
312
|
scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
|
|
279
313
|
});
|
|
@@ -282,7 +316,7 @@ export const FIXTURE_RATING_NUMBERED = base({
|
|
|
282
316
|
type: 'RATING_MATRIX',
|
|
283
317
|
displayStyle: 'numbered',
|
|
284
318
|
gridLayout: 'standard',
|
|
285
|
-
statementRows:
|
|
319
|
+
statementRows: MATRIX_ROW_SINGLE,
|
|
286
320
|
scaleColumns: RATING_TEN_COLUMNS,
|
|
287
321
|
scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
|
|
288
322
|
});
|
|
@@ -318,10 +352,7 @@ export const FIXTURE_RATING_SLIDER = base({
|
|
|
318
352
|
type: 'RATING_MATRIX',
|
|
319
353
|
displayStyle: 'graphics',
|
|
320
354
|
gridLayout: 'standard',
|
|
321
|
-
statementRows:
|
|
322
|
-
{ id: 'r1', statementText: 'Statement Item 1' },
|
|
323
|
-
{ id: 'r2', statementText: 'Statement Item 2' },
|
|
324
|
-
],
|
|
355
|
+
statementRows: MATRIX_ROW_SINGLE,
|
|
325
356
|
scaleColumns: [
|
|
326
357
|
{ id: 'c1', label: 'Likely' },
|
|
327
358
|
{ id: 'c2', label: 'Unlikely' },
|
|
@@ -333,10 +364,7 @@ export const FIXTURE_RATING_SLIDER = base({
|
|
|
333
364
|
|
|
334
365
|
export const FIXTURE_SLIDER = base({
|
|
335
366
|
type: 'SLIDER_MATRIX',
|
|
336
|
-
statementRows:
|
|
337
|
-
{ id: 'r1', statementText: 'Statement Item 1', min: 0, max: 10, step: 1 },
|
|
338
|
-
{ id: 'r2', statementText: 'Statement Item 2', min: 0, max: 10, step: 1 },
|
|
339
|
-
],
|
|
367
|
+
statementRows: SLIDER_ROWS_MULTI.slice(0, 2),
|
|
340
368
|
scaleAnchorLabels: ['Label 1', 'Label 2'],
|
|
341
369
|
ticks: {
|
|
342
370
|
count: 11,
|
|
@@ -9,8 +9,14 @@ body {
|
|
|
9
9
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
10
10
|
color: #262626;
|
|
11
11
|
background-color: #ffffff;
|
|
12
|
+
max-width: 100%;
|
|
13
|
+
overflow-x: hidden;
|
|
14
|
+
box-sizing: border-box;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
#root {
|
|
15
18
|
min-height: 100vh;
|
|
19
|
+
max-width: 100%;
|
|
20
|
+
overflow-x: hidden;
|
|
21
|
+
box-sizing: border-box;
|
|
16
22
|
}
|