@product7/product7-js 0.5.5 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +10 -10
  2. package/dist/README.md +10 -10
  3. package/dist/product7-js.js +7261 -7100
  4. package/dist/product7-js.js.map +1 -1
  5. package/dist/product7-js.min.js +1 -1
  6. package/dist/product7-js.min.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/api/services/{WebChatService.js → LiveChatService.js} +14 -14
  9. package/src/core/APIService.js +15 -15
  10. package/src/core/Product7.js +9 -4
  11. package/src/core/WebSocketService.js +1 -1
  12. package/src/docs/api.md +8 -8
  13. package/src/docs/example.md +9 -9
  14. package/src/docs/framework-integrations.md +3 -3
  15. package/src/index.js +38 -37
  16. package/src/styles/base.js +8 -8
  17. package/src/styles/{web-chat-components.js → liveChat-components.js} +114 -114
  18. package/src/styles/{web-chat-core.js → liveChat-core.js} +96 -31
  19. package/src/styles/{web-chat-features.js → liveChat-features.js} +20 -20
  20. package/src/styles/{web-chat-views.js → liveChat-views.js} +137 -137
  21. package/src/styles/liveChat.js +17 -0
  22. package/src/styles/{webChatCustomStyles.js → liveChatCustomStyles.js} +16 -16
  23. package/src/styles/styles.js +3 -3
  24. package/src/widgets/BaseWidget.js +2 -2
  25. package/src/widgets/ChangelogWidget.js +3 -3
  26. package/src/widgets/{WebChatWidget.js → LiveChatWidget.js} +169 -165
  27. package/src/widgets/SurveyWidget.js +7 -7
  28. package/src/widgets/WidgetFactory.js +2 -2
  29. package/src/widgets/{web-chat/WebChatState.js → liveChat/LiveChatState.js} +1 -1
  30. package/src/widgets/{web-chat/components/WebChatLauncher.js → liveChat/components/LiveChatLauncher.js} +16 -16
  31. package/src/widgets/{web-chat/components/WebChatPanel.js → liveChat/components/LiveChatPanel.js} +41 -10
  32. package/src/widgets/{web-chat → liveChat}/components/NavigationTabs.js +16 -16
  33. package/src/widgets/{web-chat → liveChat}/views/ChangelogView.js +17 -17
  34. package/src/widgets/{web-chat → liveChat}/views/ChatView.js +153 -95
  35. package/src/widgets/{web-chat → liveChat}/views/ConversationsView.js +24 -24
  36. package/src/widgets/{web-chat → liveChat}/views/HelpView.js +32 -32
  37. package/src/widgets/{web-chat → liveChat}/views/HomeView.js +52 -52
  38. package/src/widgets/{web-chat → liveChat}/views/PreChatFormView.js +15 -18
  39. package/types/index.d.ts +8 -9
  40. package/src/styles/web-chat.js +0 -17
@@ -11,18 +11,19 @@
11
11
  this._pendingAttachments = [];
12
12
  this._emojiPickerOpen = false;
13
13
  this._emojiOutsideHandler = null;
14
+ this._menuOutsideHandler = null;
14
15
  }
15
16
 
