@explorer02/cfm-survey-sdk 0.4.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@explorer02/cfm-survey-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -16,14 +16,64 @@ import {
16
16
  mcqSelectedAccentVar,
17
17
  mcqSelectedBgVar,
18
18
  } from '@/lib/surveyUi/selectionStyles';
19
- import { csatColumnLabelStyle, ratingColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
19
+ import { csatColumnLabelStyle, ratingColumnLabelStyle, stackAnchorLabelLines, ratingAnchorLabelPillStyle } from '@/lib/surveyUi/labelStyles';
20
20
 
21
21
  const MATRIX_ROW_LABEL_WIDTH = 'var(--cfm-matrix-row-width, 180px)';
22
22
 
23
23
  type CsatMatrixQuestion = CsatQuestion | RatingMatrixQuestion;
24
24
 
25
25
  function anchorLabelStyle(question: CsatMatrixQuestion) {
26
- return question.type === 'RATING_MATRIX' ? ratingColumnLabelStyle() : csatColumnLabelStyle();
26
+ return question.type === 'RATING_MATRIX' ? ratingAnchorLabelPillStyle() : csatColumnLabelStyle();
27
+ }
28
+
29
+ function anchorLabelPositionPercent(
30
+ index: number,
31
+ labelCount: number,
32
+ columnCount: number,
33
+ isGraphics: boolean,
34
+ question: CsatMatrixQuestion,
35
+ ): number {
36
+ if (labelCount === 1) return 50;
37
+ const useFullSpan = isGraphics || question.type === 'RATING_MATRIX';
38
+ const startPercent = useFullSpan ? 0 : 0.5 / columnCount;
39
+ const rangePercent = useFullSpan ? 1 : (columnCount - 1) / columnCount;
40
+ return (startPercent + (index / (labelCount - 1)) * rangePercent) * 100;
41
+ }
42
+
43
+ function anchorLabelRowMinHeight(question: CsatMatrixQuestion, hasLabels: boolean): string {
44
+ if (question.type === 'RATING_MATRIX' && hasLabels) return '44px';
45
+ return '24px';
46
+ }
47
+
48
+ function renderAnchorLabelContent(label: string, question: CsatMatrixQuestion): React.ReactNode {
49
+ if (question.type !== 'RATING_MATRIX') return label;
50
+ const lines = stackAnchorLabelLines(label);
51
+ if (lines.length === 1) return label;
52
+ return lines.map((line, idx) => (
53
+ <span key={idx} style={{ display: 'block' }}>
54
+ {line}
55
+ </span>
56
+ ));
57
+ }
58
+
59
+ function anchorLabelBoxStyle(question: CsatMatrixQuestion, isGraphics: boolean): React.CSSProperties {
60
+ if (question.type === 'RATING_MATRIX') {
61
+ return {
62
+ textAlign: 'center',
63
+ fontSize: '12px',
64
+ fontWeight: 600,
65
+ ...ratingAnchorLabelPillStyle(),
66
+ };
67
+ }
68
+ return {
69
+ textAlign: 'center',
70
+ fontSize: isGraphics ? '12px' : '13px',
71
+ fontWeight: 600,
72
+ lineHeight: 1.3,
73
+ whiteSpace: isGraphics ? 'normal' : 'nowrap',
74
+ maxWidth: isGraphics ? '72px' : undefined,
75
+ ...anchorLabelStyle(question),
76
+ };
27
77
  }
28
78
 
29
79
  function columnHeaderLabelStyle(question: CsatMatrixQuestion) {
@@ -227,24 +277,25 @@ function CsatMatrixGrid({
227
277
  <div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '12px', minWidth: 0 }}>
228
278
  <div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0, minWidth: 0 }} />
229
279
  <div style={{ flex: 1, display: 'flex', minWidth: 0 }}>
230
- <div style={{ flex: 1, position: 'relative', minHeight: '24px', minWidth: 0, ...(isGraphics ? { margin: '0 8px' } : {}) }}>
280
+ <div style={{
281
+ flex: 1,
282
+ position: 'relative',
283
+ minHeight: anchorLabelRowMinHeight(question, Boolean(showLabels)),
284
+ minWidth: 0,
285
+ ...(isGraphics || question.type === 'RATING_MATRIX' ? { margin: '0 8px' } : {}),
286
+ }}>
231
287
  {labels!.map((label, i) => {
232
- const startPercent = isGraphics ? 0 : 0.5 / m;
233
- const rangePercent = isGraphics ? 1 : (m - 1) / m;
234
- const percent = n === 1 ? 50 : (startPercent + (i / (n - 1)) * rangePercent) * 100;
288
+ const percent = anchorLabelPositionPercent(i, n, m, isGraphics, question);
235
289
 
236
290
  return (
237
291
  <div key={i} style={{
238
292
  position: 'absolute',
239
293
  left: `${percent}%`,
294
+ bottom: 0,
240
295
  transform: 'translateX(-50%)',
241
- textAlign: 'center', fontSize: isGraphics ? '12px' : '13px', fontWeight: 600,
242
- lineHeight: 1.3,
243
- whiteSpace: isGraphics ? 'normal' : 'nowrap',
244
- maxWidth: isGraphics ? '72px' : undefined,
245
- ...anchorLabelStyle(question),
296
+ ...anchorLabelBoxStyle(question, isGraphics),
246
297
  }}>
247
- {label}
298
+ {renderAnchorLabelContent(label, question)}
248
299
  </div>
249
300
  );
250
301
  })}
