@product7/feedback-sdk 1.5.2 → 1.5.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/feedback-sdk.js +644 -33
- package/dist/feedback-sdk.js.map +1 -1
- package/dist/feedback-sdk.min.js +1 -1
- package/dist/feedback-sdk.min.js.map +1 -1
- package/package.json +1 -1
- package/src/core/FeedbackSDK.js +139 -18
- package/src/styles/survey.js +61 -1
- package/src/widgets/SurveyWidget.js +444 -14
- package/types/index.d.ts +38 -0
|
@@ -13,6 +13,7 @@ export class SurveyWidget extends BaseWidget {
|
|
|
13
13
|
lowLabel: options.lowLabel || null,
|
|
14
14
|
highLabel: options.highLabel || null,
|
|
15
15
|
customQuestions: options.customQuestions || [],
|
|
16
|
+
pages: Array.isArray(options.pages) ? options.pages : [],
|
|
16
17
|
respondentId: options.respondentId || null,
|
|
17
18
|
email: options.email || null,
|
|
18
19
|
onSubmit: options.onSubmit || null,
|
|
@@ -23,6 +24,8 @@ export class SurveyWidget extends BaseWidget {
|
|
|
23
24
|
score: null,
|
|
24
25
|
feedback: '',
|
|
25
26
|
customAnswers: {},
|
|
27
|
+
pageAnswers: {},
|
|
28
|
+
currentPageIndex: 0,
|
|
26
29
|
isVisible: false,
|
|
27
30
|
};
|
|
28
31
|
}
|
|
@@ -53,9 +56,19 @@ export class SurveyWidget extends BaseWidget {
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
_renderSurvey() {
|
|
56
|
-
this._closeSurvey();
|
|
59
|
+
this._closeSurvey(false);
|
|
57
60
|
|
|
58
61
|
const config = this._getSurveyConfig();
|
|
62
|
+
const isMultiPage = this._isMultiPageSurvey();
|
|
63
|
+
const isLastPage = this._isLastPage();
|
|
64
|
+
const pageProgress = isMultiPage
|
|
65
|
+
? `Page ${this.surveyState.currentPageIndex + 1} of ${this.surveyOptions.pages.length}`
|
|
66
|
+
: '';
|
|
67
|
+
const submitLabel = isMultiPage && !isLastPage ? 'Next' : 'Submit';
|
|
68
|
+
const backButton =
|
|
69
|
+
isMultiPage && this.surveyState.currentPageIndex > 0
|
|
70
|
+
? '<button class="feedback-survey-back">Back</button>'
|
|
71
|
+
: '';
|
|
59
72
|
|
|
60
73
|
if (this.surveyOptions.position === 'center') {
|
|
61
74
|
this.backdropElement = document.createElement('div');
|
|
@@ -73,11 +86,15 @@ export class SurveyWidget extends BaseWidget {
|
|
|
73
86
|
<button class="feedback-survey-close">×</button>
|
|
74
87
|
<h3 class="feedback-survey-title">${config.title}</h3>
|
|
75
88
|
<p class="feedback-survey-description">${config.description}</p>
|
|
89
|
+
${isMultiPage ? `<div class="feedback-survey-progress">${pageProgress}</div>` : ''}
|
|
76
90
|
<div class="feedback-survey-content">${config.html}</div>
|
|
77
91
|
<div class="feedback-survey-feedback">
|
|
78
|
-
<textarea class="feedback-survey-textarea" placeholder="Any additional feedback? (optional)"
|
|
92
|
+
<textarea class="feedback-survey-textarea" placeholder="Any additional feedback? (optional)">${this.surveyState.feedback || ''}</textarea>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="feedback-survey-actions">
|
|
95
|
+
${backButton}
|
|
96
|
+
<button class="feedback-survey-submit">${submitLabel}</button>
|
|
79
97
|
</div>
|
|
80
|
-
<button class="feedback-survey-submit">Submit</button>
|
|
81
98
|
`;
|
|
82
99
|
|
|
83
100
|
document.body.appendChild(this.surveyElement);
|
|
@@ -93,6 +110,10 @@ export class SurveyWidget extends BaseWidget {
|
|
|
93
110
|
}
|
|
94
111
|
|
|
95
112
|
_getSurveyConfig() {
|
|
113
|
+
if (this._isMultiPageSurvey()) {
|
|
114
|
+
return this._getCurrentPageConfig();
|
|
115
|
+
}
|
|
116
|
+
|
|
96
117
|
const configs = {
|
|
97
118
|
nps: {
|
|
98
119
|
title:
|
|
@@ -166,6 +187,166 @@ export class SurveyWidget extends BaseWidget {
|
|
|
166
187
|
return configs[this.surveyOptions.surveyType] || configs.nps;
|
|
167
188
|
}
|
|
168
189
|
|
|
190
|
+
_isMultiPageSurvey() {
|
|
191
|
+
return (
|
|
192
|
+
Array.isArray(this.surveyOptions.pages) &&
|
|
193
|
+
this.surveyOptions.pages.length > 0
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_getCurrentPage() {
|
|
198
|
+
if (!this._isMultiPageSurvey()) return null;
|
|
199
|
+
return this.surveyOptions.pages[this.surveyState.currentPageIndex] || null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
_isLastPage() {
|
|
203
|
+
if (!this._isMultiPageSurvey()) return true;
|
|
204
|
+
return this.surveyState.currentPageIndex >= this.surveyOptions.pages.length - 1;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_getCurrentPageConfig() {
|
|
208
|
+
const page = this._getCurrentPage();
|
|
209
|
+
if (!page) {
|
|
210
|
+
return this._getFallbackSurveyConfig();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
title: page.title || this.surveyOptions.title || 'Quick Feedback',
|
|
215
|
+
description:
|
|
216
|
+
page.description ||
|
|
217
|
+
this.surveyOptions.description ||
|
|
218
|
+
'Help us improve by answering this question.',
|
|
219
|
+
html: this._renderSurveyPage(page),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
_getFallbackSurveyConfig() {
|
|
224
|
+
return {
|
|
225
|
+
title: this.surveyOptions.title || 'Quick Feedback',
|
|
226
|
+
description:
|
|
227
|
+
this.surveyOptions.description ||
|
|
228
|
+
'Help us improve by answering a few questions.',
|
|
229
|
+
html: this._renderCustomQuestions(),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_renderSurveyPage(page) {
|
|
234
|
+
switch (page.type) {
|
|
235
|
+
case 'rating':
|
|
236
|
+
return this._renderRatingPage(page);
|
|
237
|
+
case 'multiple_choice':
|
|
238
|
+
return this._renderMultipleChoicePage(page);
|
|
239
|
+
case 'text':
|
|
240
|
+
return this._renderTextPage(page);
|
|
241
|
+
default:
|
|
242
|
+
return this._renderTextPage(page);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
_renderRatingPage(page) {
|
|
247
|
+
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
248
|
+
const config = page.ratingConfig || page.rating_config || {};
|
|
249
|
+
const scale = Number(config.scale) || 5;
|
|
250
|
+
const ratingType = config.survey_type || this.surveyOptions.surveyType || 'csat';
|
|
251
|
+
const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
|
|
252
|
+
const currentRating = pageAnswer.rating;
|
|
253
|
+
|
|
254
|
+
const labels = `
|
|
255
|
+
<div class="feedback-survey-labels">
|
|
256
|
+
<span>${config.low_label || this.surveyOptions.lowLabel || ''}</span>
|
|
257
|
+
<span>${config.high_label || this.surveyOptions.highLabel || ''}</span>
|
|
258
|
+
</div>
|
|
259
|
+
`;
|
|
260
|
+
|
|
261
|
+
if (scale === 11 || ratingType === 'nps') {
|
|
262
|
+
return `
|
|
263
|
+
<div class="feedback-survey-nps" data-page-id="${pageId}">
|
|
264
|
+
${[...Array(11).keys()]
|
|
265
|
+
.map((n) => {
|
|
266
|
+
const selected = currentRating === n ? ' selected' : '';
|
|
267
|
+
return `<button class="feedback-survey-page-rating-btn feedback-survey-nps-btn${selected}" data-page-id="${pageId}" data-score="${n}">${n}</button>`;
|
|
268
|
+
})
|
|
269
|
+
.join('')}
|
|
270
|
+
</div>
|
|
271
|
+
${labels}
|
|
272
|
+
`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (ratingType === 'emoji' && scale === 5) {
|
|
276
|
+
const emojis = [
|
|
277
|
+
'\uD83D\uDE1E',
|
|
278
|
+
'\uD83D\uDE15',
|
|
279
|
+
'\uD83D\uDE10',
|
|
280
|
+
'\uD83D\uDE42',
|
|
281
|
+
'\uD83D\uDE04',
|
|
282
|
+
];
|
|
283
|
+
return `
|
|
284
|
+
<div class="feedback-survey-csat" data-page-id="${pageId}">
|
|
285
|
+
${emojis
|
|
286
|
+
.map((emoji, i) => {
|
|
287
|
+
const score = i + 1;
|
|
288
|
+
const selected = currentRating === score ? ' selected' : '';
|
|
289
|
+
return `<button class="feedback-survey-page-rating-btn feedback-survey-csat-btn${selected}" data-page-id="${pageId}" data-score="${score}">${emoji}</button>`;
|
|
290
|
+
})
|
|
291
|
+
.join('')}
|
|
292
|
+
</div>
|
|
293
|
+
${labels}
|
|
294
|
+
`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return `
|
|
298
|
+
<div class="feedback-survey-ces" data-page-id="${pageId}">
|
|
299
|
+
${[...Array(scale).keys()]
|
|
300
|
+
.map((i) => {
|
|
301
|
+
const score = i + 1;
|
|
302
|
+
const selected = currentRating === score ? ' selected' : '';
|
|
303
|
+
return `<button class="feedback-survey-page-rating-btn feedback-survey-ces-btn${selected}" data-page-id="${pageId}" data-score="${score}">${score}</button>`;
|
|
304
|
+
})
|
|
305
|
+
.join('')}
|
|
306
|
+
</div>
|
|
307
|
+
${labels}
|
|
308
|
+
`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
_renderMultipleChoicePage(page) {
|
|
312
|
+
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
313
|
+
const config = page.multipleChoiceConfig || page.multiple_choice_config || {};
|
|
314
|
+
const options = Array.isArray(config.options) ? config.options : [];
|
|
315
|
+
const allowMultiple = config.allow_multiple === true || config.multiple === true;
|
|
316
|
+
const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
|
|
317
|
+
const selectedValues = Array.isArray(pageAnswer.values)
|
|
318
|
+
? pageAnswer.values
|
|
319
|
+
: pageAnswer.value
|
|
320
|
+
? [pageAnswer.value]
|
|
321
|
+
: [];
|
|
322
|
+
|
|
323
|
+
return `
|
|
324
|
+
<div class="feedback-survey-multiple-choice" data-page-id="${pageId}" data-multiple="${allowMultiple}">
|
|
325
|
+
${options
|
|
326
|
+
.map((option, index) => {
|
|
327
|
+
const value =
|
|
328
|
+
option.value || option.id || option.key || `option_${index}`;
|
|
329
|
+
const label = option.label || option.text || String(value);
|
|
330
|
+
const selected = selectedValues.includes(value) ? ' selected' : '';
|
|
331
|
+
return `<button class="feedback-survey-page-choice-btn${selected}" data-page-id="${pageId}" data-value="${value}">${label}</button>`;
|
|
332
|
+
})
|
|
333
|
+
.join('')}
|
|
334
|
+
</div>
|
|
335
|
+
`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
_renderTextPage(page) {
|
|
339
|
+
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
340
|
+
const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
|
|
341
|
+
const value = pageAnswer.text || '';
|
|
342
|
+
|
|
343
|
+
return `
|
|
344
|
+
<div class="feedback-survey-text-page" data-page-id="${pageId}">
|
|
345
|
+
<textarea class="feedback-survey-page-textarea" data-page-id="${pageId}" placeholder="Type your answer...">${value}</textarea>
|
|
346
|
+
</div>
|
|
347
|
+
`;
|
|
348
|
+
}
|
|
349
|
+
|
|
169
350
|
_renderCustomQuestions() {
|
|
170
351
|
if (
|
|
171
352
|
!this.surveyOptions.customQuestions ||
|
|
@@ -238,6 +419,11 @@ export class SurveyWidget extends BaseWidget {
|
|
|
238
419
|
);
|
|
239
420
|
submitBtn.addEventListener('click', () => this._handleSubmit());
|
|
240
421
|
|
|
422
|
+
const backBtn = this.surveyElement.querySelector('.feedback-survey-back');
|
|
423
|
+
if (backBtn) {
|
|
424
|
+
backBtn.addEventListener('click', () => this._handleBack());
|
|
425
|
+
}
|
|
426
|
+
|
|
241
427
|
const textarea = this.surveyElement.querySelector(
|
|
242
428
|
'.feedback-survey-textarea'
|
|
243
429
|
);
|
|
@@ -256,6 +442,11 @@ export class SurveyWidget extends BaseWidget {
|
|
|
256
442
|
}
|
|
257
443
|
|
|
258
444
|
_attachTypeSpecificEvents() {
|
|
445
|
+
if (this._isMultiPageSurvey()) {
|
|
446
|
+
this._attachCurrentPageEvents();
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
259
450
|
const type = this.surveyOptions.surveyType;
|
|
260
451
|
|
|
261
452
|
if (type === 'nps') {
|
|
@@ -317,6 +508,87 @@ export class SurveyWidget extends BaseWidget {
|
|
|
317
508
|
}
|
|
318
509
|
}
|
|
319
510
|
|
|
511
|
+
_attachCurrentPageEvents() {
|
|
512
|
+
const page = this._getCurrentPage();
|
|
513
|
+
if (!page || !this.surveyElement) return;
|
|
514
|
+
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
515
|
+
|
|
516
|
+
if (page.type === 'rating') {
|
|
517
|
+
this.surveyElement
|
|
518
|
+
.querySelectorAll('.feedback-survey-page-rating-btn')
|
|
519
|
+
.forEach((btn) => {
|
|
520
|
+
btn.addEventListener('click', () => {
|
|
521
|
+
const score = parseInt(btn.dataset.score);
|
|
522
|
+
if (Number.isNaN(score)) return;
|
|
523
|
+
this._setPageAnswer(pageId, { rating: score });
|
|
524
|
+
|
|
525
|
+
this.surveyElement
|
|
526
|
+
.querySelectorAll('.feedback-survey-page-rating-btn')
|
|
527
|
+
.forEach((item) => item.classList.remove('selected'));
|
|
528
|
+
btn.classList.add('selected');
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (page.type === 'multiple_choice') {
|
|
534
|
+
const container = this.surveyElement.querySelector(
|
|
535
|
+
'.feedback-survey-multiple-choice'
|
|
536
|
+
);
|
|
537
|
+
const allowMultiple = container
|
|
538
|
+
? container.dataset.multiple === 'true'
|
|
539
|
+
: false;
|
|
540
|
+
|
|
541
|
+
this.surveyElement
|
|
542
|
+
.querySelectorAll('.feedback-survey-page-choice-btn')
|
|
543
|
+
.forEach((btn) => {
|
|
544
|
+
btn.addEventListener('click', () => {
|
|
545
|
+
const value = btn.dataset.value;
|
|
546
|
+
if (!value) return;
|
|
547
|
+
|
|
548
|
+
const existing = this.surveyState.pageAnswers[pageId] || {};
|
|
549
|
+
const existingValues = Array.isArray(existing.values)
|
|
550
|
+
? existing.values
|
|
551
|
+
: existing.value
|
|
552
|
+
? [existing.value]
|
|
553
|
+
: [];
|
|
554
|
+
|
|
555
|
+
let nextValues = [];
|
|
556
|
+
if (allowMultiple) {
|
|
557
|
+
const hasValue = existingValues.includes(value);
|
|
558
|
+
nextValues = hasValue
|
|
559
|
+
? existingValues.filter((v) => v !== value)
|
|
560
|
+
: [...existingValues, value];
|
|
561
|
+
} else {
|
|
562
|
+
nextValues = [value];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
this._setPageAnswer(pageId, {
|
|
566
|
+
value: nextValues[0] || null,
|
|
567
|
+
values: nextValues,
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
if (!allowMultiple) {
|
|
571
|
+
this.surveyElement
|
|
572
|
+
.querySelectorAll('.feedback-survey-page-choice-btn')
|
|
573
|
+
.forEach((item) => item.classList.remove('selected'));
|
|
574
|
+
}
|
|
575
|
+
btn.classList.toggle('selected', nextValues.includes(value));
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (page.type === 'text') {
|
|
581
|
+
const textarea = this.surveyElement.querySelector(
|
|
582
|
+
'.feedback-survey-page-textarea'
|
|
583
|
+
);
|
|
584
|
+
if (textarea) {
|
|
585
|
+
textarea.addEventListener('input', (e) => {
|
|
586
|
+
this._setPageAnswer(pageId, { text: e.target.value });
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
320
592
|
_selectNPS(score) {
|
|
321
593
|
this.surveyState.score = score;
|
|
322
594
|
this.surveyElement
|
|
@@ -375,7 +647,21 @@ export class SurveyWidget extends BaseWidget {
|
|
|
375
647
|
async _handleSubmit() {
|
|
376
648
|
const type = this.surveyOptions.surveyType;
|
|
377
649
|
|
|
650
|
+
if (this._isMultiPageSurvey()) {
|
|
651
|
+
if (!this._validateCurrentPage()) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const nextPageIndex = this._getNextPageIndex();
|
|
656
|
+
if (nextPageIndex !== -1 && nextPageIndex !== this.surveyState.currentPageIndex) {
|
|
657
|
+
this.surveyState.currentPageIndex = nextPageIndex;
|
|
658
|
+
this._renderSurvey();
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
378
663
|
if (
|
|
664
|
+
!this._isMultiPageSurvey() &&
|
|
379
665
|
(type === 'nps' || type === 'csat' || type === 'ces') &&
|
|
380
666
|
this.surveyState.score === null
|
|
381
667
|
) {
|
|
@@ -384,20 +670,28 @@ export class SurveyWidget extends BaseWidget {
|
|
|
384
670
|
}
|
|
385
671
|
|
|
386
672
|
const respondent = this._getRespondentContext();
|
|
673
|
+
const normalizedPageAnswers = this._normalizePageAnswersForSubmit();
|
|
674
|
+
const mergedAnswers = {
|
|
675
|
+
...this.surveyState.customAnswers,
|
|
676
|
+
...(Object.keys(normalizedPageAnswers).length > 0 && {
|
|
677
|
+
page_answers: normalizedPageAnswers,
|
|
678
|
+
}),
|
|
679
|
+
};
|
|
387
680
|
|
|
388
681
|
const responseData = {
|
|
389
|
-
rating: this.
|
|
682
|
+
rating: this._getSubmissionRating(),
|
|
390
683
|
feedback: this.surveyState.feedback,
|
|
391
|
-
answers:
|
|
684
|
+
answers: mergedAnswers,
|
|
392
685
|
...(respondent.respondentId && { respondentId: respondent.respondentId }),
|
|
393
686
|
...(respondent.email && { email: respondent.email }),
|
|
394
687
|
};
|
|
395
688
|
|
|
396
689
|
const response = {
|
|
397
690
|
type: type,
|
|
398
|
-
score: this.
|
|
691
|
+
score: this._getSubmissionRating(),
|
|
399
692
|
feedback: this.surveyState.feedback,
|
|
400
|
-
customAnswers:
|
|
693
|
+
customAnswers: mergedAnswers,
|
|
694
|
+
pageAnswers: normalizedPageAnswers,
|
|
401
695
|
timestamp: new Date().toISOString(),
|
|
402
696
|
};
|
|
403
697
|
|
|
@@ -419,6 +713,138 @@ export class SurveyWidget extends BaseWidget {
|
|
|
419
713
|
this._showSuccessNotification();
|
|
420
714
|
}
|
|
421
715
|
|
|
716
|
+
_handleBack() {
|
|
717
|
+
if (!this._isMultiPageSurvey()) return;
|
|
718
|
+
if (this.surveyState.currentPageIndex <= 0) return;
|
|
719
|
+
this.surveyState.currentPageIndex -= 1;
|
|
720
|
+
this._renderSurvey();
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
_setPageAnswer(pageId, data) {
|
|
724
|
+
if (!pageId) return;
|
|
725
|
+
this.surveyState.pageAnswers[pageId] = {
|
|
726
|
+
...(this.surveyState.pageAnswers[pageId] || {}),
|
|
727
|
+
...data,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
_validateCurrentPage() {
|
|
732
|
+
const page = this._getCurrentPage();
|
|
733
|
+
if (!page || page.required === false) return true;
|
|
734
|
+
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
735
|
+
|
|
736
|
+
const answer = this.surveyState.pageAnswers[pageId] || {};
|
|
737
|
+
|
|
738
|
+
if (page.type === 'rating') {
|
|
739
|
+
if (typeof answer.rating !== 'number') {
|
|
740
|
+
this._showError('Please select a rating');
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (page.type === 'multiple_choice') {
|
|
746
|
+
const hasSingle = Boolean(answer.value);
|
|
747
|
+
const hasMany = Array.isArray(answer.values) && answer.values.length > 0;
|
|
748
|
+
if (!hasSingle && !hasMany) {
|
|
749
|
+
this._showError('Please select an option');
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (page.type === 'text') {
|
|
755
|
+
if (!answer.text || !String(answer.text).trim()) {
|
|
756
|
+
this._showError('Please enter an answer');
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
_getNextPageIndex() {
|
|
765
|
+
if (!this._isMultiPageSurvey()) {
|
|
766
|
+
return -1;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const page = this._getCurrentPage();
|
|
770
|
+
const total = this.surveyOptions.pages.length;
|
|
771
|
+
const currentIndex = this.surveyState.currentPageIndex;
|
|
772
|
+
const fallbackNext = currentIndex + 1 < total ? currentIndex + 1 : -1;
|
|
773
|
+
const navigation = page ? page.afterThisPage || page.after_this_page : null;
|
|
774
|
+
if (!navigation) {
|
|
775
|
+
return fallbackNext;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const nextValue = navigation.default;
|
|
779
|
+
if (!nextValue) {
|
|
780
|
+
return fallbackNext;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (nextValue === 'end_survey') {
|
|
784
|
+
return -1;
|
|
785
|
+
}
|
|
786
|
+
if (nextValue === 'next_page' || nextValue === 'next') {
|
|
787
|
+
return fallbackNext;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (typeof nextValue === 'number') {
|
|
791
|
+
return nextValue >= 0 && nextValue < total ? nextValue : fallbackNext;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (typeof nextValue === 'string') {
|
|
795
|
+
const normalizedId = nextValue.replace(/^page:/, '');
|
|
796
|
+
const pageIndex = this.surveyOptions.pages.findIndex(
|
|
797
|
+
(item) => item.id === normalizedId
|
|
798
|
+
);
|
|
799
|
+
return pageIndex >= 0 ? pageIndex : fallbackNext;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
return fallbackNext;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
_getSubmissionRating() {
|
|
806
|
+
if (typeof this.surveyState.score === 'number') {
|
|
807
|
+
return this.surveyState.score;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (!this._isMultiPageSurvey()) {
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
for (const page of this.surveyOptions.pages) {
|
|
815
|
+
const pageId = page.id || `page_${this.surveyOptions.pages.indexOf(page)}`;
|
|
816
|
+
const answer = this.surveyState.pageAnswers[pageId];
|
|
817
|
+
if (answer && typeof answer.rating === 'number') {
|
|
818
|
+
return answer.rating;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
_normalizePageAnswersForSubmit() {
|
|
826
|
+
const output = {};
|
|
827
|
+
for (const [pageId, answer] of Object.entries(this.surveyState.pageAnswers)) {
|
|
828
|
+
if (answer == null) continue;
|
|
829
|
+
if (Array.isArray(answer.values) && answer.values.length > 0) {
|
|
830
|
+
output[pageId] = answer.values;
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
if (answer.value != null && answer.value !== '') {
|
|
834
|
+
output[pageId] = answer.value;
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
if (typeof answer.rating === 'number') {
|
|
838
|
+
output[pageId] = answer.rating;
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (typeof answer.text === 'string' && answer.text.trim()) {
|
|
842
|
+
output[pageId] = answer.text.trim();
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return output;
|
|
846
|
+
}
|
|
847
|
+
|
|
422
848
|
_getRespondentContext() {
|
|
423
849
|
const sdkUserContext =
|
|
424
850
|
typeof this.sdk.getUserContext === 'function'
|
|
@@ -497,7 +923,7 @@ export class SurveyWidget extends BaseWidget {
|
|
|
497
923
|
}, 3000);
|
|
498
924
|
}
|
|
499
925
|
|
|
500
|
-
_closeSurvey() {
|
|
926
|
+
_closeSurvey(resetState = true) {
|
|
501
927
|
if (this._escapeHandler) {
|
|
502
928
|
document.removeEventListener('keydown', this._escapeHandler);
|
|
503
929
|
this._escapeHandler = null;
|
|
@@ -530,12 +956,16 @@ export class SurveyWidget extends BaseWidget {
|
|
|
530
956
|
this.backdropElement = null;
|
|
531
957
|
}
|
|
532
958
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
959
|
+
if (resetState) {
|
|
960
|
+
this.surveyState = {
|
|
961
|
+
score: null,
|
|
962
|
+
feedback: '',
|
|
963
|
+
customAnswers: {},
|
|
964
|
+
pageAnswers: {},
|
|
965
|
+
currentPageIndex: 0,
|
|
966
|
+
isVisible: false,
|
|
967
|
+
};
|
|
968
|
+
}
|
|
539
969
|
}
|
|
540
970
|
|
|
541
971
|
destroy() {
|
package/types/index.d.ts
CHANGED
|
@@ -61,11 +61,48 @@ declare module '@product7/feedback-sdk' {
|
|
|
61
61
|
options?: Array<{ value: string; label: string }>;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
export interface SurveyRatingConfig {
|
|
65
|
+
survey_type?: string;
|
|
66
|
+
scale?: number;
|
|
67
|
+
low_label?: string | null;
|
|
68
|
+
high_label?: string | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface SurveyMultipleChoiceOption {
|
|
72
|
+
id?: string;
|
|
73
|
+
key?: string;
|
|
74
|
+
value?: string;
|
|
75
|
+
label?: string;
|
|
76
|
+
text?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SurveyMultipleChoiceConfig {
|
|
80
|
+
options?: SurveyMultipleChoiceOption[];
|
|
81
|
+
allow_multiple?: boolean;
|
|
82
|
+
multiple?: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface SurveyPage {
|
|
86
|
+
id?: string;
|
|
87
|
+
type?: 'rating' | 'multiple_choice' | 'text' | string;
|
|
88
|
+
title?: string;
|
|
89
|
+
description?: string;
|
|
90
|
+
required?: boolean;
|
|
91
|
+
position?: number;
|
|
92
|
+
ratingConfig?: SurveyRatingConfig | null;
|
|
93
|
+
rating_config?: SurveyRatingConfig | null;
|
|
94
|
+
multipleChoiceConfig?: SurveyMultipleChoiceConfig | null;
|
|
95
|
+
multiple_choice_config?: SurveyMultipleChoiceConfig | null;
|
|
96
|
+
afterThisPage?: { default?: string | number } | null;
|
|
97
|
+
after_this_page?: { default?: string | number } | null;
|
|
98
|
+
}
|
|
99
|
+
|
|
64
100
|
export interface SurveyWidgetResponse {
|
|
65
101
|
type: SurveyType;
|
|
66
102
|
score: number | null;
|
|
67
103
|
feedback: string;
|
|
68
104
|
customAnswers: Record<string, any>;
|
|
105
|
+
pageAnswers?: Record<string, any>;
|
|
69
106
|
timestamp: string;
|
|
70
107
|
}
|
|
71
108
|
|
|
@@ -78,6 +115,7 @@ declare module '@product7/feedback-sdk' {
|
|
|
78
115
|
lowLabel?: string | null;
|
|
79
116
|
highLabel?: string | null;
|
|
80
117
|
customQuestions?: SurveyQuestion[];
|
|
118
|
+
pages?: SurveyPage[];
|
|
81
119
|
respondentId?: string | null;
|
|
82
120
|
email?: string | null;
|
|
83
121
|
theme?: 'light' | 'dark';
|