@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,45 @@
1
+ import { BaseWidget } from './BaseWidget.js';
2
+
3
+ export class TabWidget extends BaseWidget {
4
+ constructor(options) {
5
+ super({ ...options, type: 'tab' });
6
+ }
7
+
8
+ _render() {
9
+ const tab = document.createElement('div');
10
+ tab.className = `feedback-widget feedback-widget-tab position-${this.options.position}`;
11
+ tab.innerHTML = `
12
+ <div class="feedback-tab-trigger">
13
+ <span class="feedback-tab-text">Feedback</span>
14
+ </div>
15
+ `;
16
+
17
+ if (this.options.customStyles) {
18
+ Object.assign(tab.style, this.options.customStyles);
19
+ }
20
+
21
+ return tab;
22
+ }
23
+
24
+ _attachEvents() {
25
+ const tab = this.element.querySelector('.feedback-tab-trigger');
26
+ tab.addEventListener('click', this.openModal);
27
+ }
28
+
29
+ updateText(text) {
30
+ const textElement = this.element?.querySelector('.feedback-tab-text');
31
+ if (textElement) {
32
+ textElement.textContent = text;
33
+ }
34
+ }
35
+
36
+ updatePosition(position) {
37
+ this.options.position = position;
38
+ if (this.element) {
39
+ this.element.className = this.element.className.replace(
40
+ /position-\w+/,
41
+ `position-${position}`
42
+ );
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,70 @@
1
+ import { SDKError } from '../utils/errors.js';
2
+ import { ButtonWidget } from './ButtonWidget.js';
3
+ import { ChangelogWidget } from './ChangelogWidget.js';
4
+ import { InlineWidget } from './InlineWidget.js';
5
+ import { MessengerWidget } from './MessengerWidget.js';
6
+ import { SurveyWidget } from './SurveyWidget.js';
7
+ import { TabWidget } from './TabWidget.js';
8
+
9
+ export class WidgetFactory {
10
+ static widgets = new Map([
11
+ ['button', ButtonWidget],
12
+ ['tab', TabWidget],
13
+ ['inline', InlineWidget],
14
+ ['survey', SurveyWidget],
15
+ ['messenger', MessengerWidget],
16
+ ['changelog', ChangelogWidget],
17
+ ]);
18
+
19
+ static register(type, WidgetClass) {
20
+ if (typeof type !== 'string' || !type.trim()) {
21
+ throw new SDKError('Widget type must be a non-empty string');
22
+ }
23
+
24
+ if (typeof WidgetClass !== 'function') {
25
+ throw new SDKError('Widget class must be a constructor function');
26
+ }
27
+
28
+ this.widgets.set(type, WidgetClass);
29
+ }
30
+
31
+ static create(type, options = {}) {
32
+ const WidgetClass = this.widgets.get(type);
33
+
34
+ if (!WidgetClass) {
35
+ const availableTypes = Array.from(this.widgets.keys()).join(', ');
36
+ throw new SDKError(
37
+ `Unknown widget type: ${type}. Available types: ${availableTypes}`
38
+ );
39
+ }
40
+
41
+ try {
42
+ return new WidgetClass(options);
43
+ } catch (error) {
44
+ throw new SDKError(
45
+ `Failed to create widget of type '${type}': ${error.message}`,
46
+ error
47
+ );
48
+ }
49
+ }
50
+
51
+ static getAvailableTypes() {
52
+ return Array.from(this.widgets.keys());
53
+ }
54
+
55
+ static isTypeRegistered(type) {
56
+ return this.widgets.has(type);
57
+ }
58
+
59
+ static unregister(type) {
60
+ return this.widgets.delete(type);
61
+ }
62
+
63
+ static clear() {
64
+ this.widgets.clear();
65
+ }
66
+
67
+ static getWidgetClass(type) {
68
+ return this.widgets.get(type);
69
+ }
70
+ }
@@ -0,0 +1,323 @@
1
+ export class MessengerState {
2
+ constructor(options = {}) {
3
+ this.currentView = 'home';
4
+ this.isOpen = false;
5
+ this.unreadCount = 0;
6
+ this.activeConversationId = null;
7
+
8
+ this.conversations = [];
9
+ this.messages = {};
10
+
11
+ this.helpArticles = [];
12
+ this.helpSearchQuery = '';
13
+
14
+ this.homeChangelogItems = [];
15
+ this.changelogItems = [];
16
+
17
+ this.teamName = options.teamName || 'Support';
18
+ this.teamAvatars = options.teamAvatars || [];
19
+ this.greetingMessage = options.greetingMessage || 'Hi there 👋';
20
+ this.welcomeMessage = options.welcomeMessage || 'How can we help?';
21
+
22
+ this.metadata = options.metadata || null;
23
+ this.isIdentified = false;
24
+ this.pendingMessage = null;
25
+
26
+ this.enableHelp = options.enableHelp !== false;
27
+ this.enableChangelog = options.enableChangelog !== false;
28
+
29
+ this.agentsOnline = false;
30
+ this.onlineCount = 0;
31
+ this.responseTime = 'Usually replies within a few minutes';
32
+
33
+ this.typingUsers = {};
34
+
35
+ this.isLoading = false;
36
+ this.isLoadingMessages = false;
37
+
38
+ this.urls = options.urls || {
39
+ feedback: null,
40
+ changelog: null,
41
+ help: null,
42
+ roadmap: null,
43
+ };
44
+
45
+ this._listeners = new Set();
46
+ }
47
+
48
+ subscribe(callback) {
49
+ this._listeners.add(callback);
50
+ return () => this._listeners.delete(callback);
51
+ }
52
+
53
+ _notify(changeType, data) {
54
+ this._listeners.forEach((cb) => cb(changeType, data, this));
55
+ }
56
+
57
+ setView(view) {
58
+ const previousView = this.currentView;
59
+ this.currentView = view;
60
+ this._notify('viewChange', { previousView, currentView: view });
61
+ }
62
+
63
+ setOpen(isOpen) {
64
+ this.isOpen = isOpen;
65
+ this._notify('openChange', { isOpen });
66
+ }
67
+
68
+ setActiveConversation(conversationId) {
69
+ const previousConversationId = this.activeConversationId;
70
+ this.activeConversationId = conversationId;
71
+ this._notify('conversationChange', {
72
+ conversationId,
73
+ previousConversationId,
74
+ });
75
+ }
76
+
77
+ setIdentified(isIdentified, metadata = null) {
78
+ this.isIdentified = isIdentified;
79
+ if (metadata) {
80
+ this.metadata = { ...this.metadata, ...metadata };
81
+ }
82
+ this._notify('identificationChange', { isIdentified, metadata });
83
+ }
84
+
85
+ setConversations(conversations) {
86
+ this.conversations = conversations;
87
+ this._updateUnreadCount();
88
+ this._notify('conversationsUpdate', { conversations });
89
+ }
90
+
91
+ addConversation(conversation) {
92
+ this.conversations.unshift(conversation);
93
+ this._updateUnreadCount();
94
+ this._notify('conversationAdded', { conversation });
95
+ }
96
+
97
+ setMessages(conversationId, messages) {
98
+ this.messages[conversationId] = messages;
99
+ this._notify('messagesUpdate', { conversationId, messages });
100
+ }
101
+
102
+ _getMessageAttachmentsSignature(message) {
103
+ if (
104
+ !Array.isArray(message?.attachments) ||
105
+ message.attachments.length === 0
106
+ ) {
107
+ return '';
108
+ }
109
+
110
+ return message.attachments
111
+ .map((att) => `${att?.type || ''}:${att?.name || ''}`)
112
+ .join('|');
113
+ }
114
+
115
+ _findOptimisticMatchIndex(conversationId, incomingMessage, matchWindowMs) {
116
+ const messages = this.messages[conversationId] || [];
117
+ const incomingTimestamp = Date.parse(incomingMessage.timestamp);
118
+ const incomingSignature =
119
+ this._getMessageAttachmentsSignature(incomingMessage);
120
+
121
+ for (let i = messages.length - 1; i >= 0; i--) {
122
+ const candidate = messages[i];
123
+ if (!candidate?.isOptimistic || !candidate?.isOwn) {
124
+ continue;
125
+ }
126
+ if ((candidate.content || '') !== (incomingMessage.content || '')) {
127
+ continue;
128
+ }
129
+
130
+ const candidateSignature =
131
+ this._getMessageAttachmentsSignature(candidate);
132
+ if (candidateSignature !== incomingSignature) {
133
+ continue;
134
+ }
135
+
136
+ const candidateTimestamp = Date.parse(candidate.timestamp);
137
+ if (
138
+ Number.isNaN(incomingTimestamp) ||
139
+ Number.isNaN(candidateTimestamp) ||
140
+ Math.abs(incomingTimestamp - candidateTimestamp) <= matchWindowMs
141
+ ) {
142
+ return i;
143
+ }
144
+ }
145
+
146
+ return -1;
147
+ }
148
+
149
+ _updateConversationFromMessage(conversationId, message, countUnread) {
150
+ const conv = this.conversations.find((c) => c.id === conversationId);
151
+ if (!conv) {
152
+ return;
153
+ }
154
+
155
+ conv.lastMessage = message.content;
156
+ conv.lastMessageTime = message.timestamp;
157
+
158
+ if (countUnread && !message.isOwn) {
159
+ conv.unread = (conv.unread || 0) + 1;
160
+ this._updateUnreadCount();
161
+ }
162
+ }
163
+
164
+ upsertMessage(conversationId, message, options = {}) {
165
+ if (!this.messages[conversationId]) {
166
+ this.messages[conversationId] = [];
167
+ }
168
+
169
+ const reconcileOwnOptimistic = options.reconcileOwnOptimistic === true;
170
+ const optimisticMatchWindowMs = options.optimisticMatchWindowMs || 30000;
171
+ const messages = this.messages[conversationId];
172
+ const existingIndex =
173
+ message?.id != null
174
+ ? messages.findIndex((msg) => msg?.id === message.id)
175
+ : -1;
176
+
177
+ if (existingIndex !== -1) {
178
+ messages[existingIndex] = {
179
+ ...messages[existingIndex],
180
+ ...message,
181
+ isOptimistic: false,
182
+ };
183
+ this._updateConversationFromMessage(
184
+ conversationId,
185
+ messages[existingIndex],
186
+ false
187
+ );
188
+ this._notify('messagesUpdate', {
189
+ conversationId,
190
+ messages: [...messages],
191
+ });
192
+ return messages[existingIndex];
193
+ }
194
+
195
+ if (reconcileOwnOptimistic && message?.isOwn) {
196
+ const optimisticIndex = this._findOptimisticMatchIndex(
197
+ conversationId,
198
+ message,
199
+ optimisticMatchWindowMs
200
+ );
201
+ if (optimisticIndex !== -1) {
202
+ messages[optimisticIndex] = {
203
+ ...messages[optimisticIndex],
204
+ ...message,
205
+ isOptimistic: false,
206
+ };
207
+ this._updateConversationFromMessage(
208
+ conversationId,
209
+ messages[optimisticIndex],
210
+ false
211
+ );
212
+ this._notify('messagesUpdate', {
213
+ conversationId,
214
+ messages: [...messages],
215
+ });
216
+ return messages[optimisticIndex];
217
+ }
218
+ }
219
+
220
+ const storedMessage = {
221
+ ...message,
222
+ isOptimistic: Boolean(message?.isOptimistic),
223
+ };
224
+ messages.push(storedMessage);
225
+ this._updateConversationFromMessage(conversationId, storedMessage, true);
226
+ this._notify('messageAdded', { conversationId, message: storedMessage });
227
+ return storedMessage;
228
+ }
229
+
230
+ addMessage(conversationId, message) {
231
+ return this.upsertMessage(conversationId, message);
232
+ }
233
+
234
+ updateConversation(conversationId, updates) {
235
+ const conv = this.conversations.find((c) => c.id === conversationId);
236
+ if (!conv) {
237
+ return null;
238
+ }
239
+
240
+ Object.assign(conv, updates);
241
+ this._notify('conversationUpdated', { conversationId, conversation: conv });
242
+ return conv;
243
+ }
244
+
245
+ markAsRead(conversationId) {
246
+ const conv = this.conversations.find((c) => c.id === conversationId);
247
+ if (conv && conv.unread > 0) {
248
+ conv.unread = 0;
249
+ this._updateUnreadCount();
250
+ this._notify('conversationRead', { conversationId });
251
+ }
252
+ }
253
+
254
+ _updateUnreadCount() {
255
+ this.unreadCount = this.conversations.reduce(
256
+ (sum, c) => sum + (c.unread || 0),
257
+ 0
258
+ );
259
+ this._notify('unreadCountChange', { count: this.unreadCount });
260
+ }
261
+
262
+ setHelpArticles(articles) {
263
+ this.helpArticles = articles;
264
+ this._notify('helpArticlesUpdate', { articles });
265
+ }
266
+
267
+ setHelpSearchQuery(query) {
268
+ this.helpSearchQuery = query;
269
+ this._notify('helpSearchChange', { query });
270
+ }
271
+
272
+ setHomeChangelogItems(items) {
273
+ this.homeChangelogItems = items;
274
+ this._notify('homeChangelogUpdate', { items });
275
+ }
276
+
277
+ setChangelogItems(items) {
278
+ this.changelogItems = items;
279
+ this._notify('changelogUpdate', { items });
280
+ }
281
+
282
+ getActiveConversation() {
283
+ return this.conversations.find((c) => c.id === this.activeConversationId);
284
+ }
285
+
286
+ getActiveMessages() {
287
+ return this.messages[this.activeConversationId] || [];
288
+ }
289
+
290
+ setTeamAvatarsFromAgents(agents) {
291
+ if (!agents || agents.length === 0) return;
292
+
293
+ this.teamAvatars = agents.map((agent) => {
294
+ if (agent.picture) return agent.picture;
295
+ return agent.full_name || '?';
296
+ });
297
+ this._notify('teamAvatarsUpdate', { teamAvatars: this.teamAvatars });
298
+ }
299
+
300
+ getFilteredHelpArticles() {
301
+ if (!this.helpSearchQuery) {
302
+ return this.helpArticles;
303
+ }
304
+ const query = this.helpSearchQuery.toLowerCase();
305
+ return this.helpArticles.filter(
306
+ (article) =>
307
+ article.title.toLowerCase().includes(query) ||
308
+ (article.description &&
309
+ article.description.toLowerCase().includes(query))
310
+ );
311
+ }
312
+
313
+ reset() {
314
+ const previousView = this.currentView;
315
+ this.currentView = 'home';
316
+ this.activeConversationId = null;
317
+ this.helpSearchQuery = '';
318
+ if (previousView !== 'home') {
319
+ this._notify('viewChange', { previousView, currentView: 'home' });
320
+ }
321
+ this._notify('reset', {});
322
+ }
323
+ }
@@ -0,0 +1,124 @@
1
+ export class MessengerLauncher {
2
+ constructor(state, options = {}) {
3
+ this.state = state;
4
+ this.options = {
5
+ position: options.position || 'right',
6
+ primaryColor: options.primaryColor || '#155EEF',
7
+ ...options,
8
+ };
9
+ this.element = null;
10
+ this._unsubscribe = null;
11
+ }
12
+
13
+ render() {
14
+ this.element = document.createElement('div');
15
+ this.element.className = `messenger-launcher messenger-launcher-${this.options.position}`;
16
+
17
+ this._updateContent();
18
+ this._attachEvents();
19
+
20
+ this._unsubscribe = this.state.subscribe((type, data) => {
21
+ if (type === 'openChange') {
22
+ this._updateIcon();
23
+ }
24
+ if (type === 'unreadCountChange') {
25
+ this._updateBadge();
26
+ }
27
+ });
28
+
29
+ return this.element;
30
+ }
31
+
32
+ _updateContent() {
33
+ const badgeHtml =
34
+ this.state.unreadCount > 0
35
+ ? `<span class="messenger-launcher-badge">${this.state.unreadCount > 9 ? '9+' : this.state.unreadCount}</span>`
36
+ : '';
37
+
38
+ this.element.innerHTML = `
39
+ <button class="messenger-launcher-btn" aria-label="Open messenger">
40
+ <span class="messenger-launcher-icon messenger-launcher-icon-chat">
41
+ <iconify-icon icon="ph:chats-circle-duotone" width="24" height="24"></iconify-icon>
42
+ </span>
43
+ <span class="messenger-launcher-icon messenger-launcher-icon-close" style="display: none;">
44
+ <iconify-icon icon="ph:caret-down-bold" width="22" height="22"></iconify-icon>
45
+ </span>
46
+ ${badgeHtml}
47
+ </button>
48
+ `;
49
+
50
+ const btn = this.element.querySelector('.messenger-launcher-btn');
51
+ if (btn && this.options.primaryColor) {
52
+ btn.style.setProperty(
53
+ 'background',
54
+ this.options.primaryColor,
55
+ 'important'
56
+ );
57
+ }
58
+ }
59
+
60
+ _attachEvents() {
61
+ this.element
62
+ .querySelector('.messenger-launcher-btn')
63
+ .addEventListener('click', () => {
64
+ this.state.setOpen(!this.state.isOpen);
65
+ });
66
+ }
67
+
68
+ _updateIcon() {
69
+ const chatIcon = this.element.querySelector(
70
+ '.messenger-launcher-icon-chat'
71
+ );
72
+ const closeIcon = this.element.querySelector(
73
+ '.messenger-launcher-icon-close'
74
+ );
75
+
76
+ if (this.state.isOpen) {
77
+ chatIcon.style.display = 'none';
78
+ closeIcon.style.display = 'flex';
79
+ this.element.classList.add('messenger-launcher-open');
80
+ } else {
81
+ chatIcon.style.display = 'flex';
82
+ closeIcon.style.display = 'none';
83
+ this.element.classList.remove('messenger-launcher-open');
84
+ }
85
+ }
86
+
87
+ _updateBadge() {
88
+ const existingBadge = this.element.querySelector(
89
+ '.messenger-launcher-badge'
90
+ );
91
+ if (existingBadge) {
92
+ existingBadge.remove();
93
+ }
94
+
95
+ if (this.state.unreadCount > 0 && !this.state.isOpen) {
96
+ const badge = document.createElement('span');
97
+ badge.className = 'messenger-launcher-badge';
98
+ badge.textContent =
99
+ this.state.unreadCount > 9 ? '9+' : this.state.unreadCount;
100
+ this.element.querySelector('.messenger-launcher-btn').appendChild(badge);
101
+ }
102
+ }
103
+
104
+ show() {
105
+ if (this.element) {
106
+ this.element.style.display = 'block';
107
+ }
108
+ }
109
+
110
+ hide() {
111
+ if (this.element) {
112
+ this.element.style.display = 'none';
113
+ }
114
+ }
115
+
116
+ destroy() {
117
+ if (this._unsubscribe) {
118
+ this._unsubscribe();
119
+ }
120
+ if (this.element && this.element.parentNode) {
121
+ this.element.parentNode.removeChild(this.element);
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,111 @@
1
+ import { NavigationTabs } from './NavigationTabs.js';
2
+
3
+ export class MessengerPanel {
4
+ constructor(state, options = {}) {
5
+ this.state = state;
6
+ this.options = {
7
+ position: options.position || 'right',
8
+ ...options,
9
+ };
10
+ this.element = null;
11
+ this.navigationTabs = null;
12
+ this.currentViewComponent = null;
13
+ this.viewRegistry = {};
14
+ this._unsubscribe = null;
15
+ }
16
+
17
+ registerView(name, ViewClass) {
18
+ this.viewRegistry[name] = ViewClass;
19
+ }
20
+
21
+ render() {
22
+ this.element = document.createElement('div');
23
+ this.element.className = `messenger-panel messenger-panel-${this.options.position}`;
24
+
25
+ this.element.innerHTML = `
26
+ <div class="messenger-panel-content">
27
+ <div class="messenger-panel-views"></div>
28
+ <div class="messenger-panel-nav"></div>
29
+ </div>
30
+ `;
31
+
32
+ this.navigationTabs = new NavigationTabs(this.state, this.options);
33
+ const navContainer = this.element.querySelector('.messenger-panel-nav');
34
+ navContainer.appendChild(this.navigationTabs.render());
35
+
36
+ this._renderCurrentView();
37
+
38
+ this._unsubscribe = this.state.subscribe((type) => {
39
+ if (type === 'viewChange') {
40
+ this._renderCurrentView();
41
+ }
42
+ });
43
+
44
+ return this.element;
45
+ }
46
+
47
+ _renderCurrentView() {
48
+ const viewsContainer = this.element.querySelector('.messenger-panel-views');
49
+ const navContainer = this.element.querySelector('.messenger-panel-nav');
50
+
51
+ if (this.currentViewComponent && this.currentViewComponent.destroy) {
52
+ this.currentViewComponent.destroy();
53
+ }
54
+
55
+ viewsContainer.innerHTML = '';
56
+
57
+ const ViewClass = this.viewRegistry[this.state.currentView];
58
+ if (ViewClass) {
59
+ this.currentViewComponent = new ViewClass(this.state, this.options);
60
+ viewsContainer.appendChild(this.currentViewComponent.render());
61
+ } else {
62
+ viewsContainer.innerHTML = `<div class="messenger-empty-state">
63
+ <p>View not found: ${this.state.currentView}</p>
64
+ </div>`;
65
+ }
66
+
67
+ // Hide nav in chat and prechat views
68
+ if (navContainer) {
69
+ const hideNav =
70
+ this.state.currentView === 'chat' ||
71
+ this.state.currentView === 'prechat';
72
+ navContainer.style.display = hideNav ? 'none' : '';
73
+ }
74
+ }
75
+
76
+ show() {
77
+ if (this.element) {
78
+ requestAnimationFrame(() => {
79
+ this.element.classList.add('open');
80
+ });
81
+ }
82
+ }
83
+
84
+ hide() {
85
+ if (this.element) {
86
+ this.element.classList.remove('open');
87
+ }
88
+ }
89
+
90
+ setHeader(headerContent) {
91
+ const header = this.element.querySelector('.messenger-panel-header');
92
+ if (header) {
93
+ header.innerHTML = headerContent;
94
+ }
95
+ }
96
+
97
+ destroy() {
98
+ if (this._unsubscribe) {
99
+ this._unsubscribe();
100
+ }
101
+ if (this.navigationTabs) {
102
+ this.navigationTabs.destroy();
103
+ }
104
+ if (this.currentViewComponent && this.currentViewComponent.destroy) {
105
+ this.currentViewComponent.destroy();
106
+ }
107
+ if (this.element && this.element.parentNode) {
108
+ this.element.parentNode.removeChild(this.element);
109
+ }
110
+ }
111
+ }