@explorer02/cfm-survey-sdk 0.2.2 → 0.2.4
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 +140 -33
- package/dist/cli/index.mjs +140 -33
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +8 -1
- package/postinstall.js +8 -5
- package/templates/AGENT.md +62 -17
- package/templates/deploy-wizard-to-s3.sh +52 -0
- package/templates/docs/00-integration/agent-execution-flow.md +281 -0
- package/templates/docs/00-integration/agent-operating-contract.md +260 -0
- package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
- package/templates/docs/00-integration/apply-ui-config.md +365 -0
- package/templates/docs/00-integration/canonical-survey-fixtures.md +94 -0
- package/templates/docs/00-integration/client-integration-guide.md +10 -8
- package/templates/docs/00-integration/client-lib-folder.md +115 -0
- package/templates/docs/00-integration/component-checklist.md +90 -54
- package/templates/docs/00-integration/constraints.md +13 -5
- package/templates/docs/00-integration/custom-field-logic-and-navigation.md +56 -0
- package/templates/docs/00-integration/display-logic-and-navigation.md +16 -1
- package/templates/docs/00-integration/file-upload-aws.md +116 -0
- package/templates/docs/00-integration/logic-fields-catalog.md +105 -0
- package/templates/docs/00-integration/partial-save-and-recovery.md +24 -0
- package/templates/docs/00-integration/progress.md +4 -2
- package/templates/docs/00-integration/question-type-sdk-matrix.md +11 -9
- package/templates/docs/00-integration/setup.md +15 -3
- package/templates/docs/00-integration/skip-logic-and-navigation.md +1 -1
- package/templates/docs/00-integration/survey-lifecycle-analytics.md +1 -1
- package/templates/docs/00-integration/ui-customization-wizard.md +144 -0
- package/templates/docs/00-integration/useSurveySDK.md +35 -1
- package/templates/docs/00-integration/wizard-api.md +134 -0
- package/templates/docs/00-integration/wizard-config-handoff.md +246 -0
- package/templates/docs/00-integration/wizard-preview-build-guide.md +400 -0
- package/templates/docs/00-integration/wizard-question-type-styling.md +195 -0
- package/templates/docs/01-components/01-survey-page.md +18 -8
- package/templates/docs/01-components/02-question.md +2 -2
- package/templates/docs/01-components/03-rating-scale.md +12 -18
- package/templates/docs/01-components/05-csat-matrix-scale.md +18 -26
- package/templates/docs/01-components/06-likert-matrix-scale.md +19 -38
- package/templates/docs/01-components/07-slider-matrix-scale.md +48 -33
- package/templates/docs/01-components/08-file-upload-scale.md +15 -52
- package/templates/docs/01-components/09-custom-slider-track.md +15 -25
- package/templates/docs/01-components/10-header-footer.md +23 -4
- package/templates/docs/01-components/11-progress-bar.md +14 -6
- package/templates/docs/01-components/13-matrix-dropdown.md +28 -33
- package/templates/docs/01-components/17-heatmap-scale.md +4 -4
- package/templates/docs/01-components/18-rank-order-scale.md +24 -31
- package/templates/docs/01-components/19-survey-sticky-chrome.md +79 -0
- package/templates/docs/01-components/README.md +2 -2
- package/templates/docs/02-reference/question-types/10-slider-matrix.md +38 -26
- package/templates/docs/02-reference/question-types/11-file-upload.md +36 -21
- package/templates/docs/02-reference/question-types/README.md +11 -1
- package/templates/docs/02-reference/routing-table.md +1 -0
- package/templates/docs/02-reference/value-derivation.md +1 -1
- package/templates/docs/03-ui-specs/00-question-shell.md +10 -0
- package/templates/docs/03-ui-specs/01-rating.md +28 -2
- package/templates/docs/03-ui-specs/02-radio.md +94 -24
- package/templates/docs/03-ui-specs/03-text.md +12 -1
- package/templates/docs/03-ui-specs/04-csat.md +90 -26
- package/templates/docs/03-ui-specs/06-slider.md +3 -1
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +74 -35
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +50 -16
- package/templates/docs/03-ui-specs/09-slider-matrix.md +70 -26
- package/templates/docs/03-ui-specs/10-file-upload.md +52 -20
- package/templates/docs/03-ui-specs/11-text-and-media.md +4 -0
- package/templates/docs/03-ui-specs/12-survey-chrome.md +92 -5
- package/templates/docs/03-ui-specs/13-heatmap.md +21 -3
- package/templates/docs/03-ui-specs/14-rank-order.md +108 -28
- package/templates/docs/03-ui-specs/README.md +11 -7
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +1 -1
- package/templates/docs/MANIFEST.json +187 -6
- package/templates/docs/index.md +116 -116
- package/templates/docs/templates/CsatMatrixScale.tsx +637 -0
- package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
- package/templates/docs/templates/FileUploadScale.tsx +262 -0
- package/templates/docs/templates/HeatmapScale.tsx +114 -0
- package/templates/docs/templates/LikertMatrixScale.tsx +414 -0
- package/templates/docs/templates/MatrixDropdown.tsx +216 -0
- package/templates/docs/templates/Question.tsx +3 -0
- package/templates/docs/templates/RankOrderScale.tsx +353 -0
- package/templates/docs/templates/RatingScale.tsx +129 -0
- package/templates/docs/templates/SliderMatrixScale.tsx +248 -0
- package/templates/docs/templates/SurveyStickyChrome.tsx +24 -0
- package/templates/docs/templates/customFieldValues.ts +36 -0
- package/templates/docs/templates/implementation_plan.md +75 -8
- package/templates/docs/templates/labelStyles.ts +33 -0
- package/templates/docs/templates/selectionStyles.ts +100 -0
- package/templates/docs/templates/survey-inventory.schema.json +28 -23
- package/templates/docs/templates/surveyUiIcons.tsx +63 -0
- package/templates/docs/templates/surveyUiScaleUtils.ts +51 -0
- package/templates/docs/templates/verify-agent-build.sh +151 -0
- package/templates/preview-harness/preview-bridge.inline.js +201 -0
- package/templates/preview-harness/previewPages.js +108 -0
- package/templates/preview-harness/previewPages.ts +298 -0
- package/templates/preview-harness/vite-app/src/PreviewConfigContext.tsx +67 -0
- package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +122 -0
- package/templates/preview-harness/vite-app/src/SurveyPagePreview.tsx +75 -0
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +311 -0
- package/templates/preview-harness/vite-app/src/globals.css +16 -0
- package/templates/preview-harness/vite-app/src/mount.tsx +9 -0
- package/templates/preview-harness/vite-app/src/preview-live-overrides.css +101 -0
- package/templates/preview-harness/vite-app/stubs/next-dynamic.tsx +25 -0
- package/templates/preview-harness/vite-app/stubs/next-image.tsx +29 -0
- package/templates/previewBridge.ts +153 -0
- package/templates/survey-theme.css +295 -0
- package/templates/survey-ui-config.schema.json +213 -0
- package/templates/wizard-dist/assets/PreviewMock-DgHfrVeb.js +1 -0
- package/templates/wizard-dist/assets/TypePanel-CFVC3Ptn.js +1 -0
- package/templates/wizard-dist/assets/index-BhWM50Yu.css +1 -0
- package/templates/wizard-dist/assets/index-DYK3X1e5.js +34 -0
- package/templates/wizard-dist/assets/vendor-BwkXDkd3.js +17 -0
- package/templates/wizard-dist/index.html +20 -0
- package/templates/wizard.html +13 -1129
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Portable SliderMatrixScale — copy to src/components/SliderMatrixScale.tsx
|
|
5
|
+
* Requires: CustomSliderTrack.tsx, surveyUiScaleUtils.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import type { SliderMatrixQuestion, MatrixRowAnswers } from '@explorer02/cfm-survey-sdk';
|
|
10
|
+
import { tickColorFromIndex } from './surveyUiScaleUtils';
|
|
11
|
+
import { CustomSliderTrack } from './CustomSliderTrack';
|
|
12
|
+
|
|
13
|
+
type SliderMatrixScaleProps = {
|
|
14
|
+
question: SliderMatrixQuestion;
|
|
15
|
+
selectedValue?: MatrixRowAnswers;
|
|
16
|
+
onSelect: (value: MatrixRowAnswers) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function SliderMatrixScale({ question, selectedValue = {}, onSelect }: SliderMatrixScaleProps) {
|
|
20
|
+
const {
|
|
21
|
+
statementRows,
|
|
22
|
+
scaleAnchorLabels: labels,
|
|
23
|
+
ticks,
|
|
24
|
+
enableInputBox,
|
|
25
|
+
displayValues,
|
|
26
|
+
hasNotApplicableOption: enableNotApplicable,
|
|
27
|
+
sliderStyle,
|
|
28
|
+
} = question;
|
|
29
|
+
|
|
30
|
+
const tickCount = ticks.count;
|
|
31
|
+
const tickValues = ticks.marks.map((mark, index) => ({
|
|
32
|
+
value: mark.tickPosition,
|
|
33
|
+
label: mark.tickLabel,
|
|
34
|
+
color: tickColorFromIndex(index, tickCount),
|
|
35
|
+
}));
|
|
36
|
+
const sliderType = sliderStyle === 'graphics' ? 'graphics' : undefined;
|
|
37
|
+
|
|
38
|
+
const handleSliderChange = (rowId: string, val: number | 'N/A') => {
|
|
39
|
+
onSelect({ ...selectedValue, [rowId]: val });
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const baseRow = statementRows[0];
|
|
43
|
+
const min = baseRow?.min ?? 0;
|
|
44
|
+
const max = baseRow?.max ?? 10;
|
|
45
|
+
const step = baseRow?.step ?? 1;
|
|
46
|
+
|
|
47
|
+
const marks: number[] = [];
|
|
48
|
+
if (tickCount && tickCount > 1) {
|
|
49
|
+
const markStep = (max - min) / (tickCount - 1);
|
|
50
|
+
for (let i = 0; i < tickCount; i++) {
|
|
51
|
+
marks.push(Number((min + i * markStep).toFixed(2)));
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
for (let i = min; i <= max; i += step) {
|
|
55
|
+
marks.push(Number(i.toFixed(2)));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const htmlStep = tickCount && tickCount > 1 ? (max - min) / (tickCount - 1) : step;
|
|
60
|
+
|
|
61
|
+
const renderHeaderRow = () => {
|
|
62
|
+
if ((!labels || labels.length === 0) && tickValues.length === 0) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return (
|
|
66
|
+
<div style={{
|
|
67
|
+
display: 'flex', width: '100%', marginBottom: '4px', alignItems: 'flex-end',
|
|
68
|
+
padding: '0 16px', boxSizing: 'border-box',
|
|
69
|
+
}}>
|
|
70
|
+
<div style={{ flex: '0 0 25%', paddingRight: '16px' }} />
|
|
71
|
+
|
|
72
|
+
<div style={{ flex: '1', position: 'relative' }}>
|
|
73
|
+
{labels && labels.length > 0 && (
|
|
74
|
+
<div style={{
|
|
75
|
+
position: 'relative', display: 'flex', justifyContent: 'space-between',
|
|
76
|
+
margin: '0 16px', marginBottom: '16px', color: '#111827', fontSize: '14px', fontWeight: 500,
|
|
77
|
+
}}>
|
|
78
|
+
{labels.map((lbl, idx) => (
|
|
79
|
+
<div key={idx} style={{ width: 0, display: 'flex', justifyContent: 'center' }}>
|
|
80
|
+
<span style={{ whiteSpace: 'nowrap' }}>{lbl}</span>
|
|
81
|
+
</div>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
<div style={{
|
|
87
|
+
position: 'relative', margin: '0 16px', marginBottom: '8px',
|
|
88
|
+
color: '#4b5563', fontSize: '14px', fontWeight: 400, height: '28px',
|
|
89
|
+
}}>
|
|
90
|
+
{tickValues.length > 0 ? (
|
|
91
|
+
tickValues.map((tv, idx) => {
|
|
92
|
+
const percentage = max !== min ? ((tv.value - min) / (max - min)) * 100 : 0;
|
|
93
|
+
const sliceWidth = tickValues.length > 1 ? 100 / (tickValues.length - 1) : 100;
|
|
94
|
+
return (
|
|
95
|
+
<div key={idx} style={{
|
|
96
|
+
position: 'absolute', left: `${percentage}%`, transform: 'translateX(-50%)',
|
|
97
|
+
width: `${sliceWidth}%`, display: 'flex', justifyContent: 'center',
|
|
98
|
+
}}>
|
|
99
|
+
{sliderType === 'graphics' ? (
|
|
100
|
+
<div style={{ wordBreak: 'break-word', textAlign: 'center', lineHeight: '1.2', fontWeight: 500, color: '#4b5563', fontSize: '13px' }}>
|
|
101
|
+
{tv.label}
|
|
102
|
+
</div>
|
|
103
|
+
) : (
|
|
104
|
+
<div style={{
|
|
105
|
+
backgroundColor: tv.color, color: 'white', padding: '4px 8px', borderRadius: '4px',
|
|
106
|
+
fontWeight: 600, wordBreak: 'break-word', textAlign: 'center', lineHeight: '1.2',
|
|
107
|
+
boxShadow: '0 1px 2px rgba(0,0,0,0.1)',
|
|
108
|
+
}}>
|
|
109
|
+
{tv.label}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
})
|
|
115
|
+
) : (
|
|
116
|
+
marks.map((m, idx) => {
|
|
117
|
+
const percentage = max !== min ? ((m - min) / (max - min)) * 100 : 0;
|
|
118
|
+
const sliceWidth = marks.length > 1 ? 100 / (marks.length - 1) : 100;
|
|
119
|
+
return (
|
|
120
|
+
<div key={idx} style={{
|
|
121
|
+
position: 'absolute', left: `${percentage}%`, transform: 'translateX(-50%)',
|
|
122
|
+
width: `${sliceWidth}%`, display: 'flex', justifyContent: 'center',
|
|
123
|
+
}}>
|
|
124
|
+
<span style={{ wordBreak: 'break-word', textAlign: 'center', lineHeight: '1.2' }}>{m}</span>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
})
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{enableInputBox && <div style={{ width: '80px', marginLeft: '16px' }} />}
|
|
133
|
+
{enableNotApplicable && (
|
|
134
|
+
<div style={{ width: '40px', marginLeft: '16px', textAlign: 'center', fontSize: '13px', fontWeight: 600, color: '#4b5563', paddingBottom: '8px' }}>
|
|
135
|
+
N/A
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
144
|
+
{renderHeaderRow()}
|
|
145
|
+
|
|
146
|
+
{statementRows.map(row => {
|
|
147
|
+
const rowVal = selectedValue[row.id];
|
|
148
|
+
const isNa = rowVal === 'N/A';
|
|
149
|
+
const val = typeof rowVal === 'number' ? rowVal : isNa ? row.min : (row.defaultValue ?? row.min);
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div key={row.id} style={{
|
|
153
|
+
display: 'flex', width: '100%', alignItems: 'center',
|
|
154
|
+
backgroundColor: '#f3f4f6', borderRadius: '12px', padding: '16px', boxSizing: 'border-box',
|
|
155
|
+
}}>
|
|
156
|
+
<div style={{ flex: '0 0 25%', paddingRight: '16px', fontSize: '15px', color: '#111827', wordWrap: 'break-word', display: 'flex', alignItems: 'center' }}>
|
|
157
|
+
{row.statementText !== 'Question' && row.statementText !== '' ? (
|
|
158
|
+
<span dangerouslySetInnerHTML={{ __html: row.statementText }} />
|
|
159
|
+
) : null}
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div style={{ flex: '1' }}>
|
|
163
|
+
<CustomSliderTrack
|
|
164
|
+
min={row.min}
|
|
165
|
+
max={row.max}
|
|
166
|
+
htmlStep={htmlStep}
|
|
167
|
+
value={val}
|
|
168
|
+
disabled={isNa}
|
|
169
|
+
displayValues={displayValues}
|
|
170
|
+
hasSelectedValue={typeof rowVal === 'number'}
|
|
171
|
+
sliderType={sliderType}
|
|
172
|
+
ticks={tickCount}
|
|
173
|
+
onChange={v => handleSliderChange(row.id, v)}
|
|
174
|
+
/>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{enableInputBox && (
|
|
178
|
+
<div style={{
|
|
179
|
+
marginLeft: '16px', width: '80px', display: 'flex', alignItems: 'stretch',
|
|
180
|
+
border: '1px solid #d1d5db', borderRadius: '8px', overflow: 'hidden',
|
|
181
|
+
backgroundColor: isNa ? '#e5e7eb' : '#fff', boxSizing: 'border-box',
|
|
182
|
+
}}>
|
|
183
|
+
<input
|
|
184
|
+
type="text"
|
|
185
|
+
value={isNa ? '' : Math.round(Number(val))}
|
|
186
|
+
disabled={isNa}
|
|
187
|
+
onChange={e => {
|
|
188
|
+
if (e.target.value === '') return;
|
|
189
|
+
let num = Number(e.target.value);
|
|
190
|
+
if (!isNaN(num)) {
|
|
191
|
+
if (num < row.min) num = row.min;
|
|
192
|
+
if (num > row.max) num = row.max;
|
|
193
|
+
handleSliderChange(row.id, num);
|
|
194
|
+
}
|
|
195
|
+
}}
|
|
196
|
+
style={{
|
|
197
|
+
width: '50px', padding: '8px', border: 'none',
|
|
198
|
+
fontSize: '14px', outline: 'none', backgroundColor: 'transparent', textAlign: 'center',
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
<div style={{ display: 'flex', flexDirection: 'column', borderLeft: '1px solid #d1d5db', backgroundColor: '#f9fafb', width: '30px' }}>
|
|
202
|
+
<button
|
|
203
|
+
disabled={isNa}
|
|
204
|
+
onClick={() => {
|
|
205
|
+
const num = Number((Number(val) + htmlStep).toFixed(2));
|
|
206
|
+
if (num <= row.max) handleSliderChange(row.id, num);
|
|
207
|
+
}}
|
|
208
|
+
style={{ flex: 1, border: 'none', background: 'none', cursor: isNa ? 'not-allowed' : 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#4b5563' }}
|
|
209
|
+
>
|
|
210
|
+
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 5L5 1L9 5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /></svg>
|
|
211
|
+
</button>
|
|
212
|
+
<div style={{ height: '1px', backgroundColor: '#d1d5db', width: '100%' }} />
|
|
213
|
+
<button
|
|
214
|
+
disabled={isNa}
|
|
215
|
+
onClick={() => {
|
|
216
|
+
const num = Number((Number(val) - htmlStep).toFixed(2));
|
|
217
|
+
if (num >= row.min) handleSliderChange(row.id, num);
|
|
218
|
+
}}
|
|
219
|
+
style={{ flex: 1, border: 'none', background: 'none', cursor: isNa ? 'not-allowed' : 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#4b5563' }}
|
|
220
|
+
>
|
|
221
|
+
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 1L5 5L9 1" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /></svg>
|
|
222
|
+
</button>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
)}
|
|
226
|
+
|
|
227
|
+
{enableNotApplicable && (
|
|
228
|
+
<div style={{ marginLeft: '16px', width: '40px', display: 'flex', justifyContent: 'center' }}>
|
|
229
|
+
<input
|
|
230
|
+
type="checkbox"
|
|
231
|
+
checked={isNa}
|
|
232
|
+
onChange={e => {
|
|
233
|
+
if (e.target.checked) {
|
|
234
|
+
handleSliderChange(row.id, 'N/A');
|
|
235
|
+
} else {
|
|
236
|
+
handleSliderChange(row.id, row.defaultValue ?? row.min);
|
|
237
|
+
}
|
|
238
|
+
}}
|
|
239
|
+
style={{ width: '20px', height: '20px', cursor: 'pointer' }}
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
</div>
|
|
244
|
+
);
|
|
245
|
+
})}
|
|
246
|
+
</div>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Header from '@/components/Header';
|
|
2
|
+
import ProgressBar from '@/components/ProgressBar';
|
|
3
|
+
|
|
4
|
+
type SurveyStickyChromeProps = {
|
|
5
|
+
progressPercentage: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Sticky top chrome: brand header + progress bar.
|
|
10
|
+
* Keeps progress visible while scrolling long pages (e.g. last question below the fold).
|
|
11
|
+
*/
|
|
12
|
+
export default function SurveyStickyChrome({ progressPercentage }: SurveyStickyChromeProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
className="sticky top-0 z-30 shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
|
|
16
|
+
style={{ background: 'var(--cfm-header-bg, #fff)' }}
|
|
17
|
+
>
|
|
18
|
+
<Header embedded />
|
|
19
|
+
<div className="mx-auto w-full max-w-4xl px-4 pb-5 pt-4 sm:px-6 lg:px-8">
|
|
20
|
+
<ProgressBar progressPercentage={progressPercentage} />
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable custom field values template — copy to src/lib/customFieldValues.ts
|
|
3
|
+
*
|
|
4
|
+
* Populate keys from survey logic rules where conditionEntity.entityType === 'CUSTOM_FIELD'.
|
|
5
|
+
* Values come from CRM/transaction context — NOT from survey respondent UI.
|
|
6
|
+
*
|
|
7
|
+
* See: docs/00-integration/custom-field-logic-and-navigation.md
|
|
8
|
+
* docs/00-integration/client-lib-folder.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { CustomFieldValueMap } from '@explorer02/cfm-survey-sdk';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* fieldType → client value shape:
|
|
15
|
+
*
|
|
16
|
+
* | fieldType | Value shape | Example |
|
|
17
|
+
* |------------------------|--------------------------------------|---------|
|
|
18
|
+
* | TEXT / TEXT_MULTI | string | 'abc' |
|
|
19
|
+
* | TEXTAREA | string | 'long text' |
|
|
20
|
+
* | NUMBER | number or numeric string | 2 or '0' |
|
|
21
|
+
* | PICKLIST | string (option id) | 'uuid-option-id' |
|
|
22
|
+
* | PICKLIST_MULTISELECT | string[] (option ids) | ['id-a', 'id-b'] |
|
|
23
|
+
* | DATETIME | epoch ms (number or numeric string) | 1781029800000 |
|
|
24
|
+
*
|
|
25
|
+
* Missing keys are treated as empty for logic evaluation.
|
|
26
|
+
*/
|
|
27
|
+
export const CUSTOM_FIELD_VALUES: CustomFieldValueMap = {
|
|
28
|
+
// Example — skip SLIDER page when CASE_SENSITIVE_TEXT equals 'abc' (integration-test survey):
|
|
29
|
+
// '_c_67199096d294be011a2b4773': 'abc',
|
|
30
|
+
|
|
31
|
+
// '_c_6a436c1244764c9d4a6ce2fc': 'var2', // TEXT
|
|
32
|
+
// '_c_6a436c3644764c9d4a6ce3f5': 2, // NUMBER
|
|
33
|
+
// '_c_6a436c9e44764c9d4a6ce717': 'option-id-uuid', // PICKLIST
|
|
34
|
+
// '_c_6a436cbb44764c9d4a6ce7e5': ['option-id'], // PICKLIST_MULTISELECT
|
|
35
|
+
// '_c_6a4386dc44208eb8b08a9056': 1781029800000, // DATETIME epoch ms
|
|
36
|
+
};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# Survey UI Implementation Plan
|
|
2
2
|
|
|
3
3
|
> Copy this file to `./implementation_plan.md` in the project root. Fill every section before writing React code.
|
|
4
|
+
>
|
|
5
|
+
> Pipeline: [`agent-operating-contract.md`](../00-integration/agent-operating-contract.md) → [`agent-execution-flow.md`](../00-integration/agent-execution-flow.md)
|
|
4
6
|
|
|
5
7
|
## 0. Project Context
|
|
6
8
|
|
|
7
9
|
- **Framework:** [ ] Next.js App Router [ ] Vite [ ] CRA [ ] Existing app
|
|
8
10
|
- **Package import name:** (from `package.json` — `@explorer02/cfm-survey-sdk` or `@repo/sdk`)
|
|
9
11
|
- **instanceId:** (JWT — wired in SurveyPage)
|
|
10
|
-
- **Branding source:** (client prompt / mockup description
|
|
12
|
+
- **Branding source:** (client prompt / mockup description; optional wizard override via `survey-ui-config.json`)
|
|
11
13
|
|
|
12
14
|
## 1. Data Fetching & State
|
|
13
15
|
|
|
@@ -26,6 +28,7 @@
|
|
|
26
28
|
- [ ] `onAction` START / NEXT / PREVIOUS / SUBMIT wiring
|
|
27
29
|
- [ ] `onAction(SCROLL)` once on first scroll (mark as started)
|
|
28
30
|
- [ ] `onAction(RECORD_QUESTION_VIEW)` via IntersectionObserver on question wrappers
|
|
31
|
+
- [ ] `SurveyStickyChrome` (sticky Header + ProgressBar) on content phase — see `19-survey-sticky-chrome.md`
|
|
29
32
|
- [ ] `ProgressBar` bound to `state.progressPercentage` (path-scoped — see `00-integration/progress.md`)
|
|
30
33
|
- [ ] Back via `state.canGoBack`; primary label via `state.primaryButtonType`
|
|
31
34
|
- [ ] Read `00-integration/skip-logic-and-navigation.md` for skip/Back behavior
|
|
@@ -91,7 +94,34 @@ Fill from `00-integration/component-checklist.md`:
|
|
|
91
94
|
| `RankOrderScale.tsx` | `RANK_ORDER` | |
|
|
92
95
|
| `lib/heatmapCoords.ts` | `HEATMAP` | copy from `templates/heatmapCoords.ts` |
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
| `lib/surveyUiScaleUtils.ts` | NPS/matrix colors | copy from `templates/surveyUiScaleUtils.ts` |
|
|
98
|
+
| `lib/customFieldValues.ts` | CRM logic input | copy from `templates/customFieldValues.ts` when survey has `_c_…` ids |
|
|
99
|
+
| `lib/surveyUiScaleUtils.ts` | NPS/tick/matrix helpers | copy from `templates/surveyUiScaleUtils.ts` |
|
|
100
|
+
| `lib/surveyUiIcons.tsx` | CSAT emoji/star | copy from `templates/surveyUiIcons.tsx` |
|
|
101
|
+
|
|
102
|
+
## 3c. Matrix variant inventory (from fetched survey)
|
|
103
|
+
|
|
104
|
+
Read `00-integration/canonical-survey-fixtures.md`. For each matrix question note:
|
|
105
|
+
|
|
106
|
+
| questionId | type | displayStyle | statementLayout | transposeTable | selectionMode | Labels visible? |
|
|
107
|
+
|------------|------|--------------|-----------------|----------------|---------------|---------------|
|
|
108
|
+
| | | | | | | row + column/anchor |
|
|
109
|
+
|
|
110
|
+
## 3d. Custom field inventory (mandatory when `_c_…` ids found)
|
|
111
|
+
|
|
112
|
+
After fetch, scan `skipLogics`, `displayLogic`, `answerLogic`, and `endPages` for `entityType: 'CUSTOM_FIELD'`.
|
|
113
|
+
|
|
114
|
+
| `_c_` id | fieldType | used in (skip/display/answer/end) | logic field (e.g. CASE_SENSITIVE_TEXT) | CRM value | populated in `lib/customFieldValues.ts`? |
|
|
115
|
+
|----------|-----------|-----------------------------------|----------------------------------------|-----------|---------------------------------------------|
|
|
116
|
+
| | | | | | |
|
|
117
|
+
|
|
118
|
+
**Blocking gate:** if any row above is filled → copy `templates/customFieldValues.ts` → `src/lib/customFieldValues.ts` before Phase 5 BUILD.
|
|
119
|
+
|
|
120
|
+
Wire map in **both** places: `useSurveySDK({ options: { customFieldValues } })` and `<Question customFieldValues={...} />`.
|
|
121
|
+
|
|
122
|
+
See `00-integration/custom-field-logic-and-navigation.md` and `00-integration/client-lib-folder.md`.
|
|
123
|
+
|
|
124
|
+
## 3e. Logic inventory (from fetched survey)
|
|
95
125
|
|
|
96
126
|
Scan `survey.pages` for logic flags. Fill after fetch:
|
|
97
127
|
|
|
@@ -99,9 +129,7 @@ Scan `survey.pages` for logic flags. Fill after fetch:
|
|
|
99
129
|
|------------|------------|--------------|----------------------------------|---------------------------|
|
|
100
130
|
| | | | | |
|
|
101
131
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
## 3d. Per-Type SDK Integration (from fetched survey)
|
|
132
|
+
## 3f. Per-Type SDK Integration (from fetched survey)
|
|
105
133
|
|
|
106
134
|
Fill from `00-integration/question-type-sdk-matrix.md`:
|
|
107
135
|
|
|
@@ -119,18 +147,57 @@ Fill from `00-integration/question-type-sdk-matrix.md`:
|
|
|
119
147
|
| LikertMatrixScale | | `06-likert-matrix-scale.md` |
|
|
120
148
|
| SliderMatrixScale | | `07-slider-matrix-scale.md` |
|
|
121
149
|
| FileUploadScale | | `08-file-upload-scale.md` |
|
|
122
|
-
| Header / Footer /
|
|
150
|
+
| Header / Footer / SurveyStickyChrome / LanguageSelector | | `10`–`12`, `19` |
|
|
151
|
+
|
|
152
|
+
## 3e. Interaction dependencies (from survey inventory)
|
|
153
|
+
|
|
154
|
+
| Package | Install when |
|
|
155
|
+
|---------|--------------|
|
|
156
|
+
| `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities` | Survey contains `RANK_ORDER` |
|
|
157
|
+
| `react-icons` | Always (shared setup) + CSAT emoji/star matrix |
|
|
158
|
+
|
|
159
|
+
Copy portable templates from `docs/templates/` — see `component-checklist.md`.
|
|
123
160
|
|
|
124
161
|
## 4b. Per-Question Config & UI Coverage
|
|
125
162
|
|
|
163
|
+
Inventory required config fields per type:
|
|
164
|
+
|
|
165
|
+
| type | must inventory |
|
|
166
|
+
|------|----------------|
|
|
167
|
+
| MCQ | `selectionMode`, `minSelections`, `maxSelections`, `defaultOptionIds`, answer-logic on options |
|
|
168
|
+
| RANK_ORDER | `interactionMode`, `optionDisplay`, `requireRankAll`, `shuffleOptions` |
|
|
169
|
+
| CFM/CSAT/RATING | `gridLayout`, `displayStyle`, `selectionMode`, `hasNotApplicableOption`, `statementLayout` |
|
|
170
|
+
| SLIDER_MATRIX | `enableInputBox`, `hasNotApplicableOption`, `sliderStyle`, `statementRows`, `ticks` |
|
|
171
|
+
| FILE_UPLOAD | `maxFileCount`, `fileSizeLimit`, `fileSizeLimitType`, upload API configured? |
|
|
172
|
+
| HEATMAP | `maxClicksAllowed`, image URL |
|
|
173
|
+
|
|
126
174
|
| questionId | type | configs detected | uiSpec read | component files | branches implemented |
|
|
127
175
|
|------------|------|------------------|-------------|-----------------|---------------------|
|
|
128
176
|
| | | | | | |
|
|
129
177
|
|
|
130
178
|
## 5. Theme, Brand & Logo
|
|
131
179
|
|
|
132
|
-
- [ ] CSS-
|
|
180
|
+
- [ ] Logo: text/CSS (`data-cfm-logo-text`) **or** local `public/` image (`data-cfm-logo`) — see `wizard-preview-build-guide.md`; no external URLs
|
|
133
181
|
- [ ] Primary / hover colors from client prompt
|
|
182
|
+
- [ ] `src/styles/survey-theme.css` with `--cfm-*` CSS variables (full template set)
|
|
183
|
+
- [ ] **Wizard preview contract:** `var(--cfm-*)` in chrome/scales + `data-cfm-*` on editable text/logo — see `00-integration/wizard-preview-build-guide.md`
|
|
184
|
+
- [ ] **Wizard per-type styling:** `selectionStyles.ts`, `labelStyles.ts`, per-type tokens — see `00-integration/wizard-question-type-styling.md`
|
|
185
|
+
- [ ] **Draft** `./survey-ui-config.json` in sync with theme tokens (seeds optional wizard in Phase 6b)
|
|
186
|
+
- [ ] `previewBridge.ts` wired for wizard live preview (see `ui-customization-wizard.md`)
|
|
187
|
+
- [ ] If `survey-ui-config.json` exists: apply per `apply-ui-config.md` (Phase 5 draft or Phase 6c final)
|
|
188
|
+
|
|
189
|
+
## 5b. Wizard handoff (Phase 6b–6c — when client uses wizard)
|
|
190
|
+
|
|
191
|
+
See `wizard-config-handoff.md`.
|
|
192
|
+
|
|
193
|
+
- [ ] `customize-ui` uploaded component previews to S3
|
|
194
|
+
- [ ] Deployed wizard link sent to client: `http://43.204.26.213:3000/wizard-app/index.html?session=…&secret=…` (from CLI output)
|
|
195
|
+
- [ ] CLI `customize-ui` exited 0 — waited for client submit
|
|
196
|
+
- [ ] Read `survey-ui-config.diff.md` — paths changed: ___
|
|
197
|
+
- [ ] Applied from `survey-ui-config.final.json` (AWS) or `survey-ui-config.json` (local)
|
|
198
|
+
- [ ] Every diff path applied (count matches)
|
|
199
|
+
- [ ] `confirm-ui-config` run (AWS)
|
|
200
|
+
- [ ] Verify + build re-run after apply
|
|
134
201
|
|
|
135
202
|
## 6. Submit & Thank You
|
|
136
203
|
|
|
@@ -145,7 +212,7 @@ Fill from `00-integration/question-type-sdk-matrix.md`:
|
|
|
145
212
|
- [ ] `bash templates/verify-agent-build.sh` passes
|
|
146
213
|
- [ ] `grep -r "not yet implemented"` → zero matches
|
|
147
214
|
|
|
148
|
-
## 8. AWS Deploy Readiness (fill if client prompt mentions AWS / CloudFront / S3 / EC2 / Sprinklr)
|
|
215
|
+
## 8. AWS Deploy Readiness (fill if client prompt mentions AWS / CloudFront / S3 / EC2 / Sprinklr, or wizard deploy.target is aws)
|
|
149
216
|
|
|
150
217
|
- [ ] Next.js `output: "export"` + `assetPrefix: "./"` configured (see `setup.md#nextjs-aws-static-export`)
|
|
151
218
|
- [ ] `images.unoptimized: true` in `next.config.js`
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Label item styling for matrix / slider tick labels (wizard Question Types step).
|
|
3
|
+
* Copy to `src/lib/surveyUi/labelStyles.ts`.
|
|
4
|
+
*/
|
|
5
|
+
import type { CSSProperties } from 'react';
|
|
6
|
+
|
|
7
|
+
export function hintLabelStyle(): CSSProperties {
|
|
8
|
+
return {
|
|
9
|
+
color: 'var(--cfm-hint-label-color, #6b7280)',
|
|
10
|
+
backgroundColor: 'var(--cfm-hint-label-bg, transparent)',
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function matrixColumnLabelStyle(): CSSProperties {
|
|
15
|
+
return {
|
|
16
|
+
color: 'var(--cfm-matrix-column-label-color, #4b5563)',
|
|
17
|
+
backgroundColor: 'var(--cfm-matrix-column-label-bg, transparent)',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function csatColumnLabelStyle(): CSSProperties {
|
|
22
|
+
return {
|
|
23
|
+
color: 'var(--cfm-csat-column-label-color, #4b5563)',
|
|
24
|
+
backgroundColor: 'var(--cfm-csat-column-label-bg, transparent)',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function sliderTickLabelStyle(): CSSProperties {
|
|
29
|
+
return {
|
|
30
|
+
color: 'var(--cfm-slider-tick-label-color, #4b5563)',
|
|
31
|
+
backgroundColor: 'var(--cfm-slider-tick-label-bg, transparent)',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable selection styling helpers for wizard live preview + apply-ui-config.
|
|
3
|
+
* Copy to `src/lib/surveyUi/selectionStyles.ts` when building agent projects.
|
|
4
|
+
*
|
|
5
|
+
* Question card theme tokens (wizard → Theme & Colors → Question card):
|
|
6
|
+
* --cfm-matrix-selected = cell selected color (annulus fill between ring and dot)
|
|
7
|
+
* --cfm-input-focus-ring = focus ring (outer border + inner dot)
|
|
8
|
+
*/
|
|
9
|
+
import type { CSSProperties } from 'react';
|
|
10
|
+
|
|
11
|
+
export const cellSelectedVar = 'var(--cfm-matrix-selected, var(--cfm-primary))';
|
|
12
|
+
export const focusRingVar = 'var(--cfm-input-focus-ring, var(--cfm-primary))';
|
|
13
|
+
|
|
14
|
+
const unselectedRing = '#d1d5db';
|
|
15
|
+
const unselectedDot = '#d1d5db';
|
|
16
|
+
|
|
17
|
+
/** NPS / large scale radio — outer ring border + annulus fill. */
|
|
18
|
+
export function scaleRadioRingStyle(
|
|
19
|
+
isSelected: boolean,
|
|
20
|
+
opts: { pill?: boolean; size?: string } = {},
|
|
21
|
+
): CSSProperties {
|
|
22
|
+
const { pill = false, size = 'var(--cfm-nps-cell-size, 32px)' } = opts;
|
|
23
|
+
return {
|
|
24
|
+
display: 'flex',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
justifyContent: 'center',
|
|
27
|
+
width: size,
|
|
28
|
+
height: size,
|
|
29
|
+
borderRadius: pill ? '9999px' : '50%',
|
|
30
|
+
border: `2px solid ${isSelected ? focusRingVar : unselectedRing}`,
|
|
31
|
+
backgroundColor: isSelected ? cellSelectedVar : '#fff',
|
|
32
|
+
transition: 'all 0.15s',
|
|
33
|
+
boxSizing: 'border-box',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Inner dot — focus ring color when selected. */
|
|
38
|
+
export function scaleRadioDotStyle(
|
|
39
|
+
isSelected: boolean,
|
|
40
|
+
opts: { pill?: boolean } = {},
|
|
41
|
+
): CSSProperties {
|
|
42
|
+
const { pill = false } = opts;
|
|
43
|
+
return {
|
|
44
|
+
borderRadius: '50%',
|
|
45
|
+
width: pill ? '40%' : '33%',
|
|
46
|
+
height: pill ? '40%' : '33%',
|
|
47
|
+
backgroundColor: isSelected ? focusRingVar : unselectedDot,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Compact matrix radio (CSAT / Likert / CFM grid). */
|
|
52
|
+
export function matrixRadioRingStyle(isSelected: boolean, size = '18px'): CSSProperties {
|
|
53
|
+
return {
|
|
54
|
+
display: 'flex',
|
|
55
|
+
width: size,
|
|
56
|
+
height: size,
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
justifyContent: 'center',
|
|
59
|
+
borderRadius: '50%',
|
|
60
|
+
margin: '0 auto',
|
|
61
|
+
border: `2px solid ${isSelected ? focusRingVar : unselectedRing}`,
|
|
62
|
+
backgroundColor: isSelected ? cellSelectedVar : '#fff',
|
|
63
|
+
transition: 'all 0.15s',
|
|
64
|
+
boxSizing: 'border-box',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function matrixRadioDotStyle(isSelected: boolean, size = '8px'): CSSProperties {
|
|
69
|
+
return {
|
|
70
|
+
width: size,
|
|
71
|
+
height: size,
|
|
72
|
+
borderRadius: '50%',
|
|
73
|
+
backgroundColor: isSelected ? focusRingVar : 'transparent',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Numbered / solid scale cell when selected. */
|
|
78
|
+
export function scaleCellSelectedStyle(isSelected: boolean): CSSProperties {
|
|
79
|
+
return isSelected
|
|
80
|
+
? {
|
|
81
|
+
border: `2px solid ${focusRingVar}`,
|
|
82
|
+
backgroundColor: cellSelectedVar,
|
|
83
|
+
color: '#fff',
|
|
84
|
+
}
|
|
85
|
+
: {};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function unselectedOpacityStyle(): CSSProperties {
|
|
89
|
+
return { opacity: 'var(--cfm-csat-unselected-opacity, 0.5)' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function emojiSizeStyle(): CSSProperties {
|
|
93
|
+
return {
|
|
94
|
+
fontSize: 'var(--cfm-csat-emoji-size, 28px)',
|
|
95
|
+
lineHeight: 1,
|
|
96
|
+
display: 'inline-flex',
|
|
97
|
+
alignItems: 'center',
|
|
98
|
+
justifyContent: 'center',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -7,22 +7,27 @@
|
|
|
7
7
|
"required": ["questions", "uniqueTypes"],
|
|
8
8
|
"properties": {
|
|
9
9
|
"instanceId": { "type": "string", "description": "JWT used to fetch survey" },
|
|
10
|
+
"interactionDependencies": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": { "type": "string" },
|
|
13
|
+
"description": "npm packages e.g. @dnd-kit/core, react-icons"
|
|
14
|
+
},
|
|
10
15
|
"uniqueTypes": {
|
|
11
16
|
"type": "array",
|
|
12
17
|
"items": {
|
|
13
18
|
"type": "string",
|
|
14
19
|
"enum": [
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
20
|
+
"MCQ",
|
|
21
|
+
"TEXTFIELD",
|
|
22
|
+
"NPS_SCALE",
|
|
23
|
+
"CFM_MATRIX",
|
|
24
|
+
"CSAT_MATRIX",
|
|
25
|
+
"RATING_MATRIX",
|
|
26
|
+
"SLIDER_MATRIX",
|
|
27
|
+
"FILE_UPLOAD",
|
|
28
|
+
"TEXT_AND_MEDIA",
|
|
29
|
+
"HEATMAP",
|
|
30
|
+
"RANK_ORDER"
|
|
26
31
|
]
|
|
27
32
|
},
|
|
28
33
|
"uniqueItems": true
|
|
@@ -37,23 +42,23 @@
|
|
|
37
42
|
"type": {
|
|
38
43
|
"type": "string",
|
|
39
44
|
"enum": [
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
45
|
+
"MCQ",
|
|
46
|
+
"TEXTFIELD",
|
|
47
|
+
"NPS_SCALE",
|
|
48
|
+
"CFM_MATRIX",
|
|
49
|
+
"CSAT_MATRIX",
|
|
50
|
+
"RATING_MATRIX",
|
|
51
|
+
"SLIDER_MATRIX",
|
|
52
|
+
"FILE_UPLOAD",
|
|
53
|
+
"TEXT_AND_MEDIA",
|
|
54
|
+
"HEATMAP",
|
|
55
|
+
"RANK_ORDER"
|
|
51
56
|
]
|
|
52
57
|
},
|
|
53
58
|
"configFlags": {
|
|
54
59
|
"type": "array",
|
|
55
60
|
"items": { "type": "string" },
|
|
56
|
-
"description": "
|
|
61
|
+
"description": "e.g. interactionMode:dragAndDrop, gridLayout:carousel, selectionMode:multiple, defaultOptionIds"
|
|
57
62
|
},
|
|
58
63
|
"uiSpecRead": { "type": "string" },
|
|
59
64
|
"componentFiles": {
|