@explorer02/cfm-survey-sdk 0.3.9 → 0.4.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/dist/cli/index.js +35 -35
- package/dist/cli/index.mjs +35 -35
- package/package.json +1 -1
- package/templates/docs/templates/CsatMatrixScale.tsx +36 -23
- package/templates/docs/templates/CustomSliderTrack.tsx +2 -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/uiConfig.ts +85 -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 +8 -1
- 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 +47 -16
- package/templates/preview-harness/vite-app/src/globals.css +6 -0
- package/templates/preview-harness/vite-app/src/mergePreviewQuestion.ts +173 -0
- package/templates/preview-harness/vite-app/src/preview-live-overrides.css +22 -0
- package/templates/previewBridge.ts +61 -18
- package/templates/survey-theme.css +3 -0
- package/templates/wizard-dist/assets/{PreviewMock-CS4WqhDB.js → PreviewMock-BlOAvUCN.js} +1 -1
- package/templates/wizard-dist/assets/TypePanel-DcbMaw1b.js +1 -0
- package/templates/wizard-dist/assets/index-2Bi_7Fdh.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
|
@@ -36,6 +36,15 @@ function graphicsColumnLabelStyle(question: CsatMatrixQuestion) {
|
|
|
36
36
|
return { fontSize: '12px', fontWeight: 500, lineHeight: 1.2, wordBreak: 'break-word' as const, ...pillStyle };
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** Graphics uses anchor labels (bold top row) — per-column tick badges duplicate them for CSAT + RATING. */
|
|
40
|
+
function hasGraphicsAnchorLabels(labels: string[] | undefined): boolean {
|
|
41
|
+
return Boolean(labels?.some((label) => label.trim().length > 0));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function shouldShowGraphicsColumnLabelRow(isGraphics: boolean, labels: string[] | undefined): boolean {
|
|
45
|
+
return isGraphics && !hasGraphicsAnchorLabels(labels);
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
function GraphicsColumnLabelsRow({
|
|
40
49
|
scaleColumns,
|
|
41
50
|
question,
|
|
@@ -47,8 +56,8 @@ function GraphicsColumnLabelsRow({
|
|
|
47
56
|
const labelStyle = graphicsColumnLabelStyle(question);
|
|
48
57
|
|
|
49
58
|
return (
|
|
50
|
-
<div style={{ flex: 1, padding: '0
|
|
51
|
-
<div style={{ position: 'relative', margin: '0
|
|
59
|
+
<div style={{ flex: 1, minWidth: 0, padding: '0 8px' }}>
|
|
60
|
+
<div style={{ position: 'relative', margin: '0 8px', height: '16px' }}>
|
|
52
61
|
{scaleColumns.map((col, i) => {
|
|
53
62
|
const percent = scaleColumns.length === 1 ? 0 : (i / (scaleColumns.length - 1)) * 100;
|
|
54
63
|
return (
|
|
@@ -60,7 +69,7 @@ function GraphicsColumnLabelsRow({
|
|
|
60
69
|
transform: 'translateX(-50%)',
|
|
61
70
|
bottom: 0,
|
|
62
71
|
textAlign: 'center',
|
|
63
|
-
|
|
72
|
+
maxWidth: '72px',
|
|
64
73
|
}}
|
|
65
74
|
>
|
|
66
75
|
<span style={labelStyle} dangerouslySetInnerHTML={{ __html: col.label }} />
|
|
@@ -212,13 +221,13 @@ function CsatMatrixGrid({
|
|
|
212
221
|
const n = labels?.length || 0;
|
|
213
222
|
|
|
214
223
|
return (
|
|
215
|
-
<div style={{ width: '100%' }}>
|
|
224
|
+
<div style={{ width: '100%', maxWidth: '100%', minWidth: 0, overflowX: 'auto', boxSizing: 'border-box' }}>
|
|
216
225
|
{/* Labels row */}
|
|
217
226
|
{showLabels && m > 0 && (
|
|
218
|
-
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '12px' }}>
|
|
219
|
-
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
220
|
-
<div style={{ flex: 1, display: 'flex' }}>
|
|
221
|
-
<div style={{ flex: 1, position: 'relative',
|
|
227
|
+
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '12px', minWidth: 0 }}>
|
|
228
|
+
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0, minWidth: 0 }} />
|
|
229
|
+
<div style={{ flex: 1, display: 'flex', minWidth: 0 }}>
|
|
230
|
+
<div style={{ flex: 1, position: 'relative', minHeight: '24px', minWidth: 0, ...(isGraphics ? { margin: '0 8px' } : {}) }}>
|
|
222
231
|
{labels!.map((label, i) => {
|
|
223
232
|
const startPercent = isGraphics ? 0 : 0.5 / m;
|
|
224
233
|
const rangePercent = isGraphics ? 1 : (m - 1) / m;
|
|
@@ -229,8 +238,10 @@ function CsatMatrixGrid({
|
|
|
229
238
|
position: 'absolute',
|
|
230
239
|
left: `${percent}%`,
|
|
231
240
|
transform: 'translateX(-50%)',
|
|
232
|
-
textAlign: 'center', fontSize: '13px', fontWeight: 600,
|
|
233
|
-
lineHeight: 1.3,
|
|
241
|
+
textAlign: 'center', fontSize: isGraphics ? '12px' : '13px', fontWeight: 600,
|
|
242
|
+
lineHeight: 1.3,
|
|
243
|
+
whiteSpace: isGraphics ? 'normal' : 'nowrap',
|
|
244
|
+
maxWidth: isGraphics ? '72px' : undefined,
|
|
234
245
|
...anchorLabelStyle(question),
|
|
235
246
|
}}>
|
|
236
247
|
{label}
|
|
@@ -243,9 +254,9 @@ function CsatMatrixGrid({
|
|
|
243
254
|
</div>
|
|
244
255
|
)}
|
|
245
256
|
|
|
246
|
-
{/* Column tick labels for graphics
|
|
247
|
-
{isGraphics && m > 0 && (
|
|
248
|
-
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom:
|
|
257
|
+
{/* Column tick labels for graphics — only when no anchor label row above */}
|
|
258
|
+
{shouldShowGraphicsColumnLabelRow(isGraphics, labels) && m > 0 && (
|
|
259
|
+
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '16px', minHeight: '24px' }}>
|
|
249
260
|
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
250
261
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
251
262
|
<GraphicsColumnLabelsRow scaleColumns={scaleColumns} question={question} />
|
|
@@ -285,15 +296,15 @@ function CsatMatrixGrid({
|
|
|
285
296
|
{statementRows.map((row, rowIdx) => (
|
|
286
297
|
<div key={row.id} style={{
|
|
287
298
|
display: 'flex', alignItems: 'center', borderRadius: '8px',
|
|
288
|
-
padding: '12px 0', ...matrixRowBackgroundStyle(rowIdx),
|
|
299
|
+
padding: '12px 0', minWidth: 0, ...matrixRowBackgroundStyle(rowIdx),
|
|
289
300
|
}}>
|
|
290
|
-
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0, padding: '0
|
|
301
|
+
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0, minWidth: 0, padding: '0 12px' }}>
|
|
291
302
|
<span style={{ fontSize: '14px', fontWeight: 500, color: '#111827', lineHeight: 1.4 }}
|
|
292
303
|
dangerouslySetInnerHTML={{ __html: row.statementText }} />
|
|
293
304
|
</div>
|
|
294
305
|
|
|
295
306
|
{isGraphics ? (
|
|
296
|
-
<div style={{ flex: 1, padding: '0
|
|
307
|
+
<div style={{ flex: 1, minWidth: 0, padding: '0 8px', position: 'relative', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
|
297
308
|
<CustomSliderTrack
|
|
298
309
|
min={0}
|
|
299
310
|
max={scaleColumns.length - 1}
|
|
@@ -450,11 +461,11 @@ function CsatMatrixCarousel({
|
|
|
450
461
|
const showLabels = labels && labels.length > 0;
|
|
451
462
|
|
|
452
463
|
return (
|
|
453
|
-
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', backgroundColor: '#fff', borderRadius: '12px', border: '1px solid #e5e5e5', padding: '
|
|
464
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', backgroundColor: '#fff', borderRadius: '12px', border: '1px solid #e5e5e5', padding: '24px 16px', boxShadow: '0 4px 6px -1px rgba(0,0,0,0.05)', width: '100%', maxWidth: '100%', minWidth: 0, overflowX: 'auto', boxSizing: 'border-box' }}>
|
|
454
465
|
<h3 style={{ fontSize: '18px', fontWeight: 500, color: '#111827', marginBottom: '32px', textAlign: 'center' }} dangerouslySetInnerHTML={{ __html: row.statementText }} />
|
|
455
466
|
|
|
456
467
|
{showLabels && scaleColumns.length > 0 && (
|
|
457
|
-
<div style={{ width: '100%', maxWidth: '
|
|
468
|
+
<div style={{ width: '100%', maxWidth: '100%', minWidth: 0, position: 'relative', height: '24px', marginBottom: '16px' }}>
|
|
458
469
|
{labels!.map((label, i) => {
|
|
459
470
|
const m = scaleColumns.length;
|
|
460
471
|
const n = labels!.length;
|
|
@@ -465,8 +476,10 @@ function CsatMatrixCarousel({
|
|
|
465
476
|
return (
|
|
466
477
|
<div key={i} style={{
|
|
467
478
|
position: 'absolute', left: `${percent}%`, transform: 'translateX(-50%)',
|
|
468
|
-
textAlign: 'center', fontSize: '13px', fontWeight: 600,
|
|
469
|
-
lineHeight: 1.3,
|
|
479
|
+
textAlign: 'center', fontSize: isGraphics ? '12px' : '13px', fontWeight: 600,
|
|
480
|
+
lineHeight: 1.3,
|
|
481
|
+
whiteSpace: isGraphics ? 'normal' : 'nowrap',
|
|
482
|
+
maxWidth: isGraphics ? '72px' : undefined,
|
|
470
483
|
...anchorLabelStyle(question),
|
|
471
484
|
}}>
|
|
472
485
|
{label}
|
|
@@ -476,14 +489,14 @@ function CsatMatrixCarousel({
|
|
|
476
489
|
</div>
|
|
477
490
|
)}
|
|
478
491
|
|
|
479
|
-
{isGraphics && scaleColumns.length > 0 && (
|
|
480
|
-
<div style={{ width: '100%', maxWidth: '
|
|
492
|
+
{shouldShowGraphicsColumnLabelRow(isGraphics, labels) && scaleColumns.length > 0 && (
|
|
493
|
+
<div style={{ width: '100%', maxWidth: '100%', marginBottom: '16px' }}>
|
|
481
494
|
<GraphicsColumnLabelsRow scaleColumns={scaleColumns} question={question} />
|
|
482
495
|
</div>
|
|
483
496
|
)}
|
|
484
497
|
|
|
485
498
|
{isGraphics ? (
|
|
486
|
-
<div style={{ width: '100%', maxWidth: '
|
|
499
|
+
<div style={{ width: '100%', maxWidth: '100%', minWidth: 0, marginBottom: '40px', padding: '0 8px' }}>
|
|
487
500
|
<CustomSliderTrack
|
|
488
501
|
min={0} max={scaleColumns.length - 1} htmlStep={1}
|
|
489
502
|
value={
|
|
@@ -56,7 +56,7 @@ export function CustomSliderTrack({
|
|
|
56
56
|
|
|
57
57
|
return (
|
|
58
58
|
<div
|
|
59
|
-
style={{ position: 'relative', height: '32px', display: 'flex', alignItems: 'center', margin: '0
|
|
59
|
+
style={{ position: 'relative', height: '32px', display: 'flex', alignItems: 'center', margin: '0 8px', opacity: disabled ? 0.5 : 1 }}
|
|
60
60
|
onMouseEnter={() => !disabled && setIsHovered(true)}
|
|
61
61
|
onMouseLeave={() => { setIsHovered(false); setIsDragging(false); }}
|
|
62
62
|
>
|
|
@@ -94,7 +94,7 @@ export function CustomSliderTrack({
|
|
|
94
94
|
onTouchStart={() => !disabled && setIsDragging(true)}
|
|
95
95
|
onTouchEnd={() => setIsDragging(false)}
|
|
96
96
|
style={{
|
|
97
|
-
position: 'absolute', top: '50%', left: '-
|
|
97
|
+
position: 'absolute', top: '50%', left: '-8px', right: '-8px', width: 'calc(100% + 16px)', height: '100%',
|
|
98
98
|
transform: 'translateY(-50%)', opacity: 0, cursor: disabled ? 'not-allowed' : 'pointer', zIndex: 10, margin: 0,
|
|
99
99
|
}}
|
|
100
100
|
/>
|
|
@@ -76,20 +76,20 @@ export default function Header({ embedded = false }: HeaderProps) {
|
|
|
76
76
|
{seedCompany}
|
|
77
77
|
</span>
|
|
78
78
|
|
|
79
|
-
{
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
79
|
+
{/* Always in DOM for wizard bridge — visibility toggled via layoutFlags.showCompanyName */}
|
|
80
|
+
<span
|
|
81
|
+
data-cfm-company
|
|
82
|
+
className="font-semibold"
|
|
83
|
+
style={{
|
|
84
|
+
display:
|
|
85
|
+
seedCompany && hasLogoFile && seedShowCompany ? undefined : 'none',
|
|
86
|
+
color: 'var(--cfm-header-company-color, var(--cfm-text))',
|
|
87
|
+
fontSize: 'var(--cfm-header-company-size, 18px)',
|
|
88
|
+
fontWeight: 'var(--cfm-header-company-weight, 600)',
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
{seedCompany}
|
|
92
|
+
</span>
|
|
93
93
|
</div>
|
|
94
94
|
</div>
|
|
95
95
|
</header>
|
|
@@ -248,7 +248,7 @@ function LikertMatrixGridLayout({
|
|
|
248
248
|
};
|
|
249
249
|
|
|
250
250
|
return (
|
|
251
|
-
<div style={{ width: '100%' }}>
|
|
251
|
+
<div style={{ width: '100%', maxWidth: '100%', minWidth: 0, overflowX: 'auto', boxSizing: 'border-box' }}>
|
|
252
252
|
{!repeatColumnHeaders && renderHeader()}
|
|
253
253
|
|
|
254
254
|
<div style={{ display: 'flex', flexDirection: 'column', gap: repeatColumnHeaders ? '24px' : '0' }}>
|
|
@@ -326,7 +326,7 @@ function LikertMatrixGridLayout({
|
|
|
326
326
|
</div>
|
|
327
327
|
|
|
328
328
|
{isBipolar && (
|
|
329
|
-
<div style={{ flex:
|
|
329
|
+
<div style={{ flex: `0 0 ${BIPOLAR_COL_WIDTH}`, padding: '0 16px', textAlign: 'right', boxSizing: 'border-box' }}>
|
|
330
330
|
<span style={{ fontSize: '14px', fontWeight: 500, color: '#111827', lineHeight: 1.4, wordWrap: 'break-word', display: 'block' }}
|
|
331
331
|
dangerouslySetInnerHTML={{ __html: transposeTable ? '' : ('oppositeStatementText' in rowItem ? rowItem.oppositeStatementText ?? '' : '') }} />
|
|
332
332
|
</div>
|
|
@@ -19,7 +19,7 @@ import { FileUploadScale } from './FileUploadScale';
|
|
|
19
19
|
import { HeatmapScale } from './HeatmapScale';
|
|
20
20
|
import { RankOrderScale } from './RankOrderScale';
|
|
21
21
|
import RatingScale from './RatingScale';
|
|
22
|
-
import {
|
|
22
|
+
import { showQuestionNumbersInChrome, showRequiredAsteriskInChrome, resolveNpsButtonStyle, resolveNpsHintMinLabel, resolveNpsHintMaxLabel } from '@/lib/uiConfig';
|
|
23
23
|
import { hintLabelStyle } from '@/lib/surveyUi/labelStyles';
|
|
24
24
|
import {
|
|
25
25
|
mcqCheckboxControlStyle,
|
|
@@ -48,7 +48,8 @@ export default function Question({
|
|
|
48
48
|
customFieldValues = {},
|
|
49
49
|
onSelect,
|
|
50
50
|
}: QuestionProps) {
|
|
51
|
-
const
|
|
51
|
+
const showNumbers = showQuestionNumbersInChrome();
|
|
52
|
+
const showAsterisk = showRequiredAsteriskInChrome();
|
|
52
53
|
|
|
53
54
|
const questionsById = useMemo(
|
|
54
55
|
() => new Map(allQuestions.map(q => [q.id, q])),
|
|
@@ -125,31 +126,35 @@ export default function Question({
|
|
|
125
126
|
borderRadius: 'var(--cfm-border-radius, 12px)',
|
|
126
127
|
fontFamily: 'var(--cfm-font-family)',
|
|
127
128
|
marginBottom: 'var(--cfm-card-gap, 32px)',
|
|
129
|
+
maxWidth: '100%',
|
|
130
|
+
overflowX: 'auto',
|
|
131
|
+
boxSizing: 'border-box',
|
|
128
132
|
}}
|
|
129
133
|
>
|
|
130
134
|
<h2
|
|
131
135
|
className="text-lg font-medium leading-relaxed"
|
|
132
136
|
style={{ color: 'var(--cfm-text, #111827)' }}
|
|
133
137
|
>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
<span
|
|
139
|
+
data-cfm-question-number
|
|
140
|
+
className="mr-1 font-semibold"
|
|
141
|
+
style={{
|
|
142
|
+
display:
|
|
143
|
+
question.questionNumber != null && showNumbers ? undefined : 'none',
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
{question.questionNumber != null ? `${question.questionNumber}.` : ''}
|
|
147
|
+
</span>
|
|
143
148
|
<span dangerouslySetInnerHTML={{ __html: displayQuestionText }} />
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
<span
|
|
150
|
+
data-cfm-required
|
|
151
|
+
className="ml-1 text-red-500"
|
|
152
|
+
style={{
|
|
153
|
+
display: question.required && showAsterisk ? undefined : 'none',
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
*
|
|
157
|
+
</span>
|
|
153
158
|
</h2>
|
|
154
159
|
{displayQuestionDescription && (
|
|
155
160
|
<div
|
|
@@ -165,7 +170,8 @@ export default function Question({
|
|
|
165
170
|
<span
|
|
166
171
|
className="absolute left-0 top-0 max-w-[30%] text-left leading-tight"
|
|
167
172
|
style={{ ...hintLabelStyle(), padding: '2px 6px', borderRadius: '4px' }}
|
|
168
|
-
|
|
173
|
+
data-cfm-hint-min
|
|
174
|
+
dangerouslySetInnerHTML={{ __html: resolveNpsHintMinLabel(question.minLabel ?? '') }}
|
|
169
175
|
/>
|
|
170
176
|
{question.midLabel && (
|
|
171
177
|
<span
|
|
@@ -184,7 +190,8 @@ export default function Question({
|
|
|
184
190
|
<span
|
|
185
191
|
className="absolute right-0 top-0 max-w-[30%] text-right leading-tight"
|
|
186
192
|
style={{ ...hintLabelStyle(), padding: '2px 6px', borderRadius: '4px' }}
|
|
187
|
-
|
|
193
|
+
data-cfm-hint-max
|
|
194
|
+
dangerouslySetInnerHTML={{ __html: resolveNpsHintMaxLabel(question.maxLabel ?? '') }}
|
|
188
195
|
/>
|
|
189
196
|
</div>
|
|
190
197
|
)}
|
|
@@ -194,7 +201,7 @@ export default function Question({
|
|
|
194
201
|
selectedValue={selectedValue}
|
|
195
202
|
onSelect={onSelect}
|
|
196
203
|
variant="nps"
|
|
197
|
-
buttonStyle={question.buttonStyle}
|
|
204
|
+
buttonStyle={resolveNpsButtonStyle(question.buttonStyle)}
|
|
198
205
|
/>
|
|
199
206
|
</div>
|
|
200
207
|
)}
|
|
@@ -182,8 +182,16 @@ function DropdownRankLayout({
|
|
|
182
182
|
aria-label={`Rank for ${option.optionLabel || option.id}`}
|
|
183
183
|
value={currentRank ?? ''}
|
|
184
184
|
onChange={event => handleRankChange(option.id, event.target.value)}
|
|
185
|
-
className="h-10 w-16 shrink-0 rounded border
|
|
186
|
-
style={{
|
|
185
|
+
className="h-10 w-16 shrink-0 rounded border px-2 text-sm font-semibold outline-none"
|
|
186
|
+
style={{
|
|
187
|
+
borderColor: 'var(--cfm-rank-select-border, #d1d5db)',
|
|
188
|
+
backgroundColor: currentRank
|
|
189
|
+
? 'var(--cfm-rank-badge-bg, var(--cfm-mcq-selected-bg, #fdf2f8))'
|
|
190
|
+
: '#ffffff',
|
|
191
|
+
color: currentRank
|
|
192
|
+
? 'var(--cfm-rank-badge-text, var(--cfm-mcq-selected-accent, var(--cfm-primary, #e20074)))'
|
|
193
|
+
: '#374151',
|
|
194
|
+
}}
|
|
187
195
|
onFocus={e => {
|
|
188
196
|
e.currentTarget.style.borderColor = focusRingVar;
|
|
189
197
|
e.currentTarget.style.boxShadow = `0 0 0 1px ${focusRingVar}`;
|
|
@@ -168,7 +168,7 @@ export function SliderMatrixScale({ question, selectedValue = {}, onSelect }: Sl
|
|
|
168
168
|
};
|
|
169
169
|
|
|
170
170
|
return (
|
|
171
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
171
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', width: '100%', maxWidth: '100%', minWidth: 0, overflowX: 'auto', boxSizing: 'border-box' }}>
|
|
172
172
|
{renderHeaderRow()}
|
|
173
173
|
|
|
174
174
|
{statementRows.map(row => {
|
|
@@ -82,6 +82,29 @@ export function getLayoutFlags() {
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
function readLiveChromeFlag(cssVar: string, configFallback: boolean): boolean {
|
|
86
|
+
if (typeof window === 'undefined') return configFallback;
|
|
87
|
+
const root = document.documentElement;
|
|
88
|
+
const live =
|
|
89
|
+
root.style.getPropertyValue(cssVar).trim() ||
|
|
90
|
+
getComputedStyle(root).getPropertyValue(cssVar).trim();
|
|
91
|
+
if (live === '0') return false;
|
|
92
|
+
if (live === '1') return true;
|
|
93
|
+
return configFallback;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Wizard live preview reads --cfm-show-question-numbers; production uses survey-ui-config.json. */
|
|
97
|
+
export function showQuestionNumbersInChrome(): boolean {
|
|
98
|
+
const flags = getLayoutFlags();
|
|
99
|
+
return readLiveChromeFlag('--cfm-show-question-numbers', flags.showQuestionNumbers);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Wizard live preview reads --cfm-show-required-asterisk; production uses survey-ui-config.json. */
|
|
103
|
+
export function showRequiredAsteriskInChrome(): boolean {
|
|
104
|
+
const flags = getLayoutFlags();
|
|
105
|
+
return readLiveChromeFlag('--cfm-show-required-asterisk', flags.showRequiredAsterisk);
|
|
106
|
+
}
|
|
107
|
+
|
|
85
108
|
export function getPlaceholders(): Record<string, string> {
|
|
86
109
|
return { ...(surveyUiConfig.placeholders ?? {}) };
|
|
87
110
|
}
|
|
@@ -89,3 +112,65 @@ export function getPlaceholders(): Record<string, string> {
|
|
|
89
112
|
export function isDebugEnabled(): boolean {
|
|
90
113
|
return surveyUiConfig.debug?.enabled === true;
|
|
91
114
|
}
|
|
115
|
+
|
|
116
|
+
export type NpsButtonStyle = 'standard' | 'numbered' | 'emoji' | 'pill';
|
|
117
|
+
|
|
118
|
+
function readLiveNpsButtonStyle(): NpsButtonStyle | null {
|
|
119
|
+
if (typeof window === 'undefined') return null;
|
|
120
|
+
const root = document.documentElement;
|
|
121
|
+
const attr = root.getAttribute('data-cfm-nps-button-style');
|
|
122
|
+
if (attr === 'standard' || attr === 'numbered' || attr === 'emoji' || attr === 'pill') {
|
|
123
|
+
return attr;
|
|
124
|
+
}
|
|
125
|
+
const live =
|
|
126
|
+
root.style.getPropertyValue('--cfm-nps-button-style').trim() ||
|
|
127
|
+
getComputedStyle(root).getPropertyValue('--cfm-nps-button-style').trim();
|
|
128
|
+
if (live === 'standard' || live === 'numbered' || live === 'emoji' || live === 'pill') {
|
|
129
|
+
return live;
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Wizard bridge + survey-ui-config.json override API `buttonStyle` for NPS cells. */
|
|
135
|
+
export function resolveNpsButtonStyle(questionStyle?: string): NpsButtonStyle {
|
|
136
|
+
const live = readLiveNpsButtonStyle();
|
|
137
|
+
if (live) return live;
|
|
138
|
+
|
|
139
|
+
const fromJson = (
|
|
140
|
+
surveyUiConfig.questionTypes as { NPS_SCALE?: { buttonStyle?: string } } | undefined
|
|
141
|
+
)?.NPS_SCALE?.buttonStyle;
|
|
142
|
+
if (
|
|
143
|
+
fromJson === 'standard' ||
|
|
144
|
+
fromJson === 'numbered' ||
|
|
145
|
+
fromJson === 'emoji' ||
|
|
146
|
+
fromJson === 'pill'
|
|
147
|
+
) {
|
|
148
|
+
return fromJson;
|
|
149
|
+
}
|
|
150
|
+
if (
|
|
151
|
+
questionStyle === 'standard' ||
|
|
152
|
+
questionStyle === 'numbered' ||
|
|
153
|
+
questionStyle === 'emoji' ||
|
|
154
|
+
questionStyle === 'pill'
|
|
155
|
+
) {
|
|
156
|
+
return questionStyle;
|
|
157
|
+
}
|
|
158
|
+
return 'numbered';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function resolveNpsHintText(kind: 'min' | 'max', questionFallback: string): string {
|
|
162
|
+
const nps = (
|
|
163
|
+
surveyUiConfig.questionTypes as { NPS_SCALE?: { hintMinText?: string; hintMaxText?: string } } | undefined
|
|
164
|
+
)?.NPS_SCALE;
|
|
165
|
+
const fromJson = kind === 'min' ? nps?.hintMinText : nps?.hintMaxText;
|
|
166
|
+
const trimmed = fromJson?.trim();
|
|
167
|
+
return trimmed || questionFallback;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function resolveNpsHintMinLabel(questionFallback = ''): string {
|
|
171
|
+
return resolveNpsHintText('min', questionFallback);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function resolveNpsHintMaxLabel(questionFallback = ''): string {
|
|
175
|
+
return resolveNpsHintText('max', questionFallback);
|
|
176
|
+
}
|
|
@@ -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',
|
|
@@ -70,14 +70,19 @@ export function PreviewConfigProvider({ children }: { children: ReactNode }) {
|
|
|
70
70
|
const data = event.data;
|
|
71
71
|
if (!data?.type) return;
|
|
72
72
|
if (data.type !== PREVIEW_BRIDGE_SNAPSHOT && data.type !== PREVIEW_BRIDGE_DELTA) return;
|
|
73
|
+
|
|
74
|
+
let shouldBumpRevision = false;
|
|
75
|
+
|
|
73
76
|
if (data.contentPatch?.previewState) {
|
|
74
77
|
setPreviewState((prev) => ({ ...prev, ...data.contentPatch.previewState }));
|
|
78
|
+
shouldBumpRevision = true;
|
|
75
79
|
}
|
|
76
80
|
if (data.contentPatch?.domAttributes) {
|
|
77
81
|
const root = document.documentElement;
|
|
78
82
|
for (const [key, value] of Object.entries(data.contentPatch.domAttributes)) {
|
|
79
83
|
root.setAttribute(key, value as string);
|
|
80
84
|
}
|
|
85
|
+
shouldBumpRevision = true;
|
|
81
86
|
}
|
|
82
87
|
if (data.cssVars) {
|
|
83
88
|
const root = document.documentElement;
|
|
@@ -85,7 +90,9 @@ export function PreviewConfigProvider({ children }: { children: ReactNode }) {
|
|
|
85
90
|
root.style.setProperty(key, value as string);
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
|
-
|
|
93
|
+
if (shouldBumpRevision) {
|
|
94
|
+
setConfigRevision((r) => r + 1);
|
|
95
|
+
}
|
|
89
96
|
applyThemeSideEffects();
|
|
90
97
|
};
|
|
91
98
|
|