@product7/feedback-sdk 1.5.3 → 1.5.7

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.
@@ -12,6 +12,25 @@ export class SurveyWidget extends BaseWidget {
12
12
  description: options.description || null,
13
13
  lowLabel: options.lowLabel || null,
14
14
  highLabel: options.highLabel || null,
15
+ ratingScale: options.ratingScale || options.scale || null,
16
+ showFeedbackInput:
17
+ typeof options.showFeedbackInput === 'boolean'
18
+ ? options.showFeedbackInput
19
+ : null,
20
+ showSubmitButton:
21
+ typeof options.showSubmitButton === 'boolean'
22
+ ? options.showSubmitButton
23
+ : null,
24
+ autoSubmitOnSelect:
25
+ typeof options.autoSubmitOnSelect === 'boolean'
26
+ ? options.autoSubmitOnSelect
27
+ : null,
28
+ showTitle:
29
+ typeof options.showTitle === 'boolean' ? options.showTitle : null,
30
+ showDescription:
31
+ typeof options.showDescription === 'boolean'
32
+ ? options.showDescription
33
+ : null,
15
34
  customQuestions: options.customQuestions || [],
16
35
  pages: Array.isArray(options.pages) ? options.pages : [],
17
36
  respondentId: options.respondentId || null,
@@ -26,6 +45,7 @@ export class SurveyWidget extends BaseWidget {
26
45
  customAnswers: {},
27
46
  pageAnswers: {},
28
47
  currentPageIndex: 0,
48
+ isSubmitting: false,
29
49
  isVisible: false,
30
50
  };
31
51
  }