@@ -465,24 +516,29 @@ function CsatMatrixCarousel({
465
516
  <h3 style={{ fontSize: '18px', fontWeight: 500, color: '#111827', marginBottom: '32px', textAlign: 'center' }} dangerouslySetInnerHTML={{ __html: row.statementText }} />
466
517
 
467
518
  {showLabels && scaleColumns.length > 0 && (
468
- <div style={{ width: '100%', maxWidth: '100%', minWidth: 0, position: 'relative', height: '24px', marginBottom: '16px' }}>
519
+ <div style={{
520
+ width: '100%',
521
+ maxWidth: '100%',
522
+ minWidth: 0,
523
+ position: 'relative',
524
+ minHeight: anchorLabelRowMinHeight(question, Boolean(showLabels)),
525
+ marginBottom: '16px',
526
+ ...(isGraphics || question.type === 'RATING_MATRIX' ? { padding: '0 8px' } : {}),
527
+ }}>
469
528
  {labels!.map((label, i) => {
470
529
  const m = scaleColumns.length;
471
530
  const n = labels!.length;
472
- const startPercent = isGraphics ? 0 : 0.5 / m;
473
- const rangePercent = isGraphics ? 1 : (m - 1) / m;
474
- const percent = n === 1 ? 50 : (startPercent + (i / (n - 1)) * rangePercent) * 100;
531
+ const percent = anchorLabelPositionPercent(i, n, m, isGraphics, question);
475
532
 
476
533
  return (
477
534
  <div key={i} style={{
478
- position: 'absolute', left: `${percent}%`, transform: 'translateX(-50%)',
479
- textAlign: 'center', fontSize: isGraphics ? '12px' : '13px', fontWeight: 600,
480
- lineHeight: 1.3,
481
- whiteSpace: isGraphics ? 'normal' : 'nowrap',
482
- maxWidth: isGraphics ? '72px' : undefined,
483
- ...anchorLabelStyle(question),
535
+ position: 'absolute',
536
+ left: `${percent}%`,
537
+ bottom: 0,
538
+ transform: 'translateX(-50%)',
539
+ ...anchorLabelBoxStyle(question, isGraphics),
484
540
  }}>
485
- {label}
541
+ {renderAnchorLabelContent(label, question)}
486
542
  </div>
487
543
  );
488
544
  })}
@@ -39,10 +39,11 @@ export default function Footer() {
39
39
  data-cfm-logo
40
40
  src={seedLogoSrc!}
41
41
  alt=""
42
- className="object-contain"
42
+ className="object-contain object-left"
43
43
  style={{
44
- width: 'var(--cfm-footer-logo-width, 120px)',
45
44
  height: 'var(--cfm-footer-logo-height, 32px)',
45
+ maxWidth: 'var(--cfm-footer-logo-width, 120px)',
46
+ width: 'auto',
46
47
  }}
47
48
  />
48
49
  </div>
@@ -64,3 +64,30 @@ export function ratingColumnLabelStyle(): CSSProperties {
64
64
  export function sliderTickLabelStyle(): CSSProperties {
65
65
  return columnLabelPillStyle('sliderTick');
66
66
  }
67
+
68
+ /** Split multi-word anchor labels into stacked lines for equal-width matrix headers. */
69
+ export function stackAnchorLabelLines(label: string): string[] {
70
+ const trimmed = label.trim();
71
+ if (!trimmed) return [''];
72
+ const words = trimmed.split(/\s+/);
73
+ if (words.length <= 1) return [trimmed];
74
+ const mid = Math.ceil(words.length / 2);
75
+ return [words.slice(0, mid).join(' '), words.slice(mid).join(' ')].filter(Boolean);
76
+ }
77
+
78
+ /** Rating matrix anchor pills — stacked text, fixed width, same look on every format. */
79
+ export function ratingAnchorLabelPillStyle(): CSSProperties {
80
+ return {
81
+ ...columnLabelPillStyle('rating'),
82
+ display: 'inline-flex',
83
+ flexDirection: 'column',
84
+ alignItems: 'center',
85
+ justifyContent: 'center',
86
+ whiteSpace: 'normal',
87
+ maxWidth: '72px',
88
+ minWidth: '52px',
89
+ padding: '3px 6px',
90
+ wordBreak: 'break-word',
91
+ lineHeight: 1.15,
92
+ };
93
+ }
@@ -136,6 +136,15 @@ export function resolveNpsButtonStyle(questionStyle?: string): NpsButtonStyle {
136
136
  const live = readLiveNpsButtonStyle();
137
137
  if (live) return live;
138
138
 
139
+ if (
140
+ questionStyle === 'standard' ||
141
+ questionStyle === 'numbered' ||
142
+ questionStyle === 'emoji' ||
143
+ questionStyle === 'pill'
144
+ ) {
145
+ return questionStyle;
146
+ }
147
+
139
148
  const fromJson = (
140
149
  surveyUiConfig.questionTypes as { NPS_SCALE?: { buttonStyle?: string } } | undefined
141
150
  )?.NPS_SCALE?.buttonStyle;
@@ -147,18 +156,24 @@ export function resolveNpsButtonStyle(questionStyle?: string): NpsButtonStyle {
147
156
  ) {
148
157
  return fromJson;
149
158
  }
150
- if (
151
- questionStyle === 'standard' ||
152
- questionStyle === 'numbered' ||
153
- questionStyle === 'emoji' ||
154
- questionStyle === 'pill'
155
- ) {
156
- return questionStyle;
157
- }
158
159
  return 'numbered';
159
160
  }
160
161
 
162
+ function readLiveNpsHintText(kind: 'min' | 'max'): string | null {
163
+ if (typeof window === 'undefined') return null;
164
+ const root = document.documentElement;
165
+ const cssVar = kind === 'min' ? '--cfm-hint-min-text' : '--cfm-hint-max-text';
166
+ const fromCss =
167
+ root.style.getPropertyValue(cssVar).trim() ||
168
+ getComputedStyle(root).getPropertyValue(cssVar).trim();
169
+ return fromCss || null;
170
+ }
171
+
161
172
  function resolveNpsHintText(kind: 'min' | 'max', questionFallback: string): string {
173
+ const fromCss = readLiveNpsHintText(kind);
174
+ if (fromCss) return fromCss;
175
+ if (questionFallback.trim()) return questionFallback;
176
+
162
177
  const nps = (
163
178
  surveyUiConfig.questionTypes as { NPS_SCALE?: { hintMinText?: string; hintMaxText?: string } } | undefined
164
179
  )?.NPS_SCALE;
@@ -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,17 +87,47 @@ 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
 
74
- let shouldBumpRevision = false;
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
+ }
75
115
 
76
- if (data.contentPatch?.previewState) {
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) {
77
124
  setPreviewState((prev) => ({ ...prev, ...data.contentPatch.previewState }));
78
125
  shouldBumpRevision = true;
126
+ } else if (data.cssVars) {
127
+ setPreviewState((prev) => previewStateFromCssVars(data.cssVars, prev));
128
+ shouldBumpRevision = true;
79
129
  }
130
+
80
131
  if (data.contentPatch?.domAttributes) {
81
132
  const root = document.documentElement;
82
133
  for (const [key, value] of Object.entries(data.contentPatch.domAttributes)) {
@@ -84,12 +135,7 @@ export function PreviewConfigProvider({ children }: { children: ReactNode }) {
84
135
  }
85
136
  shouldBumpRevision = true;
86
137
  }
87
- if (data.cssVars) {
88
- const root = document.documentElement;
89
- for (const [key, value] of Object.entries(data.cssVars)) {
90
- root.style.setProperty(key, value as string);
91
- }
92
- }
138
+
93
139
  if (shouldBumpRevision) {
94
140
  setConfigRevision((r) => r + 1);
95
141
  }
@@ -34,7 +34,7 @@ function QuestionPreviewInner({ question, initialValue }: QuestionPreviewProps)
34
34
 
35
35
  const mergedQuestion = useMemo(
36
36
  () => mergePreviewQuestion(question, previewState),
37
- [question, previewState],
37
+ [question, previewState, configRevision],
38
38
  );
39
39
 
40
40
  const heatmapInitial =
@@ -22,15 +22,15 @@ function SurveyPagePreviewInner() {
22
22
 
23
23
  const mcqQuestion = useMemo(
24
24
  () => mergeSurveyPageQuestion(FIXTURE_SURVEY_PAGE_MCQ, previewState),
25
- [previewState],
25
+ [previewState, configRevision],
26
26
  );
27
27
  const matrixQuestion = useMemo(
28
28
  () => mergeSurveyPageQuestion(FIXTURE_SURVEY_PAGE_CFM_LIKERT, previewState),
29
- [previewState],
29
+ [previewState, configRevision],
30
30
  );
31
31
  const npsQuestion = useMemo(
32
32
  () => mergeSurveyPageQuestion(FIXTURE_SURVEY_PAGE_NPS, previewState),
33
- [previewState],
33
+ [previewState, configRevision],
34
34
  );
35
35
 
36
36
  const allQuestions = [mcqQuestion, matrixQuestion, npsQuestion];
@@ -181,7 +181,7 @@ export const FIXTURE_CFM_LIKERT = base({
181
181
  showColumnHeaders: true,
182
182
  repeatColumnHeaders: false,
183
183
  hasNotApplicableOption: false,
184
- statementRows: MATRIX_ROWS_MULTI,
184
+ statementRows: MATRIX_ROW_SINGLE,
185
185
  scaleColumns: LIKERT_COLUMNS,
186
186
  });
187
187
 
@@ -221,7 +221,7 @@ export const FIXTURE_CFM_CAROUSEL = base({
221
221
  showColumnHeaders: true,
222
222
  repeatColumnHeaders: false,
223
223
  hasNotApplicableOption: false,
224
- statementRows: MATRIX_ROWS_MULTI,
224
+ statementRows: MATRIX_ROW_SINGLE,
225
225
  scaleColumns: LIKERT_COLUMNS,
226
226
  });
227
227
 
@@ -267,7 +267,7 @@ export const FIXTURE_CSAT_NUMBERED = base({
267
267
  type: 'CSAT_MATRIX',
268
268
  displayStyle: 'numbered',
269
269
  gridLayout: 'standard',
270
- statementRows: MATRIX_ROWS_MULTI,
270
+ statementRows: MATRIX_ROW_SINGLE,
271
271
  scaleColumns: LIKERT_COLUMNS,
272
272
  scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
273
273
  });
@@ -307,7 +307,7 @@ export const FIXTURE_RATING_STAR = base({
307
307
  displayStyle: 'star',
308
308
  gridLayout: 'standard',
309
309
  showColumnHeaders: true,
310
- statementRows: MATRIX_ROWS_MULTI,
310
+ statementRows: MATRIX_ROW_SINGLE,
311
311
  scaleColumns: RATING_TEN_COLUMNS,
312
312
  scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
313
313
  });
@@ -316,7 +316,7 @@ export const FIXTURE_RATING_NUMBERED = base({
316
316
  type: 'RATING_MATRIX',
317
317
  displayStyle: 'numbered',
318
318
  gridLayout: 'standard',
319
- statementRows: MATRIX_ROWS_MULTI,
319
+ statementRows: MATRIX_ROW_SINGLE,
320
320
  scaleColumns: RATING_TEN_COLUMNS,
321
321
  scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
322
322
  });
@@ -352,10 +352,7 @@ export const FIXTURE_RATING_SLIDER = base({
352
352
  type: 'RATING_MATRIX',
353
353
  displayStyle: 'graphics',
354
354
  gridLayout: 'standard',
355
- statementRows: [
356
- { id: 'r1', statementText: 'Statement Item 1' },
357
- { id: 'r2', statementText: 'Statement Item 2' },
358
- ],
355
+ statementRows: MATRIX_ROW_SINGLE,
359
356
  scaleColumns: [
360
357
  { id: 'c1', label: 'Likely' },
361
358
  { id: 'c2', label: 'Unlikely' },
@@ -37,6 +37,46 @@ const MULTI_STATEMENT_MATRIX_TYPES = new Set([
37
37
  'SLIDER_MATRIX',
38
38
  ]);
39
39
 
40
+ /** Each preview iframe loads one fixture — infer its format tab from fixture shape. */
41
+ function inferPreviewKeyFromQuestion(q: SurveyQuestion): string | null {
42
+ if (q.type === 'CFM_MATRIX') {
43
+ if (q.statementLayout === 'bipolar') return 'CFM_bipolar';
44
+ if (q.transposeTable) return 'CFM_transpose';
45
+ if (q.gridLayout === 'carousel') return 'CFM_carousel';
46
+ if (q.gridLayout === 'dropdown') return 'CFM_dropdown';
47
+ return 'CFM_likert';
48
+ }
49
+ if (q.type === 'RANK_ORDER') {
50
+ const mode = (q as { interactionMode?: string }).interactionMode;
51
+ if (mode === 'dropdown') return 'RANK_dropdown';
52
+ return 'RANK_drag';
53
+ }
54
+ return null;
55
+ }
56
+
57
+ function resolvePreviewKey(
58
+ previewState: PreviewStatePatch,
59
+ question: SurveyQuestion,
60
+ ): string {
61
+ if (previewState.activePreviewKey === 'survey_page') return 'survey_page';
62
+ const inferred = inferPreviewKeyFromQuestion(question);
63
+ if (inferred) return inferred;
64
+ return previewState.activePreviewKey ?? '';
65
+ }
66
+
67
+ function resolveMultiStatementFlag(
68
+ previewState: PreviewStatePatch,
69
+ ): boolean | undefined {
70
+ if (previewState.multiStatement !== undefined) return previewState.multiStatement;
71
+ if (typeof document === 'undefined') return undefined;
72
+ const css = getComputedStyle(document.documentElement)
73
+ .getPropertyValue('--cfm-matrix-multi-statement')
74
+ .trim();
75
+ if (css === '1') return true;
76
+ if (css === '0') return false;
77
+ return undefined;
78
+ }
79
+
40
80
  export function applyMatrixRowCount<T extends MatrixRowCapable>(
41
81
  q: T,
42
82
  multiStatement: boolean | undefined,
@@ -52,18 +92,27 @@ export function applyMatrixRowCount<T extends MatrixRowCapable>(
52
92
 
53
93
  if (multiStatement === true) {
54
94
  if (isBipolar) {
55
- return { ...q, statementRows: BIPOLAR_ROWS_MULTI } as T;
95
+ return {
96
+ ...q,
97
+ statementRows: BIPOLAR_ROWS_MULTI,
98
+ showColumnHeaders: false,
99
+ statementLayout: 'bipolar',
100
+ } as T;
56
101
  }
57
102
  if (isSlider) {
58
103
  return { ...q, statementRows: SLIDER_ROWS_MULTI } as T;
59
104
  }
60
- if (q.statementRows.length >= 3) return q;
61
105
  return { ...q, statementRows: MATRIX_ROWS_MULTI } as T;
62
106
  }
63
107
 
64
108
  if (multiStatement === false) {
65
109
  if (isBipolar) {
66
- return { ...q, statementRows: BIPOLAR_ROWS_SINGLE } as T;
110
+ return {
111
+ ...q,
112
+ statementRows: BIPOLAR_ROWS_SINGLE,
113
+ showColumnHeaders: false,
114
+ statementLayout: 'bipolar',
115
+ } as T;
67
116
  }
68
117
  if (isSlider) {
69
118
  return { ...q, statementRows: SLIDER_ROWS_SINGLE } as T;
@@ -71,7 +120,6 @@ export function applyMatrixRowCount<T extends MatrixRowCapable>(
71
120
  return { ...q, statementRows: [...MATRIX_ROW_SINGLE] } as T;
72
121
  }
73
122
 
74
- // Before bridge snapshot — keep fixture row count (single-row fixtures stay single).
75
123
  return q;
76
124
  }
77
125
 
@@ -84,7 +132,19 @@ function applyCfmMatrixLayout(
84
132
  },
85
133
  previewKey: string,
86
134
  ): SurveyQuestion {
87
- if (previewKey === 'survey_page' || previewKey === 'CFM_likert' || !previewKey.startsWith('CFM_')) {
135
+ if (previewKey === 'survey_page') {
136
+ return {
137
+ ...q,
138
+ statementLayout: 'likert',
139
+ gridLayout: 'standard',
140
+ transposeTable: false,
141
+ showColumnHeaders: true,
142
+ } as SurveyQuestion;
143
+ }
144
+ if (!previewKey) {
145
+ return q as SurveyQuestion;
146
+ }
147
+ if (previewKey === 'CFM_likert' || !previewKey.startsWith('CFM_')) {
88
148
  return {
89
149
  ...q,
90
150
  statementLayout: 'likert',
@@ -120,7 +180,7 @@ export function mergePreviewQuestion(
120
180
  previewState: PreviewStatePatch,
121
181
  ): SurveyQuestion {
122
182
  let q = { ...question } as SurveyQuestion;
123
- const previewKey = previewState.activePreviewKey ?? '';
183
+ const previewKey = resolvePreviewKey(previewState, question);
124
184
  const isSurveyPage = previewKey === 'survey_page';
125
185
 
126
186
  if (previewState.textfieldPlaceholder && q.type === 'TEXTFIELD') {
@@ -159,7 +219,7 @@ export function mergePreviewQuestion(
159
219
  }
160
220
  }
161
221
 
162
- return applyMatrixRowCount(q, previewState.multiStatement, {
222
+ return applyMatrixRowCount(q, resolveMultiStatementFlag(previewState), {
163
223
  preserveSurveyPageRows: isSurveyPage,
164
224
  });
165
225
  }
@@ -90,6 +90,14 @@ html.cfm-live-edit .cfm-footer-inner {
90
90
  flex-direction: var(--cfm-footer-flex-direction, row) !important;
91
91
  }
92
92
 
93
+ html.cfm-live-edit [data-cfm-footer-logo] {
94
+ object-fit: contain !important;
95
+ object-position: left center !important;
96
+ height: var(--cfm-footer-logo-height, 32px) !important;
97
+ max-width: var(--cfm-footer-logo-width, 120px) !important;
98
+ width: auto !important;
99
+ }
100
+
93
101
  html.cfm-live-edit .rounded-xl,
94
102
  html.cfm-live-edit .rounded-md,
95
103
  html.cfm-live-edit .rounded-lg {
@@ -228,6 +228,19 @@ body {
228
228
  grid-row: 1;
229
229
  justify-self: var(--cfm-footer-logo-justify, start);
230
230
  align-self: end;
231
+ display: flex;
232
+ justify-content: flex-start;
233
+ width: 100%;
234
+ max-width: 100%;
235
+ }
236
+
237
+ .cfm-footer-logo-slot img,
238
+ [data-cfm-footer-logo] {
239
+ object-fit: contain;
240
+ object-position: left center;
241
+ height: var(--cfm-footer-logo-height, 32px);
242
+ max-width: var(--cfm-footer-logo-width, 120px);
243
+ width: auto;
231
244
  }
232
245
 
233
246
  .cfm-footer-links {
@@ -1 +1 @@
1
- import{u as x,j as e}from"./index-2Bi_7Fdh.js";import{b as t}from"./vendor-BwkXDkd3.js";function p(d=150){const l=x(r=>r.config),[a,s]=t.useState(l);return t.useEffect(()=>{const r=window.setTimeout(()=>s(l),d);return()=>window.clearTimeout(r)},[l,d]),a}const h=t.memo(function(){const l=p(150),a=x(n=>n.logoPreviewDataUrl),{global:s}=l,{layout:r,colorScheme:o,buttons:c}=s,m=t.useMemo(()=>r.borderStyle==="sharp"?"rounded-none":r.borderStyle==="pill"?"rounded-[32px]":"rounded-2xl",[r.borderStyle]),i=t.useMemo(()=>r.fontStyle==="serif"?"Georgia, serif":r.fontStyle==="system"?"system-ui, sans-serif":"Inter, sans-serif",[r.fontStyle]),u=a??s.logo.url,f=t.useMemo(()=>({backgroundColor:o.background,color:o.text,borderColor:o.secondary,fontFamily:i}),[o,i]);return e.jsxs("div",{className:`overflow-hidden border shadow-md ${m}`,style:f,children:[r.showHeader&&e.jsxs("div",{className:"flex items-center gap-3 border-b px-5 py-4",style:{borderColor:o.secondary},children:[u&&e.jsx("img",{src:u,alt:"Logo",className:"max-h-6 w-auto object-contain",loading:"lazy"}),e.jsx("span",{className:"text-xs font-semibold tracking-wider",children:s.companyName})]}),e.jsxs("div",{className:"space-y-6 p-6",children:[r.showProgressBar&&e.jsxs("div",{className:"space-y-1.5",children:[e.jsxs("div",{className:"flex justify-between text-[10px] font-semibold text-slate-400",children:[e.jsx("span",{children:"Progress"}),e.jsx("span",{children:"35%"})]}),e.jsx("div",{className:"h-1.5 w-full overflow-hidden rounded-full bg-slate-100",children:e.jsx("div",{className:"h-full w-[35%] rounded-full",style:{backgroundColor:o.accent}})})]}),e.jsxs("div",{className:"space-y-3",children:[e.jsxs("h4",{className:"flex items-start gap-1.5 text-sm font-bold leading-snug",children:[r.showQuestionNumbers&&e.jsx("span",{className:"font-medium text-slate-400",children:"Q1."}),e.jsx("span",{children:s.surveyTitle}),r.showRequiredAsterisk&&e.jsx("span",{className:"text-red-500",children:"*"})]}),e.jsx("p",{className:"text-[11px] text-slate-400",children:"How would you rate your overall experience?"}),e.jsx("div",{className:"grid grid-cols-5 gap-2",children:[1,2,3,4,5].map(n=>e.jsx("div",{className:`flex h-9 items-center justify-center border text-xs font-semibold ${r.borderStyle==="pill"?"rounded-full":r.borderStyle==="sharp"?"rounded-none":"rounded-lg"}`,style:n===1?{backgroundColor:o.secondary,borderColor:o.primary,color:o.primary}:{backgroundColor:o.background,borderColor:o.secondary,color:o.text},children:n},n))})]})]}),e.jsxs("div",{className:"flex items-center justify-between border-t bg-slate-50/50 px-6 py-4",children:[r.showBackButton&&e.jsx("span",{className:`border px-3 py-1.5 text-[10px] font-semibold ${r.borderStyle==="pill"?"rounded-full":r.borderStyle==="sharp"?"rounded-none":"rounded-lg"}`,style:{borderColor:o.secondary,color:o.text},children:c.back}),e.jsx("div",{className:"flex-1"}),r.showNextButton&&e.jsx("span",{className:`px-4 py-1.5 text-[10px] font-bold ${r.borderStyle==="pill"?"rounded-full":r.borderStyle==="sharp"?"rounded-none":"rounded-lg"}`,style:{backgroundColor:o.primary,color:o.buttonText},children:c.next})]})]})});export{h as PreviewMock};
1
+ import{u as x,j as e}from"./index-C7RSJr09.js";import{b as t}from"./vendor-BwkXDkd3.js";function p(d=150){const l=x(r=>r.config),[a,s]=t.useState(l);return t.useEffect(()=>{const r=window.setTimeout(()=>s(l),d);return()=>window.clearTimeout(r)},[l,d]),a}const h=t.memo(function(){const l=p(150),a=x(n=>n.logoPreviewDataUrl),{global:s}=l,{layout:r,colorScheme:o,buttons:c}=s,m=t.useMemo(()=>r.borderStyle==="sharp"?"rounded-none":r.borderStyle==="pill"?"rounded-[32px]":"rounded-2xl",[r.borderStyle]),i=t.useMemo(()=>r.fontStyle==="serif"?"Georgia, serif":r.fontStyle==="system"?"system-ui, sans-serif":"Inter, sans-serif",[r.fontStyle]),u=a??s.logo.url,f=t.useMemo(()=>({backgroundColor:o.background,color:o.text,borderColor:o.secondary,fontFamily:i}),[o,i]);return e.jsxs("div",{className:`overflow-hidden border shadow-md ${m}`,style:f,children:[r.showHeader&&e.jsxs("div",{className:"flex items-center gap-3 border-b px-5 py-4",style:{borderColor:o.secondary},children:[u&&e.jsx("img",{src:u,alt:"Logo",className:"max-h-6 w-auto object-contain",loading:"lazy"}),e.jsx("span",{className:"text-xs font-semibold tracking-wider",children:s.companyName})]}),e.jsxs("div",{className:"space-y-6 p-6",children:[r.showProgressBar&&e.jsxs("div",{className:"space-y-1.5",children:[e.jsxs("div",{className:"flex justify-between text-[10px] font-semibold text-slate-400",children:[e.jsx("span",{children:"Progress"}),e.jsx("span",{children:"35%"})]}),e.jsx("div",{className:"h-1.5 w-full overflow-hidden rounded-full bg-slate-100",children:e.jsx("div",{className:"h-full w-[35%] rounded-full",style:{backgroundColor:o.accent}})})]}),e.jsxs("div",{className:"space-y-3",children:[e.jsxs("h4",{className:"flex items-start gap-1.5 text-sm font-bold leading-snug",children:[r.showQuestionNumbers&&e.jsx("span",{className:"font-medium text-slate-400",children:"Q1."}),e.jsx("span",{children:s.surveyTitle}),r.showRequiredAsterisk&&e.jsx("span",{className:"text-red-500",children:"*"})]}),e.jsx("p",{className:"text-[11px] text-slate-400",children:"How would you rate your overall experience?"}),e.jsx("div",{className:"grid grid-cols-5 gap-2",children:[1,2,3,4,5].map(n=>e.jsx("div",{className:`flex h-9 items-center justify-center border text-xs font-semibold ${r.borderStyle==="pill"?"rounded-full":r.borderStyle==="sharp"?"rounded-none":"rounded-lg"}`,style:n===1?{backgroundColor:o.secondary,borderColor:o.primary,color:o.primary}:{backgroundColor:o.background,borderColor:o.secondary,color:o.text},children:n},n))})]})]}),e.jsxs("div",{className:"flex items-center justify-between border-t bg-slate-50/50 px-6 py-4",children:[r.showBackButton&&e.jsx("span",{className:`border px-3 py-1.5 text-[10px] font-semibold ${r.borderStyle==="pill"?"rounded-full":r.borderStyle==="sharp"?"rounded-none":"rounded-lg"}`,style:{borderColor:o.secondary,color:o.text},children:c.back}),e.jsx("div",{className:"flex-1"}),r.showNextButton&&e.jsx("span",{className:`px-4 py-1.5 text-[10px] font-bold ${r.borderStyle==="pill"?"rounded-full":r.borderStyle==="sharp"?"rounded-none":"rounded-lg"}`,style:{backgroundColor:o.primary,color:o.buttonText},children:c.next})]})]})});export{h as PreviewMock};
@@ -0,0 +1 @@
1
+ import{u as m,j as e,T as P,S as b,R as d,C as i,N as t,a as R,M as z}from"./index-C7RSJr09.js";import"./vendor-BwkXDkd3.js";function C({values:n,onPatch:r}){return e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsx(i,{label:"Label text color",value:n.columnLabelColor,onChange:a=>r({columnLabelColor:a})}),e.jsx(i,{label:"Label background",value:n.columnLabelBgColor,onChange:a=>r({columnLabelBgColor:a})})]})}function T({values:n,onPatch:r}){return e.jsxs("div",{className:"grid grid-cols-1 gap-3 sm:grid-cols-2",children:[e.jsxs("label",{className:"space-y-1 sm:col-span-2",children:[e.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-wider text-slate-500",children:"Min hint label"}),e.jsx("input",{className:"w-full rounded-lg border border-white/10 bg-slate-950/50 px-3 py-2 text-sm text-white",value:n.hintMinText,onChange:a=>r({hintMinText:a.target.value})})]}),e.jsxs("label",{className:"space-y-1 sm:col-span-2",children:[e.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-wider text-slate-500",children:"Max hint label"}),e.jsx("input",{className:"w-full rounded-lg border border-white/10 bg-slate-950/50 px-3 py-2 text-sm text-white",value:n.hintMaxText,onChange:a=>r({hintMaxText:a.target.value})})]}),e.jsx(i,{label:"Hint text color",value:n.hintLabelColor,onChange:a=>r({hintLabelColor:a})}),e.jsx(i,{label:"Hint label background",value:n.hintLabelBgColor,onChange:a=>r({hintLabelBgColor:a})})]})}function M({values:n,onPatch:r,count:a=11}){return e.jsxs("div",{className:"space-y-3",children:[e.jsx(b,{label:"Number label style",value:n.numberLabelMode,onChange:c=>r({numberLabelMode:c}),options:[{value:"traffic",label:"Traffic light (default)"},{value:"monochrome",label:"Monochrome (one color)"},{value:"individual",label:"Individual per number"}]}),n.numberLabelMode==="monochrome"&&e.jsx(i,{label:"Monochrome color",value:n.monochromeNumberColor,onChange:c=>r({monochromeNumberColor:c})}),n.numberLabelMode==="individual"&&e.jsx("div",{className:"grid grid-cols-2 gap-2 sm:grid-cols-3",children:Array.from({length:a},(c,x)=>e.jsx(i,{label:`#${x}`,value:n.individualNumberColors[x]??"#9CA3AF",onChange:j=>{const h=[...n.individualNumberColors];h[x]=j,r({individualNumberColors:h})}},x))})]})}function s({title:n,children:r}){return e.jsxs("div",{className:"space-y-3 rounded-xl border border-white/5 bg-slate-950/20 p-4",children:[e.jsx("h4",{className:"text-xs font-semibold uppercase tracking-wider text-slate-400",children:n}),r]})}function I({type:n,activeFormat:r}){const a=m(l=>l.config.questionTypes[n]),c=m(l=>l.updateQuestionType),x=m(l=>l.previewMultiStatementByType),j=m(l=>l.setPreviewMultiStatementForType),h=m(l=>l.heatmapPreviewPin);if(!a)return e.jsx("p",{className:"text-sm text-slate-500",children:"No configuration available for this type."});const o=l=>c(n,l),f=z.includes(n),B=!!x[n],g=r.includes("star"),u=r.includes("emoji"),p=r.includes("numbered"),v=r.includes("drag"),S=r.includes("dropdown"),N=r.includes("radio"),k=r==="CFM_bipolar",w=r.includes("graphics"),y=a.optionStyle??a.cardStyle??"outlined",L=a.buttonStyle??"numbered";return e.jsxs("div",{className:"space-y-4",children:[f&&e.jsx(P,{label:"Preview multiple statements",checked:B,onChange:l=>j(n,l)}),n==="MCQ"&&"optionGap"in a&&e.jsx(s,{title:"Option style",children:e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsx(b,{label:"Option style",value:a.optionStyle??a.cardStyle??"outlined",onChange:l=>o({optionStyle:l}),options:[{value:"outlined",label:"Outlined"},{value:"filled",label:"Filled"},{value:"minimal",label:"Minimal"}]}),e.jsx(d,{label:"Option gap",value:a.optionGap,onChange:l=>o({optionGap:l}),min:0,max:32}),e.jsx(d,{label:"Border radius",value:a.borderRadius,onChange:l=>o({borderRadius:l}),min:0,max:24}),e.jsx(d,{label:"Option padding",value:a.optionPadding??12,onChange:l=>o({optionPadding:l}),min:4,max:24}),e.jsx(i,{label:"Hover border",value:a.hoverBorderColor,onChange:l=>o({hoverBorderColor:l})}),y==="filled"&&e.jsx(i,{label:"Unselected fill color",value:a.filledOptionBg??"#F9FAFB",onChange:l=>o({filledOptionBg:l})})]})}),n==="TEXTFIELD"&&"defaultPlaceholder"in a&&e.jsx(s,{title:"Text input",children:e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsxs("label",{className:"space-y-1 sm:col-span-2",children:[e.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-wider text-slate-500",children:"Default placeholder"}),e.jsx("input",{className:"w-full rounded-lg border border-white/10 bg-slate-950/50 px-3 py-2 text-sm text-white",value:a.defaultPlaceholder,onChange:l=>o({defaultPlaceholder:l.target.value})})]}),e.jsx(t,{label:"Input radius (px)",value:a.inputRadius,onChange:l=>o({inputRadius:l}),min:0,max:24}),e.jsx(i,{label:"Border color",value:a.borderColor,onChange:l=>o({borderColor:l})}),e.jsx(i,{label:"Placeholder color",value:a.placeholderColor,onChange:l=>o({placeholderColor:l})}),e.jsx(t,{label:"Input height (px)",value:a.inputHeight,onChange:l=>o({inputHeight:l}),min:32,max:64}),e.jsx(t,{label:"Input padding (px)",value:a.inputPadding,onChange:l=>o({inputPadding:l}),min:8,max:24})]})}),n==="NPS_SCALE"&&"buttonStyle"in a&&e.jsxs(e.Fragment,{children:[e.jsxs(s,{title:"NPS buttons",children:[e.jsx(b,{label:"Button style",value:a.buttonStyle,onChange:l=>o({buttonStyle:l}),options:[{value:"standard",label:"Radio"},{value:"numbered",label:"Numbered badges"}]}),e.jsx("p",{className:"text-xs text-slate-500",children:"Selected cell and focus ring come from Theme → Question card."})]}),e.jsx(s,{title:"Hint labels",children:e.jsx(T,{values:a,onPatch:o})}),L==="numbered"&&e.jsx(s,{title:"Number labels",children:e.jsx(M,{values:a,onPatch:o})}),e.jsx(s,{title:"Track & cells",children:e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsx(t,{label:"Cell size (px)",value:a.cellSize,onChange:l=>o({cellSize:l}),min:24,max:56}),e.jsx(t,{label:"Cell gap (px)",value:a.cellGap,onChange:l=>o({cellGap:l}),min:0,max:16}),e.jsx(i,{label:"Track bar",value:a.trackBarColor,onChange:l=>o({trackBarColor:l})})]})})]}),n==="CFM_MATRIX"&&"rowLabelWidth"in a&&e.jsxs(e.Fragment,{children:[e.jsx(s,{title:"Matrix layout",children:e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsx(t,{label:"Row label width (px)",value:a.rowLabelWidth,onChange:l=>o({rowLabelWidth:l}),min:120,max:280}),k&&e.jsx(t,{label:"Bipolar column width (%)",value:a.bipolarColumnWidthPercent,onChange:l=>o({bipolarColumnWidthPercent:l}),min:15,max:40}),e.jsx(t,{label:"Cell padding (px)",value:a.cellPadding,onChange:l=>o({cellPadding:l}),min:4,max:24})]})}),e.jsx(s,{title:"Label items",children:e.jsx(C,{values:a,onPatch:o})})]}),n==="CSAT_MATRIX"&&"emojiSize"in a&&e.jsxs(e.Fragment,{children:[(u||g||p)&&e.jsxs(s,{title:`${R.CSAT_MATRIX} — ${r.replace("CSAT_","")}`,children:[e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[u&&e.jsx(t,{label:"Emoji size (px)",value:a.emojiSize,onChange:l=>o({emojiSize:l}),min:16,max:40}),g&&e.jsx(i,{label:"Star color",value:a.accentColor,onChange:l=>o({accentColor:l})}),(u||g||p)&&e.jsx(d,{label:"Unselected opacity",value:Math.round(a.unselectedOpacity*100),onChange:l=>o({unselectedOpacity:l/100}),min:10,max:100})]}),e.jsx("p",{className:"text-xs text-slate-500",children:"Selected cell color and focus ring come from Theme → Question card."})]}),e.jsxs(s,{title:"Label items",children:[e.jsx("p",{className:"text-xs text-slate-500",children:"Applies to the 5 text labels above options in all formats."}),e.jsx(C,{values:a,onPatch:o})]}),w&&e.jsx(s,{title:"Graphics track",children:e.jsx(i,{label:"Track color",value:a.graphicsTrackColor,onChange:l=>o({graphicsTrackColor:l})})})]}),n==="RATING_MATRIX"&&"unselectedStarOpacity"in a&&e.jsxs(e.Fragment,{children:[(u||g||p||N)&&e.jsxs(s,{title:`Rating — ${r.replace("RATING_","")}`,children:[e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[u&&e.jsx(t,{label:"Emoji size (px)",value:a.emojiSize,onChange:l=>o({emojiSize:l}),min:16,max:40}),g&&e.jsx(i,{label:"Star color",value:a.starColor,onChange:l=>o({starColor:l})}),(u||g||p||N)&&e.jsx(d,{label:"Unselected opacity",value:Math.round(a.unselectedStarOpacity*100),onChange:l=>o({unselectedStarOpacity:l/100}),min:10,max:100})]}),e.jsx("p",{className:"text-xs text-slate-500",children:"Selected cell color and focus ring come from Theme → Question card."})]}),e.jsxs(s,{title:"Label items",children:[e.jsx("p",{className:"text-xs text-slate-500",children:"Applies to the 5 text anchor labels in all formats."}),e.jsx(C,{values:a,onPatch:o})]}),w&&e.jsx(s,{title:"Graphics track",children:e.jsx(i,{label:"Track color",value:a.graphicsTrackColor,onChange:l=>o({graphicsTrackColor:l})})})]}),n==="SLIDER_MATRIX"&&"trackColor"in a&&e.jsxs(e.Fragment,{children:[e.jsx(s,{title:"Slider track",children:e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsx(i,{label:"Track",value:a.trackColor,onChange:l=>o({trackColor:l})}),e.jsx(i,{label:"Thumb",value:a.thumbColor,onChange:l=>o({thumbColor:l})}),e.jsx(t,{label:"Row label width (px)",value:a.rowLabelWidth,onChange:l=>o({rowLabelWidth:l}),min:120,max:280}),e.jsx(i,{label:"Row band",value:a.rowBandColor,onChange:l=>o({rowBandColor:l})}),e.jsx(t,{label:"Tick badge size (px)",value:a.tickBadgeSize,onChange:l=>o({tickBadgeSize:l}),min:20,max:40})]})}),e.jsx(s,{title:"Anchor hint labels",children:e.jsx(T,{values:a,onPatch:o})}),e.jsx(s,{title:"Tick number labels",children:e.jsx(M,{values:a,onPatch:o,count:11})})]}),n==="FILE_UPLOAD"&&"dropzoneStyle"in a&&e.jsx(s,{title:"Dropzone",children:e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsx(b,{label:"Dropzone border",value:a.dropzoneStyle,onChange:l=>o({dropzoneStyle:l}),options:[{value:"dashed",label:"Dashed"},{value:"solid",label:"Solid"}]}),e.jsx(i,{label:"Border color",value:a.borderColor??a.accentColor,onChange:l=>o({borderColor:l})}),e.jsx(i,{label:"Fill color",value:a.dropzoneFillColor,onChange:l=>o({dropzoneFillColor:l})}),e.jsx(t,{label:"Padding (px)",value:a.dropzonePadding,onChange:l=>o({dropzonePadding:l}),min:12,max:48})]})}),n==="TEXT_AND_MEDIA"&&"mediaMaxWidth"in a&&e.jsx(s,{title:"Text & media",children:e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsx(d,{label:"Media max width (%)",value:a.mediaMaxWidth,onChange:l=>o({mediaMaxWidth:l}),min:50,max:100}),e.jsx(d,{label:"Media corner radius",value:a.mediaRadius,onChange:l=>o({mediaRadius:l}),min:0,max:24}),e.jsx(i,{label:"Caption color",value:a.captionColor,onChange:l=>o({captionColor:l})}),e.jsx(t,{label:"Caption size (px)",value:a.captionSize,onChange:l=>o({captionSize:l}),min:10,max:24})]})}),n==="HEATMAP"&&"pinColor"in a&&e.jsx(s,{title:"Heatmap pin",children:e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsx(i,{label:"Pin color",value:a.pinColor,onChange:l=>o({pinColor:l})}),e.jsx(i,{label:"Pin border",value:a.pinBorderColor,onChange:l=>o({pinBorderColor:l})}),e.jsx(d,{label:"Pin size",value:a.pinSize,onChange:l=>o({pinSize:l}),min:8,max:32}),e.jsx(d,{label:"Overlay opacity",value:Math.round(a.overlayOpacity*100),onChange:l=>o({overlayOpacity:l/100}),min:0,max:80}),e.jsxs("p",{className:"text-xs text-slate-500 sm:col-span-2",children:["Click the heatmap image in the preview to place a pin and see styling update live. Pin preview position: ",Math.round(h.x*100),"%, ",Math.round(h.y*100),"%"]})]})}),n==="RANK_ORDER"&&"drag"in a&&e.jsxs(e.Fragment,{children:[e.jsx(s,{title:"List",children:e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsx(i,{label:"List bg",value:a.drag.itemBg,onChange:l=>o({drag:{...a.drag,itemBg:l},dropdown:{...a.dropdown,itemBg:l}})}),e.jsx(i,{label:"List hover",value:a.dropdown.itemHoverBg??a.drag.itemHoverBg??"#F3F4F6",onChange:l=>o({drag:{...a.drag,itemHoverBg:l},dropdown:{...a.dropdown,itemHoverBg:l}})})]})}),e.jsx(s,{title:v?"Drag & drop":S?"Dropdown rank":"Rank order",children:e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[v&&e.jsxs(e.Fragment,{children:[e.jsx(i,{label:"Drag handle",value:a.drag.dragHandleColor,onChange:l=>o({drag:{...a.drag,dragHandleColor:l}})}),e.jsx(i,{label:"Rank badge",value:a.drag.rankBadgeColor,onChange:l=>o({drag:{...a.drag,rankBadgeColor:l},dropdown:{...a.dropdown,rankBadgeColor:l}})})]}),S&&e.jsxs(e.Fragment,{children:[e.jsx(i,{label:"Select border",value:a.dropdown.selectBorderColor,onChange:l=>o({dropdown:{...a.dropdown,selectBorderColor:l}})}),e.jsx(i,{label:"Rank badge",value:a.drag.rankBadgeColor,onChange:l=>o({drag:{...a.drag,rankBadgeColor:l},dropdown:{...a.dropdown,rankBadgeColor:l}})})]})]})})]})]})}export{I as TypePanel};