@product7/product7-js 0.1.0

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.
Files changed (58) hide show
  1. package/README.md +1025 -0
  2. package/dist/README.md +1025 -0
  3. package/dist/product7-js.js +14658 -0
  4. package/dist/product7-js.js.map +1 -0
  5. package/dist/product7-js.min.js +2 -0
  6. package/dist/product7-js.min.js.map +1 -0
  7. package/package.json +114 -0
  8. package/src/api/mock-data/index.js +360 -0
  9. package/src/api/services/ChangelogService.js +28 -0
  10. package/src/api/services/FeedbackService.js +44 -0
  11. package/src/api/services/HelpService.js +50 -0
  12. package/src/api/services/MessengerService.js +279 -0
  13. package/src/api/services/SurveyService.js +127 -0
  14. package/src/api/utils/helpers.js +30 -0
  15. package/src/core/APIService.js +303 -0
  16. package/src/core/BaseAPIService.js +298 -0
  17. package/src/core/EventBus.js +54 -0
  18. package/src/core/Product7.js +812 -0
  19. package/src/core/WebSocketService.js +275 -0
  20. package/src/docs/api.md +226 -0
  21. package/src/docs/example.md +461 -0
  22. package/src/docs/framework-integrations.md +714 -0
  23. package/src/docs/installation.md +281 -0
  24. package/src/index.js +894 -0
  25. package/src/styles/base.js +50 -0
  26. package/src/styles/changelog.js +665 -0
  27. package/src/styles/components.js +553 -0
  28. package/src/styles/design-tokens.js +124 -0
  29. package/src/styles/feedback.js +325 -0
  30. package/src/styles/messenger-components.js +632 -0
  31. package/src/styles/messenger-core.js +233 -0
  32. package/src/styles/messenger-features.js +169 -0
  33. package/src/styles/messenger-views.js +877 -0
  34. package/src/styles/messenger.js +17 -0
  35. package/src/styles/messengerCustomStyles.js +114 -0
  36. package/src/styles/styles.js +26 -0
  37. package/src/styles/survey.js +894 -0
  38. package/src/utils/errors.js +142 -0
  39. package/src/utils/helpers.js +219 -0
  40. package/src/widgets/BaseWidget.js +548 -0
  41. package/src/widgets/ButtonWidget.js +104 -0
  42. package/src/widgets/ChangelogWidget.js +615 -0
  43. package/src/widgets/InlineWidget.js +148 -0
  44. package/src/widgets/MessengerWidget.js +979 -0
  45. package/src/widgets/SurveyWidget.js +1325 -0
  46. package/src/widgets/TabWidget.js +45 -0
  47. package/src/widgets/WidgetFactory.js +70 -0
  48. package/src/widgets/messenger/MessengerState.js +323 -0
  49. package/src/widgets/messenger/components/MessengerLauncher.js +124 -0
  50. package/src/widgets/messenger/components/MessengerPanel.js +111 -0
  51. package/src/widgets/messenger/components/NavigationTabs.js +130 -0
  52. package/src/widgets/messenger/views/ChangelogView.js +167 -0
  53. package/src/widgets/messenger/views/ChatView.js +592 -0
  54. package/src/widgets/messenger/views/ConversationsView.js +244 -0
  55. package/src/widgets/messenger/views/HelpView.js +239 -0
  56. package/src/widgets/messenger/views/HomeView.js +300 -0
  57. package/src/widgets/messenger/views/PreChatFormView.js +109 -0
  58. package/types/index.d.ts +341 -0