16
17
  render() {
17
18
  this.element = document.createElement('div');
18
- this.element.className = 'web-chat-view web-chat-chat-view';
19
+ this.element.className = 'liveChat-view liveChat-chat-view';
19
20
 
20
21
  this._updateContent();
21
22
 
22
23
  this._unsubscribe = this.state.subscribe((type, data) => {
23
24
  if (type === 'connectionChange') {
24
25
  const banner = this.element?.querySelector(
25
- '.web-chat-connection-banner'
26
+ '.liveChat-connection-banner'
26
27
  );
27
28
  if (banner) {
28
29
  banner.style.display = data.connected ? 'none' : 'flex';
@@ -85,53 +86,68 @@
85
86
  const teamName = this.state.teamName || 'Support';
86
87
  const headerAvatarHtml = logoUrl
87
88
  ? `<img src="${this._escapeHtml(logoUrl)}" alt="${this._escapeHtml(teamName)}" />`
88
- : `<iconify-icon icon="ph:chats-circle-duotone" width="20" height="20"></iconify-icon>`;
89
+ : `<iconify-icon icon="ph:chats-circle" width="20" height="20"></iconify-icon>`;
90
+
91
+ const isExpanded = this.options.isExpanded?.() ?? false;
92
+ const expandIcon = isExpanded ? 'ph:arrows-in' : 'ph:arrows-out';
93
+ const expandLabel = isExpanded ? 'Collapse window' : 'Expand window';
89
94
 
90
95
  this.element.innerHTML = `
91
- <div class="web-chat-chat-header">
92
- <button class="sdk-btn-icon web-chat-back-btn" aria-label="Back">
96
+ <div class="liveChat-chat-header">
97
+ <button class="sdk-btn-icon liveChat-back-btn" aria-label="Back">
93
98
  <iconify-icon icon="ph:arrow-left" width="20" height="20"></iconify-icon>
94
99
  </button>
95
- <div class="web-chat-chat-header-avatar">
100
+ <div class="liveChat-chat-header-avatar">
96
101
  ${headerAvatarHtml}
97
102
  </div>
98
- <div class="web-chat-chat-header-info">
99
- <span class="web-chat-chat-title">${this._escapeHtml(teamName)}</span>
100
- <span class="web-chat-chat-subtitle">${isClosed ? 'Conversation resolved' : this.state.responseTime || 'Typically replies within minutes'}</span>
103
+ <div class="liveChat-chat-header-info">
104
+ <span class="liveChat-chat-title">${this._escapeHtml(teamName)}</span>
105
+ <span class="liveChat-chat-subtitle">${isClosed ? 'Conversation resolved' : this.state.responseTime || 'Typically replies within minutes'}</span>
101
106
  </div>
102
- <div class="web-chat-chat-header-actions">
103
- <button class="sdk-btn-icon sdk-close-btn web-chat-mobile-close-btn" aria-label="Close">
107
+ <div class="liveChat-chat-header-actions">
108
+ <div class="liveChat-chat-menu-wrapper">
109
+ <button class="sdk-btn-icon liveChat-chat-menu-btn" aria-label="Options">
110
+ <iconify-icon icon="ph:dots-three" width="20" height="20"></iconify-icon>
111
+ </button>
112
+ <div class="liveChat-chat-menu-dropdown">
113
+ <button class="liveChat-chat-menu-item liveChat-expand-window-btn">
114
+ <iconify-icon icon="${expandIcon}" width="16" height="16"></iconify-icon>
115
+ <span>${expandLabel}</span>
116
+ </button>
117
+ </div>
118
+ </div>
119
+ <button class="sdk-btn-icon sdk-close-btn liveChat-mobile-close-btn" aria-label="Close">
104
120
  <iconify-icon icon="ph:x" width="18" height="18"></iconify-icon>
105
121
  </button>
106
122
  </div>
107
123
  </div>
108
124
 
109
- <div class="web-chat-connection-banner" style="display:none;">
125
+ <div class="liveChat-connection-banner" style="display:none;">
110
126
  <iconify-icon icon="ph:wifi-slash" width="14" height="14"></iconify-icon>
111
127
  <span>Reconnecting…</span>
112
128
  </div>
113
129
 
114
- <div class="web-chat-chat-messages">
130
+ <div class="liveChat-chat-messages">
115
131
  ${messagesHtml}
116
132
  ${
117
133
  isClosed
118
134
  ? `
119
- <div class="web-chat-closed-banner">
120
- <iconify-icon icon="ph:check-circle-duotone" width="18" height="18"></iconify-icon>
135
+ <div class="liveChat-closed-banner">
136
+ <iconify-icon icon="ph:check-circle" width="18" height="18"></iconify-icon>
121
137
  <span>This conversation has been resolved</span>
122
138
  </div>
123
139
  `
124
140
  : ''
125
141
  }
126
- <div class="web-chat-typing-indicator">
127
- <div class="web-chat-typing-dots">
142
+ <div class="liveChat-typing-indicator">
143
+ <div class="liveChat-typing-dots">
128
144
  <span></span><span></span><span></span>
129
145
  </div>
130
- <span class="web-chat-typing-text"></span>
146
+ <span class="liveChat-typing-text"></span>
131
147
  </div>
132
148
  </div>
133
149
 
134
- <div class="web-chat-scroll-pill" style="display:none;">
150
+ <div class="liveChat-scroll-pill" style="display:none;">
135
151
  <iconify-icon icon="ph:arrow-down" width="14" height="14"></iconify-icon>
136
152
  <span>New message</span>
137
153
  </div>
@@ -140,33 +156,33 @@
140
156
  isClosed
141
157
  ? ''
142
158
  : `
143
- <div class="web-chat-compose-attachments-preview"></div>
159
+ <div class="liveChat-compose-attachments-preview"></div>
144
160
 
145
- <div class="web-chat-chat-compose">
146
- <div class="web-chat-compose-input-wrapper">
147
- <textarea class="web-chat-compose-input" placeholder="${placeholder}" rows="1"></textarea>
161
+ <div class="liveChat-chat-compose">
162
+ <div class="liveChat-compose-input-wrapper">
163
+ <textarea class="liveChat-compose-input" placeholder="${placeholder}" rows="1"></textarea>
148
164
  </div>
149
- <div class="web-chat-compose-bottom">
150
- <div class="web-chat-compose-actions">
151
- <button class="sdk-btn-icon web-chat-compose-attach" aria-label="Attach file">
152
- <iconify-icon icon="ph:paperclip-duotone" width="20" height="20"></iconify-icon>
165
+ <div class="liveChat-compose-bottom">
166
+ <div class="liveChat-compose-actions">
167
+ <button class="sdk-btn-icon liveChat-compose-attach" aria-label="Attach file">
168
+ <iconify-icon icon="ph:paperclip" width="20" height="20"></iconify-icon>
153
169
  </button>
154
- <button class="sdk-btn-icon web-chat-emoji-btn" aria-label="Emoji">
155
- <iconify-icon icon="ph:smiley-duotone" width="20" height="20"></iconify-icon>
170
+ <button class="sdk-btn-icon liveChat-emoji-btn" aria-label="Emoji">
171
+ <iconify-icon icon="ph:smiley" width="20" height="20"></iconify-icon>
156
172
  </button>
157
173
  </div>
158
- <button class="web-chat-compose-send" aria-label="Send" disabled>
174
+ <button class="liveChat-compose-send" aria-label="Send" disabled>
159
175
  <iconify-icon icon="ph:paper-plane-right" width="20" height="20"></iconify-icon>
160
176
  </button>
161
177
  </div>
162
- <input type="file" class="web-chat-compose-file-input" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip" />
178
+ <input type="file" class="liveChat-compose-file-input" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip" />
163
179
  </div>
164
180
  `
165
181
  }
166
182
  `;
167
183
 
168
184
  this._typingIndicator = this.element.querySelector(
169
- '.web-chat-typing-indicator'
185
+ '.liveChat-typing-indicator'
170
186
  );
171
187
  this._attachEvents();
172
188
  this._scrollToBottom();
@@ -178,11 +194,11 @@
178
194
  const logoUrl = this.options.logoUrl;
179
195
 
180
196
  const logoHtml = logoUrl
181
- ? `<div class="web-chat-chat-empty-logo"><img src="${this._escapeHtml(logoUrl)}" alt="${this._escapeHtml(this.state.teamName)}" /></div>`
197
+ ? `<div class="liveChat-chat-empty-logo"><img src="${this._escapeHtml(logoUrl)}" alt="${this._escapeHtml(this.state.teamName)}" /></div>`
182
198
  : '';
183
199
 
184
200
  return `
185
- <div class="web-chat-chat-empty">
201
+ <div class="liveChat-chat-empty">
186
202
  ${logoHtml}
187
203
  <h3>${isNewConversation ? 'Start a new conversation' : 'Start the conversation'}</h3>
188
204
  </div>
@@ -194,12 +210,12 @@
194
210
  return attachments
195
211
  .map((att) => {
196
212
  if (att.type === 'image') {
197
- return `<img class="web-chat-message-image" src="${this._escapeHtml(att.url)}" alt="${this._escapeHtml(att.name || 'image')}" data-url="${this._escapeHtml(att.url)}" />`;
213
+ return `<img class="liveChat-message-image" src="${this._escapeHtml(att.url)}" alt="${this._escapeHtml(att.name || 'image')}" data-url="${this._escapeHtml(att.url)}" />`;
198
214
  }
199
- return `<a class="web-chat-message-file" href="${this._escapeHtml(att.url)}" data-url="${this._escapeHtml(att.url)}" data-name="${this._escapeHtml(att.name || 'file')}">
200
- <iconify-icon icon="ph:file-duotone" width="18" height="18"></iconify-icon>
215
+ return `<a class="liveChat-message-file" href="${this._escapeHtml(att.url)}" data-url="${this._escapeHtml(att.url)}" data-name="${this._escapeHtml(att.name || 'file')}">
216
+ <iconify-icon icon="ph:file" width="18" height="18"></iconify-icon>
201
217
  <span>${this._escapeHtml(att.name || 'file')}</span>
202
- <iconify-icon icon="ph:download-simple" width="16" height="16" class="web-chat-file-download-icon"></iconify-icon>
218
+ <iconify-icon icon="ph:download-simple" width="16" height="16" class="liveChat-file-download-icon"></iconify-icon>
203
219
  </a>`;
204
220
  })
205
221
  .join('');
@@ -212,8 +228,8 @@
212
228
 
213
229
  const isOwn = message.isOwn;
214
230
  const messageClass = isOwn
215
- ? 'web-chat-message-own'
216
- : 'web-chat-message-received';
231
+ ? 'liveChat-message-own'
232
+ : 'liveChat-message-received';
217
233
  const timeStr = isLastInGroup
218
234
  ? this._formatMessageTime(message.timestamp)
219
235
  : '';
@@ -221,25 +237,25 @@
221
237
  const isOptimistic = message.isOptimistic;
222
238
 
223
239
  const contentHtml = message.content
224
- ? `<div class="web-chat-message-content">${this._formatMessageContent(message.content)}</div>`
240
+ ? `<div class="liveChat-message-content">${this._formatMessageContent(message.content)}</div>`
225
241
  : '';
226
242
  const bubbleHtml = contentHtml
227
- ? `<div class="web-chat-message-bubble">${contentHtml}</div>`
243
+ ? `<div class="liveChat-message-bubble">${contentHtml}</div>`
228
244
  : '';
229
245
 
230
246
  if (isOwn) {
231
247
  const sentIndicator = isLastInGroup
232
- ? `<div class="web-chat-message-meta web-chat-message-meta-own">
248
+ ? `<div class="liveChat-message-meta liveChat-message-meta-own">
233
249
  ${
234
250
  isOptimistic
235
- ? `<span class="web-chat-message-sent-status">Sending…</span>`
236
- : `<span class="web-chat-message-sent-status">Sent</span>`
251
+ ? `<span class="liveChat-message-sent-status">Sending…</span>`
252
+ : `<span class="liveChat-message-sent-status">Sent</span>`
237
253
  }
238
254
  ${timeStr ? `<span>·</span><span>${timeStr}</span>` : ''}
239
255
  </div>`
240
256
  : '';
241
257
  return `
242
- <div class="web-chat-message ${messageClass}${isOptimistic ? ' web-chat-message-optimistic' : ''}">
258
+ <div class="liveChat-message ${messageClass}${isOptimistic ? ' liveChat-message-optimistic' : ''}">
243
259
  ${bubbleHtml}
244
260
  ${attachmentsHtml}
245
261
  ${sentIndicator}
@@ -249,15 +265,15 @@
249
265
 
250
266
  const avatarHtml = this._renderSenderAvatar(message.sender);
251
267
  return `
252
- <div class="web-chat-message ${messageClass}">
253
- <div class="web-chat-message-row">
254
- <div class="web-chat-message-avatar">${avatarHtml}</div>
255
- <div class="web-chat-message-wrapper">
268
+ <div class="liveChat-message ${messageClass}">
269
+ <div class="liveChat-message-row">
270
+ <div class="liveChat-message-avatar">${avatarHtml}</div>
271
+ <div class="liveChat-message-wrapper">
256
272
  ${bubbleHtml}
257
273
  ${attachmentsHtml}
258
274
  </div>
259
275
  </div>
260
- ${timeStr ? `<div class="web-chat-message-meta"><span>${timeStr}</span></div>` : ''}
276
+ ${timeStr ? `<div class="liveChat-message-meta"><span>${timeStr}</span></div>` : ''}
261
277
  </div>
262
278
  `;
263
279
  }
@@ -286,11 +302,11 @@
286
302
  const action = rawAction.charAt(0).toUpperCase() + rawAction.slice(1);
287
303
 
288
304
  return `
289
- <div class="web-chat-message-system-event">
290
- <div class="web-chat-message-system-event-avatar">${logoHtml}</div>
291
- <span class="web-chat-message-system-event-name">${this._escapeHtml(name)}</span>
292
- <span class="web-chat-message-system-event-action">${this._escapeHtml(action)}</span>
293
- ${timeStr ? `<span class="web-chat-message-system-event-time">${timeStr}</span>` : ''}
305
+ <div class="liveChat-message-system-event">
306
+ <div class="liveChat-message-system-event-avatar">${logoHtml}</div>
307
+ <span class="liveChat-message-system-event-name">${this._escapeHtml(name)}</span>
308
+ <span class="liveChat-message-system-event-action">${this._escapeHtml(action)}</span>
309
+ ${timeStr ? `<span class="liveChat-message-system-event-time">${timeStr}</span>` : ''}
294
310
  </div>
295
311
  `;
296
312
  }
@@ -304,16 +320,16 @@
304
320
  const timeStr = this._formatMessageTime(message.timestamp);
305
321
 
306
322
  return `
307
- <div class="web-chat-message web-chat-message-received">
308
- <div class="web-chat-message-row">
309
- <div class="web-chat-message-avatar">${avatarHtml}</div>
310
- <div class="web-chat-message-wrapper">
311
- <div class="web-chat-message-bubble">
312
- <div class="web-chat-message-content">${this._formatMessageContent(content)}</div>
323
+ <div class="liveChat-message liveChat-message-received">
324
+ <div class="liveChat-message-row">
325
+ <div class="liveChat-message-avatar">${avatarHtml}</div>
326
+ <div class="liveChat-message-wrapper">
327
+ <div class="liveChat-message-bubble">
328
+ <div class="liveChat-message-content">${this._formatMessageContent(content)}</div>
313
329
  </div>
314
330
  </div>
315
331
  </div>
316
- ${timeStr ? `<div class="web-chat-message-meta"><span>${timeStr}</span></div>` : ''}
332
+ ${timeStr ? `<div class="liveChat-message-meta"><span>${timeStr}</span></div>` : ''}
317
333
  </div>
318
334
  `;
319
335
  }
@@ -342,7 +358,7 @@
342
358
  })
