@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,548 @@
1
+ export class BaseWidget {
2
+ static STORAGE_KEY = 'feedback_submitted';
3
+ static DEFAULT_COOLDOWN_DAYS = 30;
4
+
5
+ constructor(options = {}) {
6
+ this.id = options.id;
7
+ this.sdk = options.sdk;
8
+ this.apiService = options.apiService;
9
+ this.type = options.type || 'base';
10
+
11
+ this.options = {
12
+ container: null,
13
+ position: this.sdk.config.position,
14
+ boardName: this.sdk.config.boardName,
15
+ displayMode: options.displayMode || 'panel',
16
+ size: options.size || 'medium',
17
+ primaryColor: options.primaryColor || '#155EEF',
18
+ backgroundColor: options.backgroundColor || '#ffffff',
19
+ textColor: options.textColor || '#1F2937',
20
+ autoShow: false,
21
+ showBackdrop: true,
22
+ customStyles: {},
23
+ suppressAfterSubmission: true,
24
+ suppressionDays: BaseWidget.DEFAULT_COOLDOWN_DAYS,
25
+ trigger: true,
26
+ ...options,
27
+ };
28
+
29
+ this.element = null;
30
+ this.panelElement = null;
31
+ this.backdropElement = null;
32
+ this.mounted = false;
33
+ this.destroyed = false;
34
+
35
+ this.state = {
36
+ isOpen: false,
37
+ isLoading: false,
38
+ isSubmitting: false,
39
+ title: '',
40
+ content: '',
41
+ email: '',
42
+ attachments: [],
43
+ errors: {},
44
+ };
45
+
46
+ this._bindMethods();
47
+ }
48
+
49
+ mount(container) {
50
+ if (this.mounted || this.destroyed) return this;
51
+
52
+ if (this.options.enabled === false) {
53
+ this.sdk.eventBus.emit('widget:suppressed', {
54
+ widget: this,
55
+ type: this.type,
56
+ reason: 'disabled',
57
+ });
58
+ return this;
59
+ }
60
+
61
+ if (this.options.suppressAfterSubmission && this._hasRecentlySubmitted()) {
62
+ this.sdk.eventBus.emit('widget:suppressed', {
63
+ widget: this,
64
+ reason: 'recently_submitted',
65
+ });
66
+ return this;
67
+ }
68
+
69
+ if (typeof container === 'string') {
70
+ container = document.querySelector(container);
71
+ }
72
+
73
+ if (!container) {
74
+ container = document.body;
75
+ }
76
+
77
+ this.container = container;
78
+ this.element = this._render();
79
+ this.container.appendChild(this.element);
80
+
81
+ this.mounted = true;
82
+ this._attachEvents();
83
+
84
+ // Bind to a custom trigger element if a selector or element was provided
85
+ if (this.options.trigger && this.options.trigger !== true) {
86
+ const triggerEl =
87
+ typeof this.options.trigger === 'string'
88
+ ? document.querySelector(this.options.trigger)
89
+ : this.options.trigger instanceof Element
90
+ ? this.options.trigger
91
+ : null;
92
+ if (triggerEl) {
93
+ triggerEl.addEventListener('click', () => this.open());
94
+ }
95
+ }
96
+
97
+ this.onMount();
98
+
99
+ if (this.options.autoShow) {
100
+ this.show();
101
+ }
102
+
103
+ this.sdk.eventBus.emit('widget:mounted', { widget: this });
104
+ return this;
105
+ }
106
+
107
+ show() {
108
+ if (this.element) {
109
+ this.element.style.display = 'block';
110
+ }
111
+ return this;
112
+ }
113
+
114
+ hide() {
115
+ if (this.element) {
116
+ this.element.style.display = 'none';
117
+ }
118
+ return this;
119
+ }
120
+
121
+ openPanel() {
122
+ this.state.isOpen = true;
123
+
124
+ this._renderPanel();
125
+ requestAnimationFrame(() => {
126
+ if (this.panelElement) {
127
+ this.panelElement.classList.add('open');
128
+ }
129
+ if (this.backdropElement) {
130
+ this.backdropElement.classList.add('show');
131
+ }
132
+ });
133
+ }
134
+
135
+ _showLoadingModal() {
136
+ this.state.isLoading = true;
137
+
138
+ if (this.options.showBackdrop) {
139
+ this.backdropElement = document.createElement('div');
140
+ this.backdropElement.className = 'sdk-modal-backdrop';
141
+ document.body.appendChild(this.backdropElement);
142
+ }
143
+
144
+ this.loadingElement = document.createElement('div');
145
+ this.loadingElement.className = 'feedback-loading-modal';
146
+ this.loadingElement.innerHTML = `
147
+ <div class="sdk-spinner"></div>
148
+ `;
149
+ document.body.appendChild(this.loadingElement);
150
+
151
+ requestAnimationFrame(() => {
152
+ if (this.backdropElement) {
153
+ this.backdropElement.classList.add('show');
154
+ }
155
+ this.loadingElement.classList.add('show');
156
+ });
157
+ }
158
+
159
+ _hideLoadingModal() {
160
+ this.state.isLoading = false;
161
+ if (this.loadingElement) {
162
+ this.loadingElement.remove();
163
+ this.loadingElement = null;
164
+ }
165
+ }
166
+
167
+ closePanel() {
168
+ if (this.panelElement) {
169
+ this.panelElement.classList.remove('open');
170
+ }
171
+ if (this.backdropElement) {
172
+ this.backdropElement.classList.remove('show');
173
+ }
174
+
175
+ this.state.isOpen = false;
176
+ if (this.panelElement && this.panelElement.parentNode) {
177
+ this.panelElement.parentNode.removeChild(this.panelElement);
178
+ this.panelElement = null;
179
+ }
180
+ if (this.backdropElement && this.backdropElement.parentNode) {
181
+ this.backdropElement.parentNode.removeChild(this.backdropElement);
182
+ this.backdropElement = null;
183
+ }
184
+ this._resetForm();
185
+ }
186
+
187
+ async submitFeedback() {
188
+ if (this.state.isSubmitting) return;
189
+
190
+ this._hideError();
191
+
192
+ try {
193
+ this.state.isSubmitting = true;
194
+ this._updateSubmitButton();
195
+
196
+ const payload = {
197
+ title: this.state.title || 'Feedback',
198
+ content: this.state.content,
199
+ email: this.state.email,
200
+ board_id: this.options.boardName,
201
+ attachments: this.state.attachments,
202
+ };
203
+
204
+ if (!this.state.title.trim()) {
205
+ this._showError('Please enter a title.');
206
+ return;
207
+ }
208
+
209
+ if (!this.state.content.trim()) {
210
+ this._showError('Please enter your feedback message.');
211
+ return;
212
+ }
213
+
214
+ const response = await this.apiService.submitFeedback(payload);
215
+
216
+ this._trackSubmission();
217
+
218
+ this._showSuccessInPanel();
219
+
220
+ this.sdk.eventBus.emit('feedback:submitted', {
221
+ widget: this,
222
+ feedback: response,
223
+ });
224
+ } catch (error) {
225
+ this._showError('Failed to submit feedback. Please try again.');
226
+ this.sdk.eventBus.emit('feedback:error', { widget: this, error });
227
+ } finally {
228
+ this.state.isSubmitting = false;
229
+ this._updateSubmitButton();
230
+ }
231
+ }
232
+
233
+ handleConfigUpdate(newConfig) {
234
+ this.options.theme = newConfig.theme;
235
+ if (this.element) {
236
+ this._updateTheme();
237
+ }
238
+ }
239
+
240
+ destroy() {
241
+ if (this.destroyed) return;
242
+
243
+ this.onDestroy();
244
+ this.closePanel();
245
+
246
+ if (this.element && this.element.parentNode) {
247
+ this.element.parentNode.removeChild(this.element);
248
+ }
249
+
250
+ this.destroyed = true;
251
+ this.mounted = false;
252
+ this.sdk.eventBus.emit('widget:destroyed', { widget: this });
253
+ }
254
+
255
+ onMount() {}
256
+ onDestroy() {}
257
+
258
+ _trackSubmission() {
259
+ try {
260
+ const workspace = this.sdk.config.workspace;
261
+ const storageKey = `${BaseWidget.STORAGE_KEY}_${workspace}`;
262
+ const data = {
263
+ submittedAt: Date.now(),
264
+ boardName: this.options.boardName,
265
+ };
266
+ localStorage.setItem(storageKey, JSON.stringify(data));
267
+ } catch (e) {
268
+ console.warn('Failed to track feedback submission:', e);
269
+ }
270
+ }
271
+
272
+ _hasRecentlySubmitted() {
273
+ const cooldownMs = this.options.suppressionDays * 24 * 60 * 60 * 1000;
274
+ const now = Date.now();
275
+
276
+ if (this.sdk.config.last_feedback_at) {
277
+ try {
278
+ const backendTimestamp = new Date(
279
+ this.sdk.config.last_feedback_at
280
+ ).getTime();
281
+ if (now - backendTimestamp < cooldownMs) {
282
+ return true;
283
+ }
284
+ } catch (e) {
285
+ // Invalid date format, continue to localStorage check
286
+ }
287
+ }
288
+
289
+ try {
290
+ const workspace = this.sdk.config.workspace;
291
+ const storageKey = `${BaseWidget.STORAGE_KEY}_${workspace}`;
292
+ const stored = localStorage.getItem(storageKey);
293
+
294
+ if (!stored) return false;
295
+
296
+ const data = JSON.parse(stored);
297
+ const submittedAt = data.submittedAt;
298
+
299
+ return now - submittedAt < cooldownMs;
300
+ } catch (e) {
301
+ return false;
302
+ }
303
+ }
304
+
305
+ clearSubmissionTracking() {
306
+ try {
307
+ const workspace = this.sdk.config.workspace;
308
+ const storageKey = `${BaseWidget.STORAGE_KEY}_${workspace}`;
309
+ localStorage.removeItem(storageKey);
310
+ } catch (e) {
311
+ console.warn('Failed to clear submission tracking:', e);
312
+ }
313
+ }
314
+
315
+ shouldShow() {
316
+ if (!this.options.suppressAfterSubmission) return true;
317
+ return !this._hasRecentlySubmitted();
318
+ }
319
+
320
+ _render() {
321
+ throw new Error('_render() must be implemented by concrete widget');
322
+ }
323
+
324
+ _attachEvents() {
325
+ // Override in concrete widgets
326
+ }
327
+
328
+ _bindMethods() {
329
+ this.openPanel = this.openPanel.bind(this);
330
+ this.closePanel = this.closePanel.bind(this);
331
+ this.submitFeedback = this.submitFeedback.bind(this);
332
+ }
333
+
334
+ _renderPanel() {
335
+ if (this.panelElement) return;
336
+
337
+ if (this.options.showBackdrop && !this.backdropElement) {
338
+ this.backdropElement = document.createElement('div');
339
+ this.backdropElement.className = 'sdk-modal-backdrop';
340
+ document.body.appendChild(this.backdropElement);
341
+
342
+ this.backdropElement.addEventListener('click', this.closePanel);
343
+ }
344
+
345
+ const modeClass =
346
+ this.options.displayMode === 'modal'
347
+ ? 'feedback-modal'
348
+ : 'feedback-panel';
349
+ const sizeClass = `size-${this.options.size}`;
350
+ this.panelElement = document.createElement('div');
351
+ const themeClass = `theme-${this.options.theme || 'light'}`;
352
+ const isLeftPosition =
353
+ modeClass === 'feedback-panel' &&
354
+ String(this.options.position).includes('left');
355
+ const directionClass = isLeftPosition ? 'panel-from-left' : '';
356
+ this.panelElement.className =
357
+ `${modeClass} ${sizeClass} ${themeClass} ${directionClass}`.trim();
358
+ this.panelElement.style.setProperty(
359
+ '--color-primary',
360
+ this.options.primaryColor
361
+ );
362
+ this.panelElement.style.setProperty(
363
+ '--feedback-panel-bg',
364
+ this.options.backgroundColor
365
+ );
366
+ this.panelElement.style.setProperty(
367
+ '--feedback-panel-text',
368
+ this.options.textColor
369
+ );
370
+ this.panelElement.innerHTML = this._getPanelHTML();
371
+
372
+ document.body.appendChild(this.panelElement);
373
+ this._attachPanelEvents();
374
+
375
+ const firstInput = this.panelElement.querySelector('input, textarea');
376
+ if (firstInput) {
377
+ setTimeout(() => firstInput.focus(), 350);
378
+ }
379
+ }
380
+
381
+ _getPanelHTML() {
382
+ return `
383
+ <div class="feedback-panel-content">
384
+ <div class="feedback-panel-header">
385
+ <h3>Send Feedback</h3>
386
+ <button class="sdk-close-btn" type="button" aria-label="Close">
387
+ <iconify-icon icon="ph:x-duotone" width="18" height="18"></iconify-icon>
388
+ </button>
389
+ </div>
390
+ <div class="feedback-panel-body">
391
+ <form class="feedback-form">
392
+ <div class="sdk-form-group">
393
+ <label class="sdk-label" for="feedback-title-${this.id}">Title <span class="sdk-required">*</span></label>
394
+ <input
395
+ type="text"
396
+ id="feedback-title-${this.id}"
397
+ name="title"
398
+ class="sdk-input"
399
+ placeholder="Brief description of your feedback"
400
+ value="${this.state.title}"
401
+ />
402
+ </div>
403
+ <div class="sdk-form-group">
404
+ <label class="sdk-label" for="feedback-content-${this.id}">Message <span class="sdk-required">*</span></label>
405
+ <textarea
406
+ id="feedback-content-${this.id}"
407
+ name="content"
408
+ class="sdk-textarea"
409
+ placeholder="Tell us what you think..."
410
+ required
411
+ >${this.state.content}</textarea>
412
+ </div>
413
+ <div class="feedback-error" role="alert"></div>
414
+ <div class="feedback-form-actions">
415
+ <button type="submit" class="sdk-btn-primary sdk-btn-block">
416
+ ${this.state.isSubmitting ? 'Sending...' : 'Send Feedback'}
417
+ </button>
418
+ </div>
419
+ </form>
420
+ </div>
421
+ </div>
422
+ `;
423
+ }
424
+
425
+ _attachPanelEvents() {
426
+ const panel = this.panelElement;
427
+
428
+ panel
429
+ .querySelector('.sdk-close-btn')
430
+ .addEventListener('click', this.closePanel);
431
+
432
+ const form = panel.querySelector('.feedback-form');
433
+ form.addEventListener('submit', (e) => {
434
+ e.preventDefault();
435
+ this.submitFeedback();
436
+ });
437
+
438
+ panel
439
+ .querySelector('input[name="title"]')
440
+ .addEventListener('input', (e) => {
441
+ this.state.title = e.target.value;
442
+ });
443
+
444
+ panel
445
+ .querySelector('textarea[name="content"]')
446
+ .addEventListener('input', (e) => {
447
+ this.state.content = e.target.value;
448
+ });
449
+
450
+ const handleEscape = (e) => {
451
+ if (e.key === 'Escape') {
452
+ this.closePanel();
453
+ document.removeEventListener('keydown', handleEscape);
454
+ }
455
+ };
456
+ document.addEventListener('keydown', handleEscape);
457
+ }
458
+
459
+ _updateSubmitButton() {
460
+ if (this.panelElement) {
461
+ const submitBtn = this.panelElement.querySelector('.sdk-btn-primary');
462
+ if (submitBtn) {
463
+ submitBtn.disabled = this.state.isSubmitting;
464
+ submitBtn.innerHTML = this.state.isSubmitting
465
+ ? `<iconify-icon icon="ph:circle-notch" width="16" height="16" style="animation: spin 0.8s linear infinite;"></iconify-icon> Sending...`
466
+ : `Send Feedback`;
467
+ }
468
+ }
469
+ }
470
+
471
+ _showError(message) {
472
+ if (this.panelElement) {
473
+ const errorElement = this.panelElement.querySelector('.feedback-error');
474
+ if (errorElement) {
475
+ errorElement.textContent = message;
476
+ errorElement.classList.add('show');
477
+ }
478
+ }
479
+ }
480
+
481
+ _hideError() {
482
+ if (this.panelElement) {
483
+ const errorElement = this.panelElement.querySelector('.feedback-error');
484
+ if (errorElement) {
485
+ errorElement.classList.remove('show');
486
+ }
487
+ }
488
+ }
489
+
490
+ _showSuccessInPanel() {
491
+ if (!this.panelElement) return;
492
+
493
+ const content = this.panelElement.querySelector('.feedback-panel-content');
494
+ if (!content) return;
495
+
496
+ content.innerHTML = `
497
+ <div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; padding: 40px 24px; text-align: center;">
498
+ <div style="width: 56px; height: 56px; border-radius: 50%; background: #037F0C; display: flex; align-items: center; justify-content: center; margin-bottom: 20px;">
499
+ <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="none" stroke="#ffffff" stroke-width="24" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 256 256">
500
+ <polyline points="216,72 96,192 40,136"/>
501
+ </svg>
502
+ </div>
503
+ <h3 style="margin: 0 0 8px; font-size: 1.25rem; font-weight: 600; color: var(--feedback-panel-text, #1d1d1f);">Thank you!</h3>
504
+ <p style="margin: 0; font-size: 0.875rem; color: #6b7280; line-height: 1.5;">Your feedback has been submitted successfully.</p>
505
+ </div>
506
+ `;
507
+
508
+ setTimeout(() => this.closePanel(), 2000);
509
+ }
510
+
511
+ _resetForm() {
512
+ this.state.title = '';
513
+ this.state.content = '';
514
+ this.state.email = '';
515
+ this.state.errors = {};
516
+ }
517
+
518
+ _updateTheme() {
519
+ // No longer needed - single theme
520
+ }
521
+
522
+ open() {
523
+ this.openPanel();
524
+ return this;
525
+ }
526
+
527
+ close() {
528
+ this.closePanel();
529
+ return this;
530
+ }
531
+
532
+ toggle() {
533
+ if (this.state.isOpen) {
534
+ this.closePanel();
535
+ } else {
536
+ this.openPanel();
537
+ }
538
+ return this;
539
+ }
540
+
541
+ openModal() {
542
+ this.openPanel();
543
+ }
544
+
545
+ closeModal() {
546
+ this.closePanel();
547
+ }
548
+ }
@@ -0,0 +1,104 @@
1
+ import { BaseWidget } from './BaseWidget.js';
2
+
3
+ export class ButtonWidget extends BaseWidget {
4
+ constructor(options) {
5
+ super({ ...options, type: 'button' });
6
+ this.isMinimized = false;
7
+ this._hiddenForOpenPanel = false;
8
+ }
9
+
10
+ _hasTrigger() {
11
+ return this.options.trigger === true || this.options.trigger === undefined;
12
+ }
13
+
14
+ _render() {
15
+ if (!this._hasTrigger()) {
16
+ // No built-in button — widget is opened programmatically or via custom trigger
17
+ const placeholder = document.createElement('div');
18
+ placeholder.style.display = 'none';
19
+ return placeholder;
20
+ }
21
+
22
+ const buttonText =
23
+ this.options.buttonText ||
24
+ this.options.triggerText ||
25
+ this.options.label ||
26
+ this.options.text ||
27
+ 'Feedback';
28
+
29
+ const button = document.createElement('div');
30
+ button.className = `feedback-widget-button position-${this.options.position}`;
31
+ button.innerHTML = `
32
+ <button class="feedback-trigger-btn" type="button">
33
+ <svg class="feedback-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256">
34
+ <path d="M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48Zm-8,144H40V74.19l82.59,75.71a8,8,0,0,0,10.82,0L216,74.19V192Z"/>
35
+ </svg>
36
+ <span class="feedback-text">${buttonText}</span>
37
+ </button>
38
+ `;
39
+
40
+ if (this.options.customStyles) {
41
+ Object.assign(button.style, this.options.customStyles);
42
+ }
43
+
44
+ return button;
45
+ }
46
+
47
+ _attachEvents() {
48
+ if (!this._hasTrigger()) return;
49
+
50
+ const button = this.element.querySelector('.feedback-trigger-btn');
51
+
52
+ button.addEventListener('click', () => {
53
+ this.openPanel();
54
+ });
55
+
56
+ button.addEventListener('pointerup', (e) => {
57
+ if (e.pointerType === 'mouse') return;
58
+ this.openPanel();
59
+ });
60
+ }
61
+
62
+ openPanel() {
63
+ if (this._hasTrigger() && !this.state.isOpen) {
64
+ this._hiddenForOpenPanel = true;
65
+ this.hide();
66
+ }
67
+ super.openPanel();
68
+ }
69
+
70
+ closePanel() {
71
+ const shouldRestoreButton = this._hiddenForOpenPanel;
72
+ super.closePanel();
73
+
74
+ if (shouldRestoreButton) {
75
+ setTimeout(() => {
76
+ if (!this.destroyed && !this.options._noTriggerButton) {
77
+ this.show();
78
+ }
79
+ this._hiddenForOpenPanel = false;
80
+ }, 320);
81
+ }
82
+ }
83
+
84
+ mount(container) {
85
+ super.mount(container);
86
+ }
87
+
88
+ updateText(text) {
89
+ const textEl = this.element?.querySelector('.feedback-text');
90
+ if (textEl) {
91
+ textEl.textContent = text;
92
+ }
93
+ }
94
+
95
+ updatePosition(position) {
96
+ this.options.position = position;
97
+ if (this.element) {
98
+ this.element.className = this.element.className.replace(
99
+ /position-\w+/,
100
+ `position-${position}`
101
+ );
102
+ }
103
+ }
104
+ }