@@ -0,0 +1,1325 @@
1
+ import { BaseWidget } from './BaseWidget.js';
2
+
3
+ export class SurveyWidget extends BaseWidget {
4
+ constructor(options) {
5
+ super({ ...options, type: 'survey' });
6
+
7
+ this.surveyOptions = {
8
+ surveyId: options.surveyId || null,
9
+ surveyType: options.surveyType || 'nps',
10
+ position: options.position || 'center',
11
+ theme: options.theme || 'light',
12
+ enabled:
13
+ typeof options.enabled === 'boolean' ? options.enabled : undefined,
14
+ title: options.title || null,
15
+ description: options.description || null,
16
+ lowLabel: options.lowLabel || null,
17
+ highLabel: options.highLabel || null,
18
+ ratingScale: options.ratingScale || options.scale || null,
19
+ showFeedbackInput:
20
+ typeof options.showFeedbackInput === 'boolean'
21
+ ? options.showFeedbackInput
22
+ : null,
23
+ showSubmitButton:
24
+ typeof options.showSubmitButton === 'boolean'
25
+ ? options.showSubmitButton
26
+ : null,
27
+ autoSubmitOnSelect:
28
+ typeof options.autoSubmitOnSelect === 'boolean'
29
+ ? options.autoSubmitOnSelect
30
+ : null,
31
+ showTitle:
32
+ typeof options.showTitle === 'boolean' ? options.showTitle : null,
33
+ showDescription:
34
+ typeof options.showDescription === 'boolean'
35
+ ? options.showDescription
36
+ : null,
37
+ customQuestions: options.customQuestions || [],
38
+ pages: Array.isArray(options.pages) ? options.pages : [],
39
+ respondentId: options.respondentId || null,
40
+ email: options.email || null,
41
+ onSubmit: options.onSubmit || null,
42
+ onDismiss: options.onDismiss || null,
43
+ };
44
+
45
+ this.surveyState = {
46
+ score: null,
47
+ feedback: '',
48
+ customAnswers: {},
49
+ pageAnswers: {},
50
+ currentPageIndex: 0,
51
+ isSubmitting: false,
52
+ isVisible: false,
53
+ };
54
+ }
55
+
56
+ static removeDanglingElements() {
57
+ if (typeof document === 'undefined') return;
58
+
59
+ document
60
+ .querySelectorAll(
61
+ '.feedback-survey, .feedback-survey-backdrop, .feedback-survey-success, .product7-notification'
62
+ )
63
+ .forEach((el) => {
64
+ if (el && el.parentNode) {
65
+ el.parentNode.removeChild(el);
66
+ }
67
+ });
68
+ }
69
+
70
+ _render() {
71
+ const container = document.createElement('div');
72
+ container.className = 'feedback-survey-container';
73
+ return container;
74
+ }
75
+
76
+ _attachEvents() {
77
+ // Events are attached when survey is shown
78
+ }
79
+
80
+ show() {
81
+ if (
82
+ this.options.enabled === false ||
83
+ this.surveyOptions.enabled === false
84
+ ) {
85
+ this.sdk.eventBus.emit('survey:suppressed', {
86
+ widget: this,
87
+ surveyId: this.surveyOptions.surveyId,
88
+ reason: 'disabled',
89
+ });
90
+ return this;
91
+ }
92
+
93
+ SurveyWidget.removeDanglingElements();
94
+ this._renderSurvey();
95
+ this.surveyState.isVisible = true;
96
+ this.sdk.eventBus.emit('survey:shown', {
97
+ widget: this,
98
+ type: this.surveyOptions.surveyType,
99
+ });
100
+ return this;
101
+ }
102
+
103
+ hide() {
104
+ this._closeSurvey();
105
+ return this;
106
+ }
107
+
108
+ _renderSurvey() {
109
+ this._closeSurvey(false);
110
+
111
+ const config = this._getSurveyConfig();
112
+ if (!config) return;
113
+ const isMultiPage = this._isMultiPageSurvey();
114
+ const isLastPage = this._isLastPage();
115
+ const showFeedbackInput = this._shouldShowFeedbackInput();
116
+ const showActions = this._shouldShowActions();
117
+ const showTitle = this._shouldShowTitle(config);
118
+ const showDescription = this._shouldShowDescription(config);
119
+ const progressPercent = isMultiPage
120
+ ? Math.round(
121
+ ((this.surveyState.currentPageIndex + 1) /
122
+ this.surveyOptions.pages.length) *
123
+ 100
124
+ )
125
+ : 0;
126
+ const submitLabel = isMultiPage && !isLastPage ? 'Next' : 'Submit';
127
+ const iconArrowRight = `<iconify-icon icon="ph:arrow-right" width="16" height="16"></iconify-icon>`;
128
+ const iconCheck = `<iconify-icon icon="ph:arrow-right" width="16" height="16"></iconify-icon>`;
129
+ const iconArrowLeft = `<iconify-icon icon="ph:arrow-left" width="16" height="16"></iconify-icon>`;
130
+ const backButton =
131
+ isMultiPage && this.surveyState.currentPageIndex > 0
132
+ ? `<button class="feedback-survey-back">${iconArrowLeft} Back</button>`
133
+ : '';
134
+
135
+ if (this.surveyOptions.position === 'center') {
136
+ this.backdropElement = document.createElement('div');
137
+ this.backdropElement.className = `feedback-survey-backdrop feedback-survey-backdrop-${this.surveyOptions.theme}`;
138
+ document.body.appendChild(this.backdropElement);
139
+ this.backdropElement.addEventListener('click', () =>
140
+ this._handleDismiss()
141
+ );
142
+ }
143
+
144
+ this.surveyElement = document.createElement('div');
145
+ this.surveyElement.className = `feedback-survey feedback-survey-${this.surveyOptions.position} feedback-survey-theme-${this.surveyOptions.theme}${
146
+ showDescription && !showTitle
147
+ ? ' feedback-survey-description-primary'
148
+ : ''
149
+ }`;
150
+
151
+ this.surveyElement.innerHTML = `
152
+ <button class="feedback-survey-close"><iconify-icon icon="ph:x-duotone" width="16" height="16"></iconify-icon></button>
153
+ ${showTitle ? `<h3 class="feedback-survey-title">${config.title}</h3>` : ''}
154
+ ${showDescription ? `<p class="feedback-survey-description">${config.description}</p>` : ''}
155
+ ${isMultiPage && this.surveyOptions.pages.length > 1 ? `<div class="feedback-survey-progress"><div class="feedback-survey-progress-track"><div class="feedback-survey-progress-fill" style="width:${progressPercent}%"></div></div></div>` : ''}
156
+ <div class="feedback-survey-content">${config.html}</div>
157
+ ${
158
+ showFeedbackInput
159
+ ? `<div class="feedback-survey-feedback">
160
+ <textarea class="feedback-survey-textarea" placeholder="Any additional feedback? (optional)">${this.surveyState.feedback || ''}</textarea>
161
+ </div>`
162
+ : ''
163
+ }
164
+ ${
165
+ showActions
166
+ ? `<div class="feedback-survey-actions">
167
+ ${backButton}
168
+ <button class="feedback-survey-submit">${submitLabel} ${isLastPage ? iconCheck : iconArrowRight}</button>
169
+ </div>`
170
+ : ''
171
+ }
172
+ `;
173
+
174
+ document.body.appendChild(this.surveyElement);
175
+ this._attachSurveyEvents();
176
+
177
+ requestAnimationFrame(() => {
178
+ this.surveyElement.style.opacity = '1';
179
+ this.surveyElement.style.transform =
180
+ this.surveyOptions.position === 'center'
181
+ ? 'translate(-50%, -50%) scale(1)'
182
+ : 'translateY(0)';
183
+ });
184
+ }
185
+
186
+ _getSurveyConfig() {
187
+ if (this._isMultiPageSurvey()) {
188
+ return this._getCurrentPageConfig();
189
+ }
190
+
191
+ const npsScale = this._getNPSScale();
192
+ const npsUsesSegmentedScale = npsScale.values.length <= 7;
193
+ const npsContainerClass = npsUsesSegmentedScale
194
+ ? 'feedback-survey-ces feedback-survey-rating-scale'
195
+ : 'feedback-survey-nps';
196
+ const npsButtonClass = npsUsesSegmentedScale
197
+ ? 'feedback-survey-nps-btn feedback-survey-ces-btn feedback-survey-rating-scale-btn'
198
+ : 'feedback-survey-nps-btn';
199
+ const npsLowLabel =
200
+ this.surveyOptions.lowLabel ||
201
+ (npsScale.start === 0 ? 'Not likely' : 'Strongly Disagree');
202
+ const npsHighLabel =
203
+ this.surveyOptions.highLabel ||
204
+ (npsScale.start === 0 ? 'Very likely' : 'Strongly Agree');
205
+ const npsPrompt =
206
+ this.surveyOptions.description ||
207
+ this.surveyOptions.title ||
208
+ 'How likely are you to recommend us?';
209
+ const csatPrompt =
210
+ this.surveyOptions.description ||
211
+ this.surveyOptions.title ||
212
+ 'How satisfied are you?';
213
+ const cesPrompt =
214
+ this.surveyOptions.description ||
215
+ this.surveyOptions.title ||
216
+ 'How easy was it?';
217
+
218
+ const configs = {
219
+ nps: {
220
+ title: this.surveyOptions.title || '',
221
+ description: npsPrompt,
222
+ html: `
223
+ <div class="${npsContainerClass}">
224
+ ${npsScale.values
225
+ .map(
226
+ (n) => `
227
+ <button class="${npsButtonClass}" data-score="${n}">${n}</button>
228
+ `
229
+ )
230
+ .join('')}
231
+ </div>
232
+ ${this._renderScaleLabels(npsLowLabel, npsHighLabel)}
233
+ `,
234
+ },
235
+ csat: {
236
+ title: this.surveyOptions.title || '',
237
+ description: csatPrompt,
238
+ html: `
239
+ <div class="feedback-survey-csat">
240
+ ${[
241
+ '\uD83D\uDE1E',
242
+ '\uD83D\uDE15',
243
+ '\uD83D\uDE10',
244
+ '\uD83D\uDE42',
245
+ '\uD83D\uDE04',
246
+ ]
247
+ .map(
248
+ (emoji, i) => `
249
+ <button class="feedback-survey-csat-btn" data-score="${i + 1}">${emoji}</button>
250
+ `
251
+ )
252
+ .join('')}
253
+ </div>
254
+ ${this._renderScaleLabels(
255
+ this.surveyOptions.lowLabel || 'Very dissatisfied',
256
+ this.surveyOptions.highLabel || 'Very satisfied'
257
+ )}
258
+ `,
259
+ },
260
+ ces: {
261
+ title: this.surveyOptions.title || '',
262
+ description: cesPrompt,
263
+ html: `
264
+ <div class="feedback-survey-ces-list">
265
+ ${['Very Difficult', 'Difficult', 'Neutral', 'Easy', 'Very Easy']
266
+ .map(
267
+ (label, i) => `
268
+ <button class="feedback-survey-ces-btn feedback-survey-ces-list-btn" data-score="${i + 1}">${label}</button>
269
+ `
270
+ )
271
+ .join('')}
272
+ </div>
273
+ `,
274
+ },
275
+ custom: {
276
+ title: this.surveyOptions.title || 'Quick Feedback',
277
+ description:
278
+ this.surveyOptions.description ||
279
+ 'Help us improve by answering a few questions.',
280
+ html: this._renderCustomQuestions(),
281
+ },
282
+ };
283
+
284
+ return configs[this.surveyOptions.surveyType] || null;
285
+ }
286
+
287
+ _getNPSScale() {
288
+ const rawScale = Number(this.surveyOptions.ratingScale);
289
+ const scale = Number.isFinite(rawScale) && rawScale >= 2 ? rawScale : 5;
290
+ const start = scale === 11 ? 0 : 1;
291
+ return {
292
+ scale,
293
+ start,
294
+ values: Array.from({ length: scale }, (_, index) => start + index),
295
+ };
296
+ }
297
+
298
+ _renderScaleLabels(lowLabel, highLabel) {
299
+ const low = lowLabel || '';
300
+ const high = highLabel || '';
301
+ if (!low && !high) {
302
+ return '';
303
+ }
304
+ return `
305
+ <div class="feedback-survey-labels">
306
+ <span>${low}</span>
307
+ <span>${high}</span>
308
+ </div>
309
+ `;
310
+ }
311
+
312
+ _isRatingSurveyType(type = this.surveyOptions.surveyType) {
313
+ return type === 'nps' || type === 'csat' || type === 'ces';
314
+ }
315
+
316
+ _shouldShowTitle(config) {
317
+ if (!config || !config.title) {
318
+ return false;
319
+ }
320
+ if (typeof this.surveyOptions.showTitle === 'boolean') {
321
+ return this.surveyOptions.showTitle;
322
+ }
323
+ if (!this._isMultiPageSurvey() && this._isRatingSurveyType()) {
324
+ return !config.description;
325
+ }
326
+ return true;
327
+ }
328
+
329
+ _shouldShowDescription(config) {
330
+ if (typeof this.surveyOptions.showDescription === 'boolean') {
331
+ return this.surveyOptions.showDescription;
332
+ }
333
+ if (!this._isMultiPageSurvey() && this._isRatingSurveyType()) {
334
+ return Boolean(config && (config.description || config.title));
335
+ }
336
+ return Boolean(config && config.description);
337
+ }
338
+
339
+ _shouldShowFeedbackInput() {
340
+ if (this._isMultiPageSurvey()) {
341
+ return false;
342
+ }
343
+ if (typeof this.surveyOptions.showFeedbackInput === 'boolean') {
344
+ return this.surveyOptions.showFeedbackInput;
345
+ }
346
+ return false;
347
+ }
348
+
349
+ _shouldAutoSubmitOnSelect() {
350
+ if (typeof this.surveyOptions.autoSubmitOnSelect === 'boolean') {
351
+ return this.surveyOptions.autoSubmitOnSelect;
352
+ }
353
+ if (this._isMultiPageSurvey()) {
354
+ return false;
355
+ }
356
+ if (this.surveyOptions.showSubmitButton === true) {
357
+ return false;
358
+ }
359
+ return this._isRatingSurveyType() && !this._shouldShowFeedbackInput();
360
+ }
361
+
362
+ _shouldShowActions() {
363
+ if (this._isMultiPageSurvey()) {
364
+ const page = this._getCurrentPage();
365
+ if (page && page.type === 'link') {
366
+ return false;
367
+ }
368
+ return true;
369
+ }
370
+ if (typeof this.surveyOptions.showSubmitButton === 'boolean') {
371
+ return this.surveyOptions.showSubmitButton;
372
+ }
373
+ return !this._shouldAutoSubmitOnSelect();
374
+ }
375
+
376
+ _isMultiPageSurvey() {
377
+ return (
378
+ Array.isArray(this.surveyOptions.pages) &&
379
+ this.surveyOptions.pages.length > 0
380
+ );
381
+ }
382
+
383
+ _getCurrentPage() {
384
+ if (!this._isMultiPageSurvey()) return null;
385
+ return this.surveyOptions.pages[this.surveyState.currentPageIndex] || null;
386
+ }
387
+
388
+ _isLastPage() {
389
+ if (!this._isMultiPageSurvey()) return true;
390
+ return (
391
+ this.surveyState.currentPageIndex >= this.surveyOptions.pages.length - 1
392
+ );
393
+ }
394
+
395
+ _getCurrentPageConfig() {
396
+ const page = this._getCurrentPage();
397
+ if (!page) {
398
+ return null;
399
+ }
400
+
401
+ return {
402
+ title: page.title || '',
403
+ description: page.description || '',
404
+ html: this._renderSurveyPage(page),
405
+ };
406
+ }
407
+
408
+ _renderSurveyPage(page) {
409
+ switch (page.type) {
410
+ case 'rating':
411
+ return this._renderRatingPage(page);
412
+ case 'multiple_choice':
413
+ return this._renderMultipleChoicePage(page);
414
+ case 'text':
415
+ return this._renderTextPage(page);
416
+ case 'link':
417
+ return this._renderLinkPage(page);
418
+ default:
419
+ return '';
420
+ }
421
+ }
422
+
423
+ _renderRatingPage(page) {
424
+ const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
425
+ const config = page.ratingConfig || page.rating_config || {};
426
+ const scale = Number(config.scale) || 5;
427
+ const topSurveyType = this.surveyOptions.surveyType;
428
+ const configType = config.survey_type;
429
+ let ratingType;
430
+ if (topSurveyType === 'ces') {
431
+ ratingType = 'ces';
432
+ } else if (topSurveyType === 'nps') {
433
+ ratingType = 'nps';
434
+ } else if (topSurveyType === 'csat' && !configType) {
435
+ ratingType = 'emoji';
436
+ } else {
437
+ ratingType = configType || topSurveyType || 'csat';
438
+ }
439
+ const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
440
+ const currentRating = pageAnswer.rating;
441
+ const defaultLowLabel =
442
+ ratingType === 'nps'
443
+ ? scale === 11
444
+ ? 'Not likely'
445
+ : 'Strongly Disagree'
446
+ : '';
447
+ const defaultHighLabel =
448
+ ratingType === 'nps'
449
+ ? scale === 11
450
+ ? 'Very likely'
451
+ : 'Strongly Agree'
452
+ : '';
453
+ const lowLabel =
454
+ config.low_label || this.surveyOptions.lowLabel || defaultLowLabel;
455
+ const highLabel =
456
+ config.high_label || this.surveyOptions.highLabel || defaultHighLabel;
457
+ const labels = this._renderScaleLabels(lowLabel, highLabel);
458
+
459
+ if (ratingType === 'nps') {
460
+ const npsScale = Number.isFinite(scale) && scale >= 2 ? scale : 5;
461
+ const start = npsScale === 11 ? 0 : 1;
462
+ const values = Array.from(
463
+ { length: npsScale },
464
+ (_, index) => start + index
465
+ );
466
+ const usesSegmentedScale = values.length <= 7;
467
+ const containerClass = usesSegmentedScale
468
+ ? 'feedback-survey-ces feedback-survey-rating-scale'
469
+ : 'feedback-survey-nps';
470
+ const buttonClass = usesSegmentedScale
471
+ ? 'feedback-survey-page-rating-btn feedback-survey-nps-btn feedback-survey-ces-btn feedback-survey-rating-scale-btn'
472
+ : 'feedback-survey-page-rating-btn feedback-survey-nps-btn';
473
+ return `
474
+ <div class="${containerClass}" data-page-id="${pageId}">
475
+ ${values
476
+ .map((n) => {
477
+ const selected = currentRating === n ? ' selected' : '';
478
+ return `<button class="${buttonClass}${selected}" data-page-id="${pageId}" data-score="${n}">${n}</button>`;
479
+ })
480
+ .join('')}
481
+ </div>
482
+ ${labels}
483
+ `;
484
+ }
485
+
486
+ if (ratingType === 'ces') {
487
+ const cesLabels = [
488
+ 'Very Difficult',
489
+ 'Difficult',
490
+ 'Neutral',
491
+ 'Easy',
492
+ 'Very Easy',
493
+ ];
494
+ return `
495
+ <div class="feedback-survey-ces-list" data-page-id="${pageId}">
496
+ ${cesLabels
497
+ .map((label, i) => {
498
+ const score = i + 1;
499
+ const selected = currentRating === score ? ' selected' : '';
500
+ return `<button class="feedback-survey-page-rating-btn feedback-survey-ces-list-btn${selected}" data-page-id="${pageId}" data-score="${score}">${label}</button>`;
501
+ })
502
+ .join('')}
503
+ </div>
504
+ `;
505
+ }
506
+
507
+ if (ratingType === 'emoji' && scale === 5) {
508
+ const emojis = [
509
+ '\uD83D\uDE1E',
510
+ '\uD83D\uDE15',
511
+ '\uD83D\uDE10',
512
+ '\uD83D\uDE42',
513
+ '\uD83D\uDE04',
514
+ ];
515
+ return `
516
+ <div class="feedback-survey-csat" data-page-id="${pageId}">
517
+ ${emojis
518
+ .map((emoji, i) => {
519
+ const score = i + 1;
520
+ const selected = currentRating === score ? ' selected' : '';
521
+ return `<button class="feedback-survey-page-rating-btn feedback-survey-csat-btn${selected}" data-page-id="${pageId}" data-score="${score}">${emoji}</button>`;
522
+ })
523
+ .join('')}
524
+ </div>
525
+ ${labels}
526
+ `;
527
+ }
528
+
529
+ return `
530
+ <div class="feedback-survey-rating-scale" data-page-id="${pageId}">
531
+ ${[...Array(scale).keys()]
532
+ .map((i) => {
533
+ const score = i + 1;
534
+ const selected = currentRating === score ? ' selected' : '';
535
+ return `<button class="feedback-survey-page-rating-btn feedback-survey-rating-scale-btn${selected}" data-page-id="${pageId}" data-score="${score}">${score}</button>`;
536
+ })
537
+ .join('')}
538
+ </div>
539
+ ${labels}
540
+ `;
541
+ }
542
+
543
+ _renderMultipleChoicePage(page) {
544
+ const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
545
+ const config =
546
+ page.multipleChoiceConfig || page.multiple_choice_config || {};
547
+ const options = Array.isArray(config.options) ? config.options : [];
548
+ const allowMultiple =
549
+ config.allow_multiple === true ||
550
+ config.multiple === true ||
551
+ config.allow_multiple_selection === true;
552
+ const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
553
+ const selectedValues = Array.isArray(pageAnswer.values)
554
+ ? pageAnswer.values
555
+ : pageAnswer.value
556
+ ? [pageAnswer.value]
557
+ : [];
558
+
559
+ return `
560
+ <div class="feedback-survey-multiple-choice" data-page-id="${pageId}" data-multiple="${allowMultiple}">
561
+ ${options
562
+ .map((option, index) => {
563
+ const value =
564
+ option.value || option.id || option.key || `option_${index}`;
565
+ const label = option.label || option.text || String(value);
566
+ const selected = selectedValues.includes(value) ? ' selected' : '';
567
+ return `<button class="feedback-survey-page-choice-btn${selected}" data-page-id="${pageId}" data-value="${value}">${label}</button>`;
568
+ })
569
+ .join('')}
570
+ </div>
571
+ `;
572
+ }
573
+
574
+ _renderTextPage(page) {
575
+ const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
576
+ const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
577
+ const value = pageAnswer.text || '';
578
+ const placeholder = page.placeholder || 'Type your answer...';
579
+
580
+ return `
581
+ <div class="feedback-survey-text-page" data-page-id="${pageId}">
582
+ <textarea class="feedback-survey-page-textarea" data-page-id="${pageId}" placeholder="${placeholder}">${value}</textarea>
583
+ </div>
584
+ `;
585
+ }
586
+
587
+ _renderLinkPage(page) {
588
+ const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
589
+ const config = page.link_config || page.linkConfig || {};
590
+ const buttonText = config.button_text || config.buttonText || 'Click here';
591
+ const linkText = config.link_text || config.linkText || '';
592
+ const redirectUrl = config.redirect_url || config.redirectUrl || '#';
593
+ const openIn = config.open_in || config.openIn || 'new_tab';
594
+ const target = openIn === 'new_tab' ? '_blank' : '_self';
595
+ const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
596
+ const clicked = pageAnswer.clicked === true;
597
+
598
+ const iconExternalLink = `<iconify-icon icon="ph:arrow-square-out-duotone" width="15" height="15"></iconify-icon>`;
599
+ return `
600
+ <div class="feedback-survey-link-page" data-page-id="${pageId}">
601
+ ${linkText ? `<p class="feedback-survey-link-text">${linkText}</p>` : ''}
602
+ <a class="feedback-survey-link-btn${clicked ? ' selected' : ''}"
603
+ href="${redirectUrl}"
604
+ target="${target}"
605
+ rel="noopener noreferrer"
606
+ data-page-id="${pageId}">
607
+ ${buttonText} ${iconExternalLink}
608
+ </a>
609
+ </div>
610
+ `;
611
+ }
612
+
613
+ _renderCustomQuestions() {
614
+ if (
615
+ !this.surveyOptions.customQuestions ||
616
+ this.surveyOptions.customQuestions.length === 0
617
+ ) {
618
+ return `
619
+ <div class="sdk-form-group">
620
+ <label class="sdk-label">What feature do you use most?</label>
621
+ <select class="feedback-survey-select" data-question="feature">
622
+ <option value="">Select a feature</option>
623
+ <option value="feedback">Feedback Collection</option>
624
+ <option value="surveys">Surveys</option>
625
+ <option value="analytics">Analytics</option>
626
+ <option value="integrations">Integrations</option>
627
+ </select>
628
+ </div>
629
+ <div class="sdk-form-group">
630
+ <label class="sdk-label">How often do you use it?</label>
631
+ <div class="feedback-survey-frequency">
632
+ ${['Daily', 'Weekly', 'Monthly', 'Rarely']
633
+ .map(
634
+ (freq) => `
635
+ <button class="feedback-survey-freq-btn" data-freq="${freq}">${freq}</button>
636
+ `
637
+ )
638
+ .join('')}
639
+ </div>
640
+ </div>
641
+ `;
642
+ }
643
+
644
+ return this.surveyOptions.customQuestions
645
+ .map(
646
+ (q, index) => `
647
+ <div class="sdk-form-group">
648
+ <label class="sdk-label">${q.label}</label>
649
+ ${this._renderQuestionInput(q, index)}
650
+ </div>
651
+ `
652
+ )
653
+ .join('');
654
+ }
655
+
656
+ _renderQuestionInput(question, index) {
657
+ switch (question.type) {
658
+ case 'select':
659
+ return `
660
+ <select class="feedback-survey-select" data-question="${question.id || index}">
661
+ <option value="">${question.placeholder || 'Select an option'}</option>
662
+ ${question.options.map((opt) => `<option value="${opt.value}">${opt.label}</option>`).join('')}
663
+ </select>
664
+ `;
665
+ case 'text':
666
+ return `
667
+ <input type="text" class="feedback-survey-input" data-question="${question.id || index}" placeholder="${question.placeholder || ''}">
668
+ `;
669
+ default:
670
+ return '';
671
+ }
672
+ }
673
+
674
+ _attachSurveyEvents() {
675
+ if (!this.surveyElement) return;
676
+
677
+ const closeBtn = this.surveyElement.querySelector('.feedback-survey-close');
678
+ closeBtn.addEventListener('click', () => this._handleDismiss());
679
+
680
+ const submitBtn = this.surveyElement.querySelector(
681
+ '.feedback-survey-submit'
682
+ );
683
+ if (submitBtn) {
684
+ submitBtn.addEventListener('click', () => this._handleSubmit());
685
+ }
686
+
687
+ const backBtn = this.surveyElement.querySelector('.feedback-survey-back');
688
+ if (backBtn) {
689
+ backBtn.addEventListener('click', () => this._handleBack());
690
+ }
691
+
692
+ const textarea = this.surveyElement.querySelector(
693
+ '.feedback-survey-textarea'
694
+ );
695
+ if (textarea) {
696
+ textarea.addEventListener('input', (e) => {
697
+ this.surveyState.feedback = e.target.value;
698
+ });
699
+ }
700
+
701
+ this._attachTypeSpecificEvents();
702
+
703
+ this._escapeHandler = (e) => {
704
+ if (e.key === 'Escape') {
705
+ this._handleDismiss();
706
+ }
707
+ };
708
+ document.addEventListener('keydown', this._escapeHandler);
709
+ }
710
+
711
+ _attachTypeSpecificEvents() {
712
+ if (this._isMultiPageSurvey()) {
713
+ this._attachCurrentPageEvents();
714
+ return;
715
+ }
716
+
717
+ const type = this.surveyOptions.surveyType;
718
+
719
+ if (type === 'nps') {
720
+ this.surveyElement
721
+ .querySelectorAll('.feedback-survey-nps-btn')
722
+ .forEach((btn) => {
723
+ btn.addEventListener('click', () =>
724
+ this._selectNPS(parseInt(btn.dataset.score))
725
+ );
726
+ });
727
+ }
728
+
729
+ if (type === 'csat') {
730
+ this.surveyElement
731
+ .querySelectorAll('.feedback-survey-csat-btn')
732
+ .forEach((btn) => {
733
+ btn.addEventListener('click', () =>
734
+ this._selectCSAT(parseInt(btn.dataset.score))
735
+ );
736
+ });
737
+ }
738
+
739
+ if (type === 'ces') {
740
+ this.surveyElement
741
+ .querySelectorAll('.feedback-survey-ces-btn')
742
+ .forEach((btn) => {
743
+ btn.addEventListener('click', () =>
744
+ this._selectCES(parseInt(btn.dataset.score))
745
+ );
746
+ });
747
+ }
748
+
749
+ if (type === 'custom') {
750
+ this.surveyElement
751
+ .querySelectorAll('.feedback-survey-freq-btn')
752
+ .forEach((btn) => {
753
+ btn.addEventListener('click', () =>
754
+ this._selectFrequency(btn.dataset.freq)
755
+ );
756
+ });
757
+
758
+ this.surveyElement
759
+ .querySelectorAll('.feedback-survey-select')
760
+ .forEach((select) => {
761
+ select.addEventListener('change', (e) => {
762
+ this.surveyState.customAnswers[select.dataset.question] =
763
+ e.target.value;
764
+ });
765
+ });
766
+
767
+ this.surveyElement
768
+ .querySelectorAll('.feedback-survey-input')
769
+ .forEach((input) => {
770
+ input.addEventListener('input', (e) => {
771
+ this.surveyState.customAnswers[input.dataset.question] =
772
+ e.target.value;
773
+ });
774
+ });
775
+ }
776
+ }
777
+
778
+ _attachCurrentPageEvents() {
779
+ const page = this._getCurrentPage();
780
+ if (!page || !this.surveyElement) return;
781
+ const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
782
+
783
+ if (page.type === 'rating') {
784
+ this.surveyElement
785
+ .querySelectorAll('.feedback-survey-page-rating-btn')
786
+ .forEach((btn) => {
787
+ btn.addEventListener('click', () => {
788
+ const score = parseInt(btn.dataset.score);
789
+ if (Number.isNaN(score)) return;
790
+ this._setPageAnswer(pageId, { rating: score });
791
+
792
+ this.surveyElement
793
+ .querySelectorAll('.feedback-survey-page-rating-btn')
794
+ .forEach((item) => item.classList.remove('selected'));
795
+ btn.classList.add('selected');
796
+ });
797
+ });
798
+ }
799
+
800
+ if (page.type === 'multiple_choice') {
801
+ const container = this.surveyElement.querySelector(
802
+ '.feedback-survey-multiple-choice'
803
+ );
804
+ const allowMultiple = container
805
+ ? container.dataset.multiple === 'true'
806
+ : false;
807
+
808
+ this.surveyElement
809
+ .querySelectorAll('.feedback-survey-page-choice-btn')
810
+ .forEach((btn) => {
811
+ btn.addEventListener('click', () => {
812
+ const value = btn.dataset.value;
813
+ if (!value) return;
814
+
815
+ const existing = this.surveyState.pageAnswers[pageId] || {};
816
+ const existingValues = Array.isArray(existing.values)
817
+ ? existing.values
818
+ : existing.value
819
+ ? [existing.value]
820
+ : [];
821
+
822
+ let nextValues = [];
823
+ if (allowMultiple) {
824
+ const hasValue = existingValues.includes(value);
825
+ nextValues = hasValue
826
+ ? existingValues.filter((v) => v !== value)
827
+ : [...existingValues, value];
828
+ } else {
829
+ nextValues = [value];
830
+ }
831
+
832
+ this._setPageAnswer(pageId, {
833
+ value: nextValues[0] || null,
834
+ values: nextValues,
835
+ });
836
+
837
+ if (!allowMultiple) {
838
+ this.surveyElement
839
+ .querySelectorAll('.feedback-survey-page-choice-btn')
840
+ .forEach((item) => item.classList.remove('selected'));
841
+ }
842
+ btn.classList.toggle('selected', nextValues.includes(value));
843
+ });
844
+ });
845
+ }
846
+
847
+ if (page.type === 'text') {
848
+ const textarea = this.surveyElement.querySelector(
849
+ '.feedback-survey-page-textarea'
850
+ );
851
+ if (textarea) {
852
+ textarea.addEventListener('input', (e) => {
853
+ this._setPageAnswer(pageId, { text: e.target.value });
854
+ });
855
+ }
856
+ }
857
+
858
+ if (page.type === 'link') {
859
+ this.surveyElement
860
+ .querySelectorAll('.feedback-survey-link-btn')
861
+ .forEach((btn) => {
862
+ btn.addEventListener('click', () => {
863
+ const pId = btn.dataset.pageId;
864
+ this._setPageAnswer(pId, { clicked: true });
865
+ btn.classList.add('selected');
866
+
867
+ const navigation = page.afterThisPage || page.after_this_page;
868
+ const goesTo = navigation ? navigation.default : null;
869
+ if (!goesTo || goesTo === 'end_survey') {
870
+ setTimeout(() => this._handleSubmit(), 400);
871
+ }
872
+ });
873
+ });
874
+ }
875
+ }
876
+
877
+ _selectNPS(score) {
878
+ this.surveyState.score = score;
879
+ this.surveyElement
880
+ .querySelectorAll('.feedback-survey-nps-btn')
881
+ .forEach((btn) => {
882
+ const btnScore = parseInt(btn.dataset.score);
883
+ if (btnScore === score) {
884
+ btn.classList.add('selected');
885
+ } else {
886
+ btn.classList.remove('selected');
887
+ }
888
+ });
889
+ this._maybeAutoSubmit();
890
+ }
891
+
892
+ _selectCSAT(score) {
893
+ this.surveyState.score = score;
894
+ this.surveyElement
895
+ .querySelectorAll('.feedback-survey-csat-btn')
896
+ .forEach((btn) => {
897
+ const btnScore = parseInt(btn.dataset.score);
898
+ if (btnScore === score) {
899
+ btn.classList.add('selected');
900
+ } else {
901
+ btn.classList.remove('selected');
902
+ }
903
+ });
904
+ this._maybeAutoSubmit();
905
+ }
906
+
907
+ _selectCES(score) {
908
+ this.surveyState.score = score;
909
+ this.surveyElement
910
+ .querySelectorAll('.feedback-survey-ces-btn')
911
+ .forEach((btn) => {
912
+ const btnScore = parseInt(btn.dataset.score);
913
+ if (btnScore === score) {
914
+ btn.classList.add('selected');
915
+ } else {
916
+ btn.classList.remove('selected');
917
+ }
918
+ });
919
+ this._maybeAutoSubmit();
920
+ }
921
+
922
+ _maybeAutoSubmit() {
923
+ if (!this._shouldAutoSubmitOnSelect()) {
924
+ return;
925
+ }
926
+ this._handleSubmit();
927
+ }
928
+
929
+ _selectFrequency(freq) {
930
+ this.surveyState.customAnswers.frequency = freq;
931
+ this.surveyElement
932
+ .querySelectorAll('.feedback-survey-freq-btn')
933
+ .forEach((btn) => {
934
+ if (btn.dataset.freq === freq) {
935
+ btn.classList.add('selected');
936
+ } else {
937
+ btn.classList.remove('selected');
938
+ }
939
+ });
940
+ }
941
+
942
+ _setSubmitLoading(loading) {
943
+ const btn = this.surveyElement?.querySelector('.feedback-survey-submit');
944
+ if (!btn) return;
945
+ btn.disabled = loading;
946
+ btn.innerHTML = loading
947
+ ? `<iconify-icon icon="ph:circle-notch" width="16" height="16" style="animation: spin 0.8s linear infinite;"></iconify-icon> Submitting...`
948
+ : `Submit <iconify-icon icon="ph:arrow-right" width="16" height="16"></iconify-icon>`;
949
+ }
950
+
951
+ async _handleSubmit() {
952
+ const type = this.surveyOptions.surveyType;
953
+ if (this.surveyState.isSubmitting) {
954
+ return;
955
+ }
956
+
957
+ if (this._isMultiPageSurvey()) {
958
+ if (!this._validateCurrentPage()) {
959
+ return;
960
+ }
961
+
962
+ const nextPageIndex = this._getNextPageIndex();
963
+ if (
964
+ nextPageIndex !== -1 &&
965
+ nextPageIndex !== this.surveyState.currentPageIndex
966
+ ) {
967
+ this.surveyState.currentPageIndex = nextPageIndex;
968
+ this._renderSurvey();
969
+ return;
970
+ }
971
+ }
972
+
973
+ if (
974
+ !this._isMultiPageSurvey() &&
975
+ (type === 'nps' || type === 'csat' || type === 'ces') &&
976
+ this.surveyState.score === null
977
+ ) {
978
+ this._showError('Please select a rating');
979
+ return;
980
+ }
981
+
982
+ this.surveyState.isSubmitting = true;
983
+ this._setSubmitLoading(true);
984
+
985
+ const respondent = this._getRespondentContext();
986
+ const normalizedPageAnswers = this._normalizePageAnswersForSubmit();
987
+ const mergedAnswers = {
988
+ ...this.surveyState.customAnswers,
989
+ ...(Object.keys(normalizedPageAnswers).length > 0 && {
990
+ page_answers: normalizedPageAnswers,
991
+ }),
992
+ };
993
+
994
+ const responseData = {
995
+ rating: this._getSubmissionRating(),
996
+ feedback: this.surveyState.feedback,
997
+ answers: mergedAnswers,
998
+ ...(respondent.respondentId && { respondentId: respondent.respondentId }),
999
+ ...(respondent.email && { email: respondent.email }),
1000
+ };
1001
+
1002
+ const response = {
1003
+ type: type,
1004
+ score: this._getSubmissionRating(),
1005
+ feedback: this.surveyState.feedback,
1006
+ customAnswers: mergedAnswers,
1007
+ pageAnswers: normalizedPageAnswers,
1008
+ timestamp: new Date().toISOString(),
1009
+ };
1010
+
1011
+ try {
1012
+ if (this.surveyOptions.onSubmit) {
1013
+ this.surveyOptions.onSubmit(response);
1014
+ }
1015
+
1016
+ const surveyId =
1017
+ this.surveyOptions.surveyId || `local_${type}_${Date.now()}`;
1018
+ await this.apiService.submitSurveyResponse(surveyId, responseData);
1019
+
1020
+ this.sdk.eventBus.emit('survey:submitted', { widget: this, response });
1021
+ this._closeSurvey();
1022
+ this._showSuccessNotification();
1023
+ } catch (error) {
1024
+ console.error('[SurveyWidget] Failed to submit survey:', error);
1025
+ this._showError('Something went wrong. Please try again.');
1026
+ } finally {
1027
+ this.surveyState.isSubmitting = false;
1028
+ this._setSubmitLoading(false);
1029
+ }
1030
+ }
1031
+
1032
+ _handleBack() {
1033
+ if (!this._isMultiPageSurvey()) return;
1034
+ if (this.surveyState.currentPageIndex <= 0) return;
1035
+ this.surveyState.currentPageIndex -= 1;
1036
+ this._renderSurvey();
1037
+ }
1038
+
1039
+ _setPageAnswer(pageId, data) {
1040
+ if (!pageId) return;
1041
+ this.surveyState.pageAnswers[pageId] = {
1042
+ ...(this.surveyState.pageAnswers[pageId] || {}),
1043
+ ...data,
1044
+ };
1045
+
1046
+ // Emit question answered event
1047
+ const page = this._getCurrentPage();
1048
+ this.sdk.eventBus.emit('survey:questionAnswered', {
1049
+ widget: this,
1050
+ pageId,
1051
+ pageType: page?.type,
1052
+ answer: this.surveyState.pageAnswers[pageId],
1053
+ });
1054
+ }
1055
+
1056
+ _validateCurrentPage() {
1057
+ const page = this._getCurrentPage();
1058
+ if (!page || page.required === false) return true;
1059
+ const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
1060
+
1061
+ const answer = this.surveyState.pageAnswers[pageId] || {};
1062
+
1063
+ if (page.type === 'rating') {
1064
+ if (typeof answer.rating !== 'number') {
1065
+ this._showError('Please select a rating');
1066
+ return false;
1067
+ }
1068
+ }
1069
+
1070
+ if (page.type === 'multiple_choice') {
1071
+ const hasSingle = Boolean(answer.value);
1072
+ const hasMany = Array.isArray(answer.values) && answer.values.length > 0;
1073
+ if (!hasSingle && !hasMany) {
1074
+ this._showError('Please select an option');
1075
+ return false;
1076
+ }
1077
+ }
1078
+
1079
+ if (page.type === 'text') {
1080
+ if (!answer.text || !String(answer.text).trim()) {
1081
+ this._showError('Please enter an answer');
1082
+ return false;
1083
+ }
1084
+ }
1085
+
1086
+ if (page.type === 'link') {
1087
+ if (page.required && !answer.clicked) {
1088
+ this._showError('Please click the link to continue');
1089
+ return false;
1090
+ }
1091
+ }
1092
+
1093
+ return true;
1094
+ }
1095
+
1096
+ _getNextPageIndex() {
1097
+ if (!this._isMultiPageSurvey()) {
1098
+ return -1;
1099
+ }
1100
+
1101
+ const page = this._getCurrentPage();
1102
+ const total = this.surveyOptions.pages.length;
1103
+ const currentIndex = this.surveyState.currentPageIndex;
1104
+ const fallbackNext = currentIndex + 1 < total ? currentIndex + 1 : -1;
1105
+ const navigation = page ? page.afterThisPage || page.after_this_page : null;
1106
+ if (!navigation) {
1107
+ return fallbackNext;
1108
+ }
1109
+
1110
+ const nextValue = navigation.default;
1111
+ if (!nextValue) {
1112
+ return fallbackNext;
1113
+ }
1114
+
1115
+ if (nextValue === 'end_survey') {
1116
+ return -1;
1117
+ }
1118
+ if (nextValue === 'next_page' || nextValue === 'next') {
1119
+ return fallbackNext;
1120
+ }
1121
+
1122
+ if (typeof nextValue === 'number') {
1123
+ return nextValue >= 0 && nextValue < total ? nextValue : fallbackNext;
1124
+ }
1125
+
1126
+ if (typeof nextValue === 'string') {
1127
+ const normalizedId = nextValue.replace(/^page:/, '');
1128
+ const pageIndex = this.surveyOptions.pages.findIndex(
1129
+ (item) => item.id === normalizedId
1130
+ );
1131
+ return pageIndex >= 0 ? pageIndex : fallbackNext;
1132
+ }
1133
+
1134
+ return fallbackNext;
1135
+ }
1136
+
1137
+ _getSubmissionRating() {
1138
+ if (typeof this.surveyState.score === 'number') {
1139
+ return this.surveyState.score;
1140
+ }
1141
+
1142
+ if (!this._isMultiPageSurvey()) {
1143
+ return null;
1144
+ }
1145
+
1146
+ for (const page of this.surveyOptions.pages) {
1147
+ const pageId =
1148
+ page.id || `page_${this.surveyOptions.pages.indexOf(page)}`;
1149
+ const answer = this.surveyState.pageAnswers[pageId];
1150
+ if (answer && typeof answer.rating === 'number') {
1151
+ return answer.rating;
1152
+ }
1153
+ }
1154
+
1155
+ return null;
1156
+ }
1157
+
1158
+ _normalizePageAnswersForSubmit() {
1159
+ const output = {};
1160
+ for (const [pageId, answer] of Object.entries(
1161
+ this.surveyState.pageAnswers
1162
+ )) {
1163
+ if (answer == null) continue;
1164
+ if (Array.isArray(answer.values) && answer.values.length > 0) {
1165
+ output[pageId] = answer.values;
1166
+ continue;
1167
+ }
1168
+ if (answer.value != null && answer.value !== '') {
1169
+ output[pageId] = answer.value;
1170
+ continue;
1171
+ }
1172
+ if (typeof answer.rating === 'number') {
1173
+ output[pageId] = answer.rating;
1174
+ continue;
1175
+ }
1176
+ if (typeof answer.text === 'string' && answer.text.trim()) {
1177
+ output[pageId] = answer.text.trim();
1178
+ }
1179
+ }
1180
+ return output;
1181
+ }
1182
+
1183
+ _getRespondentContext() {
1184
+ const sdkMetadata =
1185
+ typeof this.sdk.getMetadata === 'function'
1186
+ ? this.sdk.getMetadata() || {}
1187
+ : {};
1188
+ const apiMetadata =
1189
+ this.apiService && typeof this.apiService.getMetadata === 'function'
1190
+ ? this.apiService.getMetadata() || {}
1191
+ : {};
1192
+ const localMetadata = this.options.metadata || {};
1193
+
1194
+ return {
1195
+ respondentId:
1196
+ this.surveyOptions.respondentId ||
1197
+ localMetadata.user_id ||
1198
+ sdkMetadata.user_id ||
1199
+ apiMetadata.user_id ||
1200
+ null,
1201
+ email:
1202
+ this.surveyOptions.email ||
1203
+ localMetadata.email ||
1204
+ sdkMetadata.email ||
1205
+ apiMetadata.email ||
1206
+ null,
1207
+ };
1208
+ }
1209
+
1210
+ async _handleDismiss() {
1211
+ if (this.surveyOptions.surveyId) {
1212
+ try {
1213
+ await this.apiService.dismissSurvey(this.surveyOptions.surveyId);
1214
+ } catch (error) {
1215
+ console.error('[SurveyWidget] Failed to dismiss survey:', error);
1216
+ }
1217
+ }
1218
+
1219
+ if (this.surveyOptions.onDismiss) {
1220
+ this.surveyOptions.onDismiss();
1221
+ }
1222
+ this.sdk.eventBus.emit('survey:dismissed', { widget: this });
1223
+ this._closeSurvey();
1224
+ }
1225
+
1226
+ _showError(message) {
1227
+ const existing = this.surveyElement.querySelector('.feedback-survey-error');
1228
+ if (existing) existing.remove();
1229
+
1230
+ const error = document.createElement('div');
1231
+ error.className = 'feedback-survey-error';
1232
+ error.innerHTML = `<iconify-icon icon="ph:warning-duotone" width="15" height="15"></iconify-icon><span>${message}</span>`;
1233
+
1234
+ this.surveyElement.prepend(error);
1235
+
1236
+ setTimeout(() => error.remove(), 3000);
1237
+ }
1238
+
1239
+ _showSuccessNotification() {
1240
+ const notification = document.createElement('div');
1241
+ notification.className =
1242
+ 'product7-notification product7-notification-success';
1243
+ notification.innerHTML = `
1244
+ <div>
1245
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z" fill="currentColor"/></svg>
1246
+ <span>Thank you for your feedback!</span>
1247
+ </div>
1248
+ `;
1249
+ document.body.appendChild(notification);
1250
+
1251
+ setTimeout(() => {
1252
+ notification.style.opacity = '0';
1253
+ notification.style.transition = 'opacity 0.3s ease';
1254
+ setTimeout(() => notification.remove(), 300);
1255
+ }, 3000);
1256
+ }
1257
+
1258
+ _closeSurvey(resetState = true, immediate = false) {
1259
+ if (this._escapeHandler) {
1260
+ document.removeEventListener('keydown', this._escapeHandler);
1261
+ this._escapeHandler = null;
1262
+ }
1263
+
1264
+ const surveyEl = this.surveyElement;
1265
+ const backdropEl = this.backdropElement;
1266
+
1267
+ if (surveyEl) {
1268
+ surveyEl.style.opacity = '0';
1269
+ const pos = this.surveyOptions.position;
1270
+ surveyEl.style.transform =
1271
+ pos === 'center'
1272
+ ? 'translate(-50%, -50%) scale(0.95)'
1273
+ : pos === 'bottom'
1274
+ ? 'translateY(100%)'
1275
+ : 'translateY(10px)';
1276
+ if (immediate) {
1277
+ if (surveyEl.parentNode) {
1278
+ surveyEl.parentNode.removeChild(surveyEl);
1279
+ }
1280
+ } else {
1281
+ setTimeout(() => {
1282
+ if (surveyEl && surveyEl.parentNode) {
1283
+ surveyEl.parentNode.removeChild(surveyEl);
1284
+ }
1285
+ }, 300);
1286
+ }
1287
+ this.surveyElement = null;
1288
+ }
1289
+
1290
+ if (backdropEl) {
1291
+ backdropEl.style.opacity = '0';
1292
+ if (immediate) {
1293
+ if (backdropEl.parentNode) {
1294
+ backdropEl.parentNode.removeChild(backdropEl);
1295
+ }
1296
+ } else {
1297
+ setTimeout(() => {
1298
+ if (backdropEl && backdropEl.parentNode) {
1299
+ backdropEl.parentNode.removeChild(backdropEl);
1300
+ }
1301
+ }, 300);
1302
+ }
1303
+ this.backdropElement = null;
1304
+ }
1305
+
1306
+ if (resetState) {
1307
+ this.surveyState = {
1308
+ score: null,
1309
+ feedback: '',
1310
+ customAnswers: {},
1311
+ pageAnswers: {},
1312
+ currentPageIndex: 0,
1313
+ isSubmitting: false,
1314
+ isVisible: false,
1315
+ };
1316
+ }
1317
+
1318
+ this.sdk.eventBus.emit('survey:closed', { widget: this });
1319
+ }
1320
+
1321
+ destroy() {
1322
+ this._closeSurvey(true, true);
1323
+ super.destroy();
1324
+ }
1325
+ }