343
359
  .join('');
344
360
 
345
- return `<div class="web-chat-avatar-stack">${avatarItems}</div>`;
361
+ return `<div class="liveChat-avatar-stack">${avatarItems}</div>`;
346
362
  }
347
363
 
348
364
  _formatMessageTime(timestamp) {
@@ -372,9 +388,9 @@
372
388
 
373
389
  _appendMessage(message) {
374
390
  const messagesContainer = this.element.querySelector(
375
- '.web-chat-chat-messages'
391
+ '.liveChat-chat-messages'
376
392
  );
377
- const emptyState = messagesContainer.querySelector('.web-chat-chat-empty');
393
+ const emptyState = messagesContainer.querySelector('.liveChat-chat-empty');
378
394
  if (emptyState) {
379
395
  emptyState.remove();
380
396
  }
@@ -394,14 +410,14 @@
394
410
  if (isNearBottom) {
395
411
  this._scrollToBottom();
396
412
  } else if (!message.isOwn) {
397
- const pill = this.element.querySelector('.web-chat-scroll-pill');
413
+ const pill = this.element.querySelector('.liveChat-scroll-pill');
398
414
  if (pill) pill.style.display = 'flex';
399
415
  }
400
416
  }
401
417
 
402
418
  _scrollToBottom() {
403
419
  const messagesContainer = this.element.querySelector(
404
- '.web-chat-chat-messages'
420
+ '.liveChat-chat-messages'
405
421
  );
406
422
  if (messagesContainer) {
407
423
  setTimeout(() => {
@@ -412,9 +428,9 @@
412
428
 
413
429
  _setupScrollPill() {
414
430
  const messagesContainer = this.element.querySelector(
415
- '.web-chat-chat-messages'
431
+ '.liveChat-chat-messages'
416
432
  );
417
- const pill = this.element.querySelector('.web-chat-scroll-pill');
433
+ const pill = this.element.querySelector('.liveChat-scroll-pill');
418
434
  if (!messagesContainer || !pill) return;
419
435
 
420
436
  pill.addEventListener('click', () => {
@@ -469,8 +485,8 @@
469
485
  }
470
486
 
471
487
  _updateSendButtonState() {
472
- const input = this.element.querySelector('.web-chat-compose-input');
473
- const sendBtn = this.element.querySelector('.web-chat-compose-send');
488
+ const input = this.element.querySelector('.liveChat-compose-input');
489
+ const sendBtn = this.element.querySelector('.liveChat-compose-send');
474
490
  if (input && sendBtn) {
475
491
  sendBtn.disabled =
476
492
  !input.value.trim() && this._pendingAttachments.length === 0;
@@ -479,7 +495,7 @@
479
495
 
480
496
  _renderAttachmentPreviews() {
481
497
  const container = this.element.querySelector(
482
- '.web-chat-compose-attachments-preview'
498
+ '.liveChat-compose-attachments-preview'
483
499
  );
484
500
  if (!container) return;
485
501
 
@@ -494,18 +510,18 @@
494
510
  .map((att, i) => {
495
511
  const isImage = att.type.startsWith('image');
496
512
  const thumb = isImage
497
- ? `<img class="web-chat-attachment-thumb" src="${att.preview}" alt="${this._escapeHtml(att.file.name)}" />`
498
- : `<div class="web-chat-attachment-thumb web-chat-attachment-file-icon"><iconify-icon icon="ph:file-duotone" width="24" height="24"></iconify-icon></div>`;
513
+ ? `<img class="liveChat-attachment-thumb" src="${att.preview}" alt="${this._escapeHtml(att.file.name)}" />`
514
+ : `<div class="liveChat-attachment-thumb liveChat-attachment-file-icon"><iconify-icon icon="ph:file" width="24" height="24"></iconify-icon></div>`;
499
515
  return `
500
- <div class="web-chat-attachment-preview" data-index="${i}">
516
+ <div class="liveChat-attachment-preview" data-index="${i}">
501
517
  ${thumb}
502
- <button class="web-chat-attachment-remove" data-index="${i}" aria-label="Remove">&times;</button>
518
+ <button class="liveChat-attachment-remove" data-index="${i}" aria-label="Remove">&times;</button>
503
519
  </div>
504
520
  `;
505
521
  })
506
522
  .join('');
507
523
 
508
- container.querySelectorAll('.web-chat-attachment-remove').forEach((btn) => {
524
+ container.querySelectorAll('.liveChat-attachment-remove').forEach((btn) => {
509
525
  btn.addEventListener('click', (e) => {
510
526
  const idx = parseInt(e.currentTarget.dataset.index, 10);
511
527
  this._pendingAttachments.splice(idx, 1);
@@ -517,13 +533,13 @@
517
533
 
518
534
  _attachEvents() {
519
535
  this.element
520
- .querySelector('.web-chat-back-btn')
536
+ .querySelector('.liveChat-back-btn')
521
537
  .addEventListener('click', () => {
522
538
  this.state.setView('messages');
523
539
  });
524
540
 
525
541
  const mobileCloseBtn = this.element.querySelector(
526
- '.web-chat-mobile-close-btn'
542
+ '.liveChat-mobile-close-btn'
527
543
  );
528
544
  if (mobileCloseBtn) {
529
545
  mobileCloseBtn.addEventListener('click', () => {
@@ -531,8 +547,47 @@
531
547
  });
532
548
  }
533
549
 
534
- const input = this.element.querySelector('.web-chat-compose-input');
535
- const sendBtn = this.element.querySelector('.web-chat-compose-send');
550
+ const menuBtn = this.element.querySelector('.liveChat-chat-menu-btn');
551
+ const dropdown = this.element.querySelector('.liveChat-chat-menu-dropdown');
552
+ if (menuBtn && dropdown) {
553
+ menuBtn.addEventListener('click', (e) => {
554
+ e.stopPropagation();
555
+ dropdown.classList.toggle('open');
556
+ if (
557
+ dropdown.classList.contains('open') &&
558
+ this._menuOutsideHandler === null
559
+ ) {
560
+ this._menuOutsideHandler = (ev) => {
561
+ if (!dropdown.contains(ev.target) && !menuBtn.contains(ev.target)) {
562
+ dropdown.classList.remove('open');
563
+ document.removeEventListener('click', this._menuOutsideHandler);
564
+ this._menuOutsideHandler = null;
565
+ }
566
+ };
567
+ setTimeout(
568
+ () => document.addEventListener('click', this._menuOutsideHandler),
569
+ 0
570
+ );
571
+ }
572
+ });
573
+ }
574
+
575
+ const expandWindowBtn = this.element.querySelector(
576
+ '.liveChat-expand-window-btn'
577
+ );
578
+ if (expandWindowBtn && dropdown) {
579
+ expandWindowBtn.addEventListener('click', () => {
580
+ dropdown.classList.remove('open');
581
+ if (this._menuOutsideHandler) {
582
+ document.removeEventListener('click', this._menuOutsideHandler);
583
+ this._menuOutsideHandler = null;
584
+ }
585
+ this.options.onToggleExpand?.();
586
+ });
587
+ }
588
+
589
+ const input = this.element.querySelector('.liveChat-compose-input');
590
+ const sendBtn = this.element.querySelector('.liveChat-compose-send');
536
591
 
537
592
  if (input && sendBtn) {
538
593
  input.addEventListener('input', () => {
@@ -557,7 +612,7 @@
557
612
  });
558
613
  }
559
614
 
560
- const emojiBtn = this.element.querySelector('.web-chat-emoji-btn');
615
+ const emojiBtn = this.element.querySelector('.liveChat-emoji-btn');
561
616
  if (emojiBtn) {
562
617
  emojiBtn.addEventListener('click', (e) => {
563
618
  e.stopPropagation();
@@ -565,9 +620,9 @@
565
620
  });
566
621
  }
567
622
 
568
- const attachBtn = this.element.querySelector('.web-chat-compose-attach');
623
+ const attachBtn = this.element.querySelector('.liveChat-compose-attach');
569
624
  const fileInput = this.element.querySelector(
570
- '.web-chat-compose-file-input'
625
+ '.liveChat-compose-file-input'
571
626
  );
572
627
 
573
628
  if (attachBtn && fileInput) {
@@ -596,11 +651,11 @@
596
651
  }
597
652
 
598
653
  const messagesContainer = this.element.querySelector(
599
- '.web-chat-chat-messages'
654
+ '.liveChat-chat-messages'
600
655
  );
601
656
  if (messagesContainer) {
602
657
  messagesContainer.addEventListener('click', (e) => {
603
- const fileLink = e.target.closest('.web-chat-message-file');
658
+ const fileLink = e.target.closest('.liveChat-message-file');
604
659
  if (fileLink) {
605
660
  e.preventDefault();
606
661
  const url = fileLink.dataset.url;
@@ -609,7 +664,7 @@
609
664
  return;
610
665
  }
611
666
 
612
- const img = e.target.closest('.web-chat-message-image');
667
+ const img = e.target.closest('.liveChat-message-image');
613
668
  if (img) {
614
669
  const url = img.dataset.url || img.src;
615
670
  window.open(url, '_blank');
@@ -648,7 +703,7 @@
648
703
  async _sendMessage() {
649
704
  if (this._isConversationClosed) return;
650
705
 
651
- const input = this.element.querySelector('.web-chat-compose-input');
706
+ const input = this.element.querySelector('.liveChat-compose-input');
652
707
  const content = input.value.trim();
653
708
  const hasAttachments = this._pendingAttachments.length > 0;
654
709
 
@@ -713,7 +768,7 @@
713
768
 
714
769
  async _toggleEmojiPicker() {
715
770
  const existing = this.element.querySelector(
716
- '.web-chat-emoji-picker-container'
771
+ '.liveChat-emoji-picker-container'
717
772
  );
718
773
  if (existing) {
719
774
  existing.remove();
@@ -732,12 +787,12 @@
732
787
  }
733
788
 
734
789
  const container = document.createElement('div');
735
- container.className = 'web-chat-emoji-picker-container';
790
+ container.className = 'liveChat-emoji-picker-container';
736
791
 
737
792
  const picker = document.createElement('emoji-picker');
738
793
  container.appendChild(picker);
739
794
 
740
- const compose = this.element.querySelector('.web-chat-chat-compose');
795
+ const compose = this.element.querySelector('.liveChat-chat-compose');
741
796
  compose.parentNode.insertBefore(container, compose);
742
797
  this._emojiPickerOpen = true;
743
798
 
@@ -754,7 +809,7 @@
754
809
  this._emojiOutsideHandler = (e) => {
755
810
  if (
756
811
  !container.contains(e.target) &&
757
- !e.target.closest('.web-chat-emoji-btn')
812
+ !e.target.closest('.liveChat-emoji-btn')
758
813
  ) {
759
814
  container.remove();
760
815
  this._emojiPickerOpen = false;
@@ -769,7 +824,7 @@
769
824
  }
770
825
 
771
826
  _insertEmoji(emoji) {
772
- const input = this.element.querySelector('.web-chat-compose-input');
827
+ const input = this.element.querySelector('.liveChat-compose-input');
773
828
  if (!input) return;
774
829
  const start = input.selectionStart;
775
830
  const end = input.selectionEnd;
@@ -813,7 +868,7 @@
813
868
  if (this._typingIndicator) {
814
869
  this._typingIndicator.style.display = 'flex';
815
870
  const textEl = this._typingIndicator.querySelector(
816
- '.web-chat-typing-text'
871
+ '.liveChat-typing-text'
817
872
  );
818
873
  if (textEl) {
819
874
  textEl.textContent = `${userName || 'Support'} is typing...`;
@@ -839,6 +894,9 @@
839
894
  if (this._emojiOutsideHandler) {
840
895
  document.removeEventListener('click', this._emojiOutsideHandler);
841
896
  }
897
+ if (this._menuOutsideHandler) {
898
+ document.removeEventListener('click', this._menuOutsideHandler);
899
+ }
842
900
  if (this.element && this.element.parentNode) {
843
901
  this.element.parentNode.removeChild(this.element);
844
902
  }