@product7/feedback-sdk 1.5.4 → 1.5.8

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>
@@ -295,12 +460,12 @@ export class SurveyWidget extends BaseWidget {
295
460
  }
296
461
 
297
462
  return `
298
- <div class="feedback-survey-ces" data-page-id="${pageId}">
463
+ <div class="feedback-survey-ces feedback-survey-rating-scale" data-page-id="${pageId}">
299
464
  ${[...Array(scale).keys()]
300
465
  .map((i) => {
301
466
  const score = i + 1;
302
467
  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>`;
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>`;
304
469
  })
305
470
  .join('')}
306
471
  </div>
@@ -310,9 +475,11 @@ export class SurveyWidget extends BaseWidget {
310
475
 
311
476
  _renderMultipleChoicePage(page) {
312
477
  const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
313
- const config = page.multipleChoiceConfig || page.multiple_choice_config || {};
478
+ const config =
479
+ page.multipleChoiceConfig || page.multiple_choice_config || {};
314
480
  const options = Array.isArray(config.options) ? config.options : [];
315
- const allowMultiple = config.allow_multiple === true || config.multiple === true;
481
+ const allowMultiple =
482
+ config.allow_multiple === true || config.multiple === true;
316
483
  const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
317
484
  const selectedValues = Array.isArray(pageAnswer.values)
318
485
  ? pageAnswer.values
@@ -417,7 +584,9 @@ export class SurveyWidget extends BaseWidget {
417
584
  const submitBtn = this.surveyElement.querySelector(
418
585
  '.feedback-survey-submit'
419
586
  );
420
- submitBtn.addEventListener('click', () => this._handleSubmit());
587
+ if (submitBtn) {
588
+ submitBtn.addEventListener('click', () => this._handleSubmit());
589
+ }
421
590
 
422
591
  const backBtn = this.surveyElement.querySelector('.feedback-survey-back');
423
592
  if (backBtn) {
@@ -427,9 +596,11 @@ export class SurveyWidget extends BaseWidget {
427
596
  const textarea = this.surveyElement.querySelector(
428
597
  '.feedback-survey-textarea'
429
598
  );
430
- textarea.addEventListener('input', (e) => {
431
- this.surveyState.feedback = e.target.value;
432
- });
599
+ if (textarea) {
600
+ textarea.addEventListener('input', (e) => {
601
+ this.surveyState.feedback = e.target.value;
602
+ });
603
+ }
433
604
 
434
605
  this._attachTypeSpecificEvents();
435
606
 
@@ -601,6 +772,7 @@ export class SurveyWidget extends BaseWidget {
601
772
  btn.classList.remove('selected');
602
773
  }
603
774
  });
775
+ this._maybeAutoSubmit();
604
776
  }
605
777
 
606
778
  _selectCSAT(score) {
@@ -615,6 +787,7 @@ export class SurveyWidget extends BaseWidget {
615
787
  btn.classList.remove('selected');
616
788
  }
617
789
  });
790
+ this._maybeAutoSubmit();
618
791
  }
619
792
 
620
793
  _selectCES(score) {
@@ -629,6 +802,14 @@ export class SurveyWidget extends BaseWidget {
629
802
  btn.classList.remove('selected');
630
803
  }
631
804
  });
805
+ this._maybeAutoSubmit();
806
+ }
807
+
808
+ _maybeAutoSubmit() {
809
+ if (!this._shouldAutoSubmitOnSelect()) {
810
+ return;
811
+ }
812
+ this._handleSubmit();
632
813
  }
633
814
 
634
815
  _selectFrequency(freq) {
@@ -646,6 +827,9 @@ export class SurveyWidget extends BaseWidget {
646
827
 
647
828
  async _handleSubmit() {
648
829
  const type = this.surveyOptions.surveyType;
830
+ if (this.surveyState.isSubmitting) {
831
+ return;
832
+ }
649
833
 
650
834
  if (this._isMultiPageSurvey()) {
651
835
  if (!this._validateCurrentPage()) {
@@ -653,7 +837,10 @@ export class SurveyWidget extends BaseWidget {
653
837
  }
654
838
 
655
839
  const nextPageIndex = this._getNextPageIndex();
656
- if (nextPageIndex !== -1 && nextPageIndex !== this.surveyState.currentPageIndex) {
840
+ if (
841
+ nextPageIndex !== -1 &&
842
+ nextPageIndex !== this.surveyState.currentPageIndex
843
+ ) {
657
844
  this.surveyState.currentPageIndex = nextPageIndex;
658
845
  this._renderSurvey();
659
846
  return;
@@ -669,6 +856,8 @@ export class SurveyWidget extends BaseWidget {
669
856
  return;
670
857
  }
671
858
 
859
+ this.surveyState.isSubmitting = true;
860
+
672
861
  const respondent = this._getRespondentContext();
673
862
  const normalizedPageAnswers = this._normalizePageAnswersForSubmit();
674
863
  const mergedAnswers = {
@@ -695,16 +884,18 @@ export class SurveyWidget extends BaseWidget {
695
884
  timestamp: new Date().toISOString(),
696
885
  };
697
886
 
698
- if (this.surveyOptions.onSubmit) {
699
- this.surveyOptions.onSubmit(response);
700
- }
701
-
702
887
  try {
888
+ if (this.surveyOptions.onSubmit) {
889
+ this.surveyOptions.onSubmit(response);
890
+ }
891
+
703
892
  const surveyId =
704
893
  this.surveyOptions.surveyId || `local_${type}_${Date.now()}`;
705
894
  await this.apiService.submitSurveyResponse(surveyId, responseData);
706
895
  } catch (error) {
707
896
  console.error('[SurveyWidget] Failed to submit survey:', error);
897
+ } finally {
898
+ this.surveyState.isSubmitting = false;
708
899
  }
709
900
 
710
901
  this.sdk.eventBus.emit('survey:submitted', { widget: this, response });
@@ -812,7 +1003,8 @@ export class SurveyWidget extends BaseWidget {
812
1003
  }
813
1004
 
814
1005
  for (const page of this.surveyOptions.pages) {
815
- const pageId = page.id || `page_${this.surveyOptions.pages.indexOf(page)}`;
1006
+ const pageId =
1007
+ page.id || `page_${this.surveyOptions.pages.indexOf(page)}`;
816
1008
  const answer = this.surveyState.pageAnswers[pageId];
817
1009
  if (answer && typeof answer.rating === 'number') {
818
1010
  return answer.rating;
@@ -824,7 +1016,9 @@ export class SurveyWidget extends BaseWidget {
824
1016
 
825
1017
  _normalizePageAnswersForSubmit() {
826
1018
  const output = {};
827
- for (const [pageId, answer] of Object.entries(this.surveyState.pageAnswers)) {
1019
+ for (const [pageId, answer] of Object.entries(
1020
+ this.surveyState.pageAnswers
1021
+ )) {
828
1022
  if (answer == null) continue;
829
1023
  if (Array.isArray(answer.values) && answer.values.length > 0) {
830
1024
  output[pageId] = answer.values;
@@ -851,8 +1045,7 @@ export class SurveyWidget extends BaseWidget {
851
1045
  ? this.sdk.getUserContext() || {}
852
1046
  : {};
853
1047
  const apiUserContext =
854
- this.apiService &&
855
- typeof this.apiService.getUserContext === 'function'
1048
+ this.apiService && typeof this.apiService.getUserContext === 'function'
856
1049
  ? this.apiService.getUserContext() || {}
857
1050
  : {};
858
1051
  const localUserContext = this.options.userContext || {};
@@ -900,7 +1093,15 @@ export class SurveyWidget extends BaseWidget {
900
1093
  const submitBtn = this.surveyElement.querySelector(
901
1094
  '.feedback-survey-submit'
902
1095
  );
903
- 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
+ }
904
1105
 
905
1106
  setTimeout(() => error.remove(), 3000);
906
1107
  }
@@ -963,6 +1164,7 @@ export class SurveyWidget extends BaseWidget {
963
1164
  customAnswers: {},
964
1165
  pageAnswers: {},
965
1166
  currentPageIndex: 0,
1167
+ isSubmitting: false,
966
1168
  isVisible: false,
967
1169
  };
968
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">