@@ -61,6 +81,10 @@ export class SurveyWidget extends BaseWidget {
61
81
  const config = this._getSurveyConfig();
62
82
  const isMultiPage = this._isMultiPageSurvey();
63
83
  const isLastPage = this._isLastPage();
84
+ const showFeedbackInput = this._shouldShowFeedbackInput();
85
+ const showActions = this._shouldShowActions();
86
+ const showTitle = this._shouldShowTitle(config);
87
+ const showDescription = this._shouldShowDescription(config);
64
88
  const pageProgress = isMultiPage
65
89
  ? `Page ${this.surveyState.currentPageIndex + 1} of ${this.surveyOptions.pages.length}`
66
90
  : '';
@@ -80,21 +104,33 @@ export class SurveyWidget extends BaseWidget {
80
104
  }
81
105
 
82
106
  this.surveyElement = document.createElement('div');
83
- this.surveyElement.className = `feedback-survey feedback-survey-${this.surveyOptions.position}`;
107
+ this.surveyElement.className = `feedback-survey feedback-survey-${this.surveyOptions.position}${
108
+ showDescription && !showTitle
109
+ ? ' feedback-survey-description-primary'
110
+ : ''
111
+ }`;
84
112
 
85
113
  this.surveyElement.innerHTML = `
86
- <button class="feedback-survey-close">&times;</button>
87
- <h3 class="feedback-survey-title">${config.title}</h3>
88
- <p class="feedback-survey-description">${config.description}</p>
114
+ <button class="feedback-survey-close"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><line x1="200" y1="56" x2="56" y2="200" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="200" y1="200" x2="56" y2="56" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg></button>
115
+ ${showTitle ? `<h3 class="feedback-survey-title">${config.title}</h3>` : ''}
116
+ ${showDescription ? `<p class="feedback-survey-description">${config.description}</p>` : ''}
89
117
  ${isMultiPage ? `<div class="feedback-survey-progress">${pageProgress}</div>` : ''}
90
118
  <div class="feedback-survey-content">${config.html}</div>
91
- <div class="feedback-survey-feedback">
119
+ ${
120
+ showFeedbackInput
121
+ ? `<div class="feedback-survey-feedback">
92
122
  <textarea class="feedback-survey-textarea" placeholder="Any additional feedback? (optional)">${this.surveyState.feedback || ''}</textarea>
93
- </div>
94
- <div class="feedback-survey-actions">
123
+ </div>`
124
+ : ''
125
+ }
126
+ ${
127
+ showActions
128
+ ? `<div class="feedback-survey-actions">
95
129
  ${backButton}
96
130
  <button class="feedback-survey-submit">${submitLabel}</button>
97
- </div>
131
+ </div>`
132
+ : ''
133
+ }
98
134
  `;
99
135
 
100
136
  document.body.appendChild(this.surveyElement);
@@ -114,37 +150,62 @@ export class SurveyWidget extends BaseWidget {
114
150
  return this._getCurrentPageConfig();
115
151
  }
116
152
 
153
+ const npsScale = this._getNPSScale();
154
+ const npsUsesSegmentedScale = npsScale.values.length <= 7;
155
+ const npsContainerClass = npsUsesSegmentedScale
156
+ ? 'feedback-survey-ces feedback-survey-rating-scale'
157
+ : 'feedback-survey-nps';
158
+ const npsButtonClass = npsUsesSegmentedScale
159
+ ? 'feedback-survey-nps-btn feedback-survey-ces-btn feedback-survey-rating-scale-btn'
160
+ : 'feedback-survey-nps-btn';
161
+ const npsLowLabel =
162
+ this.surveyOptions.lowLabel ||
163
+ (npsScale.start === 0 ? 'Not likely' : 'Strongly Disagree');
164
+ const npsHighLabel =
165
+ this.surveyOptions.highLabel ||
166
+ (npsScale.start === 0 ? 'Very likely' : 'Strongly Agree');
167
+ const npsPrompt =
168
+ this.surveyOptions.description ||
169
+ this.surveyOptions.title ||
170
+ 'How likely are you to recommend us?';
171
+ const csatPrompt =
172
+ this.surveyOptions.description ||
173
+ this.surveyOptions.title ||
174
+ 'How satisfied are you?';
175
+ const cesPrompt =
176
+ this.surveyOptions.description ||
177
+ this.surveyOptions.title ||
178
+ 'How easy was it?';
179
+
117
180
  const configs = {
118
181
  nps: {
119
- title:
120
- this.surveyOptions.title || 'How likely are you to recommend us?',
121
- description:
122
- this.surveyOptions.description ||
123
- 'On a scale of 0-10, how likely are you to recommend our product to a friend or colleague?',
182
+ title: this.surveyOptions.title || '',
183
+ description: npsPrompt,
124
184
  html: `
125
- <div class="feedback-survey-nps">
126
- ${[...Array(11).keys()]
185
+ <div class="${npsContainerClass}">
186
+ ${npsScale.values
127
187
  .map(
128
188
  (n) => `
129
- <button class="feedback-survey-nps-btn" data-score="${n}">${n}</button>
189
+ <button class="${npsButtonClass}" data-score="${n}">${n}</button>
130
190
  `
131
191
  )
132
192
  .join('')}
133
193
  </div>
134
- <div class="feedback-survey-labels">
135
- <span>${this.surveyOptions.lowLabel || 'Not likely'}</span>
136
- <span>${this.surveyOptions.highLabel || 'Very likely'}</span>
137
- </div>
194
+ ${this._renderScaleLabels(npsLowLabel, npsHighLabel)}
138
195
  `,
139
196
  },
140
197
  csat: {
141
- title: this.surveyOptions.title || 'How satisfied are you?',
142
- description:
143
- this.surveyOptions.description ||
144
- 'How would you rate your overall satisfaction with our product?',
198
+ title: this.surveyOptions.title || '',
199
+ description: csatPrompt,
145
200
  html: `
146
201
  <div class="feedback-survey-csat">
147
- ${['😞', '😕', '😐', '🙂', '😄']
202
+ ${[
203
+ '\uD83D\uDE1E',
204
+ '\uD83D\uDE15',
205
+ '\uD83D\uDE10',
206
+ '\uD83D\uDE42',
207
+ '\uD83D\uDE04',
208
+ ]
148
209
  .map(
149
210
  (emoji, i) => `
150
211
  <button class="feedback-survey-csat-btn" data-score="${i + 1}">${emoji}</button>
@@ -152,17 +213,15 @@ export class SurveyWidget extends BaseWidget {
152
213
  )
153
214
  .join('')}
154
215
  </div>
155
- <div class="feedback-survey-labels">
156
- <span>${this.surveyOptions.lowLabel || 'Very dissatisfied'}</span>
157
- <span>${this.surveyOptions.highLabel || 'Very satisfied'}</span>
158
- </div>
216
+ ${this._renderScaleLabels(
217
+ this.surveyOptions.lowLabel || 'Very dissatisfied',
218
+ this.surveyOptions.highLabel || 'Very satisfied'
219
+ )}
159
220
  `,
160
221
  },
161
222
  ces: {
162
- title: this.surveyOptions.title || 'How easy was it?',
163
- description:
164
- this.surveyOptions.description ||
165
- 'How easy was it to accomplish your task today?',
223
+ title: this.surveyOptions.title || '',
224
+ description: cesPrompt,
166
225
  html: `
167
226
  <div class="feedback-survey-ces">
168
227
  ${['Very Difficult', 'Difficult', 'Neutral', 'Easy', 'Very Easy']
@@ -187,6 +246,91 @@ export class SurveyWidget extends BaseWidget {
187
246
  return configs[this.surveyOptions.surveyType] || configs.nps;
188
247
  }
189
248
 
249
+ _getNPSScale() {
250
+ const rawScale = Number(this.surveyOptions.ratingScale);
251
+ const scale = Number.isFinite(rawScale) && rawScale >= 2 ? rawScale : 5;
252
+ const start = scale === 11 ? 0 : 1;
253
+ return {
254
+ scale,
255
+ start,
256
+ values: Array.from({ length: scale }, (_, index) => start + index),
257
+ };
258
+ }
259
+
260
+ _renderScaleLabels(lowLabel, highLabel) {
261
+ const low = lowLabel || '';
262
+ const high = highLabel || '';
263
+ if (!low && !high) {
264
+ return '';
265
+ }
266
+ return `
267
+ <div class="feedback-survey-labels">
268
+ <span>${low}</span>
269
+ <span>${high}</span>
270
+ </div>
271
+ `;
272
+ }
273
+
274
+ _isRatingSurveyType(type = this.surveyOptions.surveyType) {
275
+ return type === 'nps' || type === 'csat' || type === 'ces';
276
+ }
277
+
278
+ _shouldShowTitle(config) {
279
+ if (!config || !config.title) {
280
+ return false;
281
+ }
282
+ if (typeof this.surveyOptions.showTitle === 'boolean') {
283
+ return this.surveyOptions.showTitle;
284
+ }
285
+ if (!this._isMultiPageSurvey() && this._isRatingSurveyType()) {
286
+ return !config.description;
287
+ }
288
+ return true;
289
+ }
290
+
291
+ _shouldShowDescription(config) {
292
+ if (typeof this.surveyOptions.showDescription === 'boolean') {
293
+ return this.surveyOptions.showDescription;
294
+ }
295
+ if (!this._isMultiPageSurvey() && this._isRatingSurveyType()) {
296
+ return Boolean(config && (config.description || config.title));
297
+ }
298
+ return Boolean(config && config.description);
299
+ }
300
+
301
+ _shouldShowFeedbackInput() {
302
+ if (this._isMultiPageSurvey()) {
303
+ return false;
304
+ }
305
+ if (typeof this.surveyOptions.showFeedbackInput === 'boolean') {
306
+ return this.surveyOptions.showFeedbackInput;
307
+ }
308
+ return false;
309
+ }
310
+
311
+ _shouldAutoSubmitOnSelect() {
312
+ if (typeof this.surveyOptions.autoSubmitOnSelect === 'boolean') {
313
+ return this.surveyOptions.autoSubmitOnSelect;
314
+ }
315
+ if (this._isMultiPageSurvey()) {
316
+ return false;
317
+ }
318
+ if (this.surveyOptions.showSubmitButton === true) {
319
+ return false;
320
+ }
321
+ return this._isRatingSurveyType() && !this._shouldShowFeedbackInput();
322
+ }
323
+
324
+ _shouldShowActions() {
325
+ if (this._isMultiPageSurvey()) {
326
+ return true;
327
+ }
328
+ if (typeof this.surveyOptions.showSubmitButton === 'boolean') {
329
+ return this.surveyOptions.showSubmitButton;
330
+ }
331
+ return !this._shouldAutoSubmitOnSelect();
332
+ }
333
+
190
334
  _isMultiPageSurvey() {
191
335
  return (
192
336
  Array.isArray(this.surveyOptions.pages) &&
@@ -201,7 +345,9 @@ export class SurveyWidget extends BaseWidget {
201
345
 
202
346
  _isLastPage() {
203
347
  if (!this._isMultiPageSurvey()) return true;
204
- return this.surveyState.currentPageIndex >= this.surveyOptions.pages.length - 1;
348
+ return (
349
+ this.surveyState.currentPageIndex >= this.surveyOptions.pages.length - 1
350
+ );
205
351
  }
206
352
 
207
353
  _getCurrentPageConfig() {
@@ -212,10 +358,7 @@ export class SurveyWidget extends BaseWidget {
212
358
 
213
359
  return {
214
360
  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.',
361
+ description: page.description || this.surveyOptions.description || '',
219
362
  html: this._renderSurveyPage(page),
220
363
  };
221
364
  }
@@ -223,9 +366,7 @@ export class SurveyWidget extends BaseWidget {
223
366
  _getFallbackSurveyConfig() {
224
367
  return {
225
368
  title: this.surveyOptions.title || 'Quick Feedback',
226
- description:
227
- this.surveyOptions.description ||
228
- 'Help us improve by answering a few questions.',
369
+ description: this.surveyOptions.description || '',
229
370
  html: this._renderCustomQuestions(),
230
371
  };
231
372
  }
@@ -247,24 +388,48 @@ export class SurveyWidget extends BaseWidget {
247
388
  const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
248
389
  const config = page.ratingConfig || page.rating_config || {};
249
390
  const scale = Number(config.scale) || 5;
250
- const ratingType = config.survey_type || this.surveyOptions.surveyType || 'csat';
391
+ const ratingType =
392
+ config.survey_type || this.surveyOptions.surveyType || 'csat';
251
393
  const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
252
394
  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') {
395
+ const defaultLowLabel =
396
+ ratingType === 'nps'
397
+ ? scale === 11
398
+ ? 'Not likely'
399
+ : 'Strongly Disagree'
400
+ : '';
401
+ const defaultHighLabel =
402
+ ratingType === 'nps'
403
+ ? scale === 11
404
+ ? 'Very likely'
405
+ : 'Strongly Agree'
406
+ : '';
407
+ const lowLabel =
408
+ config.low_label || this.surveyOptions.lowLabel || defaultLowLabel;
409
+ const highLabel =
410
+ config.high_label || this.surveyOptions.highLabel || defaultHighLabel;
411
+ const labels = this._renderScaleLabels(lowLabel, highLabel);
412
+
413
+ if (ratingType === 'nps') {
414
+ const npsScale = Number.isFinite(scale) && scale >= 2 ? scale : 5;
415
+ const start = npsScale === 11 ? 0 : 1;
416
+ const values = Array.from(
417
+ { length: npsScale },
418
+ (_, index) => start + index
419
+ );
420
+ const usesSegmentedScale = values.length <= 7;
421
+ const containerClass = usesSegmentedScale
422
+ ? 'feedback-survey-ces feedback-survey-rating-scale'
423
+ : 'feedback-survey-nps';
424
+ const buttonClass = usesSegmentedScale
425
+ ? 'feedback-survey-page-rating-btn feedback-survey-nps-btn feedback-survey-ces-btn feedback-survey-rating-scale-btn'
426
+ : 'feedback-survey-page-rating-btn feedback-survey-nps-btn';
262
427
  return `
263
- <div class="feedback-survey-nps" data-page-id="${pageId}">
264
- ${[...Array(11).keys()]
428
+ <div class="${containerClass}" data-page-id="${pageId}">
429
+ ${values
265
430
  .map((n) => {
266
431
  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>`;
432
+ return `<button class="${buttonClass}${selected}" data-page-id="${pageId}" data-score="${n}">${n}</button>`;
268
433
  })
269
434
  .join('')}
270
435
  </div>
@@ -273,7 +438,13 @@ export class SurveyWidget extends BaseWidget {
273
438
  }
274
439
 
275
440
  if (ratingType === 'emoji' && scale === 5) {
276
- const emojis = [':(', ':/', ':|', ':)', ':D'];
441
+ const emojis = [
442
+ '\uD83D\uDE1E',
443
+ '\uD83D\uDE15',
444
+ '\uD83D\uDE10',
445
+ '\uD83D\uDE42',
446
+ '\uD83D\uDE04',
447
+ ];
277
448
  return `
278
449
  <div class="feedback-survey-csat" data-page-id="${pageId}">
279
450
  ${emojis
@@ -289,12 +460,12 @@ export class SurveyWidget extends BaseWidget {
289
460
  }
290
461
 
291
462
  return `
292
- <div class="feedback-survey-ces" data-page-id="${pageId}">
463
+ <div class="feedback-survey-ces feedback-survey-rating-scale" data-page-id="${pageId}">
293
464
  ${[...Array(scale).keys()]
294
465
  .map((i) => {
295
466
  const score = i + 1;
296
467
  const selected = currentRating === score ? ' selected' : '';
297
- return `<button class="feedback-survey-page-rating-btn feedback-survey-ces-btn${selected}" data-page-id="${pageId}" data-score="${score}">${score}</button>`;
468
+ return `<button class="feedback-survey-page-rating-btn feedback-survey-ces-btn feedback-survey-rating-scale-btn${selected}" data-page-id="${pageId}" data-score="${score}">${score}</button>`;
298
469
  })
299
470
  .join('')}
300
471
  </div>
@@ -304,9 +475,11 @@ export class SurveyWidget extends BaseWidget {
304
475
 
305
476
  _renderMultipleChoicePage(page) {
306
477
  const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
307
- const config = page.multipleChoiceConfig || page.multiple_choice_config || {};
478
+ const config =
479
+ page.multipleChoiceConfig || page.multiple_choice_config || {};
308
480
  const options = Array.isArray(config.options) ? config.options : [];
309
- const allowMultiple = config.allow_multiple === true || config.multiple === true;
481
+ const allowMultiple =
482
+ config.allow_multiple === true || config.multiple === true;
310
483
  const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
311
484
  const selectedValues = Array.isArray(pageAnswer.values)
312
485
  ? pageAnswer.values
@@ -411,7 +584,9 @@ export class SurveyWidget extends BaseWidget {
411
584
  const submitBtn = this.surveyElement.querySelector(
412
585
  '.feedback-survey-submit'
413
586
  );
414
- submitBtn.addEventListener('click', () => this._handleSubmit());
587
+ if (submitBtn) {
588
+ submitBtn.addEventListener('click', () => this._handleSubmit());
589
+ }
415
590
 
416
591
  const backBtn = this.surveyElement.querySelector('.feedback-survey-back');
417
592
  if (backBtn) {
@@ -421,9 +596,11 @@ export class SurveyWidget extends BaseWidget {
421
596
  const textarea = this.surveyElement.querySelector(
422
597
  '.feedback-survey-textarea'
423
598
  );
424
- textarea.addEventListener('input', (e) => {
425
- this.surveyState.feedback = e.target.value;
426
- });
599
+ if (textarea) {
600
+ textarea.addEventListener('input', (e) => {
601
+ this.surveyState.feedback = e.target.value;
602
+ });
603
+ }
427
604
 
428
605
  this._attachTypeSpecificEvents();
429
606
 
@@ -595,6 +772,7 @@ export class SurveyWidget extends BaseWidget {
595
772
  btn.classList.remove('selected');
596
773
  }
597
774
  });
775
+ this._maybeAutoSubmit();
598
776
  }
599
777
 
600
778
  _selectCSAT(score) {
@@ -609,6 +787,7 @@ export class SurveyWidget extends BaseWidget {
609
787
  btn.classList.remove('selected');
610
788
  }
611
789
  });
790
+ this._maybeAutoSubmit();
612
791
  }
613
792
 
614
793
  _selectCES(score) {
@@ -623,6 +802,14 @@ export class SurveyWidget extends BaseWidget {
623
802
  btn.classList.remove('selected');
624
803
  }
625
804
  });
805
+ this._maybeAutoSubmit();
806
+ }
807
+
808
+ _maybeAutoSubmit() {
809
+ if (!this._shouldAutoSubmitOnSelect()) {
810
+ return;
811
+ }
812
+ this._handleSubmit();
626
813
  }
627
814
 
628
815
  _selectFrequency(freq) {
@@ -640,6 +827,9 @@ export class SurveyWidget extends BaseWidget {
640
827
 
641
828
  async _handleSubmit() {
642
829
  const type = this.surveyOptions.surveyType;
830
+ if (this.surveyState.isSubmitting) {
831
+ return;
832
+ }
643
833
 
644
834
  if (this._isMultiPageSurvey()) {
645
835
  if (!this._validateCurrentPage()) {
@@ -647,7 +837,10 @@ export class SurveyWidget extends BaseWidget {
647
837
  }
648
838
 
649
839
  const nextPageIndex = this._getNextPageIndex();
650
- if (nextPageIndex !== -1 && nextPageIndex !== this.surveyState.currentPageIndex) {
840
+ if (
841
+ nextPageIndex !== -1 &&
842
+ nextPageIndex !== this.surveyState.currentPageIndex
843
+ ) {
651
844
  this.surveyState.currentPageIndex = nextPageIndex;
652
845
  this._renderSurvey();
653
846
  return;
@@ -663,6 +856,8 @@ export class SurveyWidget extends BaseWidget {
663
856
  return;
664
857
  }
665
858
 
859
+ this.surveyState.isSubmitting = true;
860
+
666
861
  const respondent = this._getRespondentContext();
667
862
  const normalizedPageAnswers = this._normalizePageAnswersForSubmit();
668
863
  const mergedAnswers = {
@@ -689,16 +884,18 @@ export class SurveyWidget extends BaseWidget {
689
884
  timestamp: new Date().toISOString(),
690
885
  };
691
886
 
692
- if (this.surveyOptions.onSubmit) {
693
- this.surveyOptions.onSubmit(response);
694
- }
695
-
696
887
  try {
888
+ if (this.surveyOptions.onSubmit) {
889
+ this.surveyOptions.onSubmit(response);
890
+ }
891
+
697
892
  const surveyId =
698
893
  this.surveyOptions.surveyId || `local_${type}_${Date.now()}`;
699
894
  await this.apiService.submitSurveyResponse(surveyId, responseData);
700
895
  } catch (error) {
701
896
  console.error('[SurveyWidget] Failed to submit survey:', error);
897
+ } finally {
898
+ this.surveyState.isSubmitting = false;
702
899
  }
703
900
 
704
901
  this.sdk.eventBus.emit('survey:submitted', { widget: this, response });
@@ -806,7 +1003,8 @@ export class SurveyWidget extends BaseWidget {
806
1003
  }
807
1004
 
808
1005
  for (const page of this.surveyOptions.pages) {
809
- const pageId = page.id || `page_${this.surveyOptions.pages.indexOf(page)}`;
1006
+ const pageId =
1007
+ page.id || `page_${this.surveyOptions.pages.indexOf(page)}`;
810
1008
  const answer = this.surveyState.pageAnswers[pageId];
811
1009
  if (answer && typeof answer.rating === 'number') {
812
1010
  return answer.rating;
@@ -818,7 +1016,9 @@ export class SurveyWidget extends BaseWidget {
818
1016
 
819
1017
  _normalizePageAnswersForSubmit() {
820
1018
  const output = {};
821
- for (const [pageId, answer] of Object.entries(this.surveyState.pageAnswers)) {
1019
+ for (const [pageId, answer] of Object.entries(
1020
+ this.surveyState.pageAnswers
1021
+ )) {
822
1022
  if (answer == null) continue;
823
1023
  if (Array.isArray(answer.values) && answer.values.length > 0) {
824
1024
  output[pageId] = answer.values;
@@ -845,8 +1045,7 @@ export class SurveyWidget extends BaseWidget {
845
1045
  ? this.sdk.getUserContext() || {}
846
1046
  : {};
847
1047
  const apiUserContext =
848
- this.apiService &&
849
- typeof this.apiService.getUserContext === 'function'
1048
+ this.apiService && typeof this.apiService.getUserContext === 'function'
850
1049
  ? this.apiService.getUserContext() || {}
851
1050
  : {};
852
1051
  const localUserContext = this.options.userContext || {};
@@ -894,7 +1093,15 @@ export class SurveyWidget extends BaseWidget {
894
1093
  const submitBtn = this.surveyElement.querySelector(
895
1094
  '.feedback-survey-submit'
896
1095
  );
897
- submitBtn.parentNode.insertBefore(error, submitBtn);
1096
+ const actions = this.surveyElement.querySelector(
1097
+ '.feedback-survey-actions'
1098
+ );
1099
+ const anchor = submitBtn || actions;
1100
+ if (anchor && anchor.parentNode) {
1101
+ anchor.parentNode.insertBefore(error, anchor);
1102
+ } else {
1103
+ this.surveyElement.appendChild(error);
1104
+ }
898
1105
 
899
1106
  setTimeout(() => error.remove(), 3000);
900
1107
  }
@@ -957,6 +1164,7 @@ export class SurveyWidget extends BaseWidget {
957
1164
  customAnswers: {},
958
1165
  pageAnswers: {},
959
1166
  currentPageIndex: 0,
1167
+ isSubmitting: false,
960
1168
  isVisible: false,
961
1169
  };
962
1170
  }
@@ -124,12 +124,7 @@ export class NavigationTabs {
124
124
  }
125
125
 
126
126
  _getMessagesIcon() {
127
- return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 256 256">
128
- <circle cx="128" cy="128" r="12"/>
129
- <circle cx="84" cy="128" r="12"/>
130
- <circle cx="172" cy="128" r="12"/>
131
- <path d="M79.93,211.11a96,96,0,1,0-35-35h0L32.42,213.46a8,8,0,0,0,10.12,10.12l37.39-12.47Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
132
- </svg>`;
127
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M71.58,144,32,176V48a8,8,0,0,1,8-8H168a8,8,0,0,1,8,8v88a8,8,0,0,1-8,8Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M80,144v40a8,8,0,0,0,8,8h96.42L224,224V96a8,8,0,0,0-8-8H176" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>`;
133
128
  }
134
129
 
135
130
  _getHelpIcon() {
@@ -116,10 +116,12 @@ export class ChatView {
116
116
  <div class="messenger-compose-attachments-preview"></div>
117
117
 
118
118
  <div class="messenger-chat-compose">
119
- <button class="sdk-btn-icon messenger-compose-attach" aria-label="Attach file">
120
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="16" height="16" fill="none"/><path d="M160,80,76.69,164.69a16,16,0,0,0,22.63,22.62L198.63,86.63a32,32,0,0,0-45.26-45.26L54.06,142.06a48,48,0,0,0,67.88,67.88L204,128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
121
- </button>
122
- <div class="messenger-compose-input-wrapper">
119
+ <button class="sdk-btn-icon messenger-compose-attach" aria-label="Attach file">
120
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="20" height="20">
121
+ <rect width="256" height="256" fill="none"/>
122
+ <path d="M160,80,76.69,164.69a16,16,0,0,0,22.63,22.62L198.63,86.63a32,32,0,0,0-45.26-45.26L54.06,142.06a48,48,0,0,0,67.88,67.88L204,128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
123
+ </svg>
124
+ </button> <div class="messenger-compose-input-wrapper">
123
125
  <textarea class="messenger-compose-input" placeholder="${placeholder}" rows="1"></textarea>
124
126
  </div>
125
127
  <button class="messenger-compose-send" aria-label="Send" disabled>
@@ -69,11 +69,13 @@ export class ConversationsView {
69
69
  this.element.innerHTML = `
70
70
  <div class="messenger-conversations-header">
71
71
  <h2>Messages</h2>
72
- <button class="sdk-close-btn" aria-label="Close">
73
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256">
74
- <path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path>
75
- </svg>
76
- </button>
72
+ <button class="sdk-close-btn" aria-label="Close">
73
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="18" height="18">
74
+ <rect width="256" height="256" fill="none"/>
75
+ <line x1="200" y1="56" x2="56" y2="200" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
76
+ <line x1="200" y1="200" x2="56" y2="56" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
77
+ </svg>
78
+ </button>
77
79
  </div>
78
80
 
79
81
  <div class="messenger-conversations-body">