embeddedaichatux 2.2.0 → 2.3.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 (2) hide show
  1. package/EmbeddedChat.js +231 -160
  2. package/package.json +1 -1
package/EmbeddedChat.js CHANGED
@@ -1,4 +1,4 @@
1
- class EmbeddedChat {
1
+ class EmbeddedChat {
2
2
  static instanceCounter = 0;
3
3
 
4
4
  constructor(containerDiv, chatId, options) {
@@ -7,22 +7,26 @@
7
7
 
8
8
  if (!containerDiv) {
9
9
  containerDiv = this.createChatContainer();
10
+ this.createdContainer = true;
11
+ } else {
12
+ this.createdContainer = false;
10
13
  }
11
14
  this.containerDiv = containerDiv;
12
15
  this.chatId = chatId;
13
16
  this.options = options || {};
14
17
  this.isPreview = this.options.isPreview || false;
15
18
  this.locale = options.locale || 'en';
16
- this.previewParam = this.isPreview ? "?isPreview=true" : "";
19
+
17
20
  this.serverUrl = this.options.serverUrl || 'https://app.bizdriver.ai/';
18
21
  this.height = options.height || 600; // Default height
19
- this.width = options.width || 320; // Default width
22
+ this.width = options.width || 400; // Default width
20
23
  this.marginBottom = options.marginBottom || 0;
21
24
  this.enableAnimation = options.enableAnimation !== false; // Default to true if not provided
22
- this.minimizedHeight = "180px"; // Sensible default instead of 30% calculation
25
+ this.theme = options.theme || this.containerDiv.dataset.theme || 'Standard';
26
+ this.minimizedHeight = this.theme === 'MinimalInput' ? "75px" : "80px"; // Default heights for different themes
23
27
  this.hasMeasuredSize = false;
24
28
  this.mouseInsideChat = false;
25
- this.hasRefreshed = false; // Flag to prevent endless loop
29
+
26
30
  this.conversationId = null;
27
31
  this.userTokenProvider = options.userTokenProvider;
28
32
  this.targetOrigin = "*";
@@ -33,7 +37,9 @@
33
37
  this.position = this.options.position || this.containerDiv.dataset.position || 'fixed';
34
38
  this.positionStyle = this.position === 'in-place' ?
35
39
  `position: relative; width:100%; height:${this.height}px; ${transitionStyle}` :
36
- `position: fixed; right: 1em; bottom: 0; width: ${this.width}px; z-index: 100000; max-width:90vw; max-height: 90vh; ${transitionStyle}`;
40
+ (this.theme === 'MinimalInput' ?
41
+ `position: fixed; left: 0; right: 0; margin-left: auto; margin-right: auto; width: 100%; max-width: ${this.width}px; bottom: ${this.marginBottom}px; z-index: 100000; max-height: 90vh; ${transitionStyle}` :
42
+ `position: fixed; right: 1em; bottom: ${this.marginBottom}px; width: 100%; max-width: ${this.width}px; z-index: 100000; max-height: 90vh; ${transitionStyle}`);
37
43
  this.mode = options.mode || 'Chat'; // default to 'chat'
38
44
  this.minimizeOnScroll = false; // Default to false
39
45
  if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') {
@@ -52,6 +58,13 @@
52
58
  this.init();
53
59
  }
54
60
 
61
+ queueDOMUpdate(fn) {
62
+ requestAnimationFrame(() => {
63
+ if (this.isDestroyed) return;
64
+ fn();
65
+ });
66
+ }
67
+
55
68
  isSafari() {
56
69
  const ua = navigator.userAgent.toLowerCase();
57
70
  return ua.indexOf('safari') != -1 && ua.indexOf('chrome') == -1;
@@ -89,17 +102,31 @@
89
102
  }
90
103
 
91
104
  async updateIframes() {
105
+ if (this.isDestroyed) return;
106
+
92
107
  const cleanedServerUrl = this.serverUrl.endsWith('/')
93
108
  ? this.serverUrl.slice(0, -1)
94
109
  : this.serverUrl;
95
110
  const baseIframeUrl = `${cleanedServerUrl}/ChatUX/${this.chatId}`;
111
+ const transitionStyle = this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : '';
112
+
113
+ // Recalculate positionStyle conditionally in case theme changed dynamically
114
+ this.positionStyle = this.position === 'in-place' ?
115
+ `position: relative; width:100%; height:${this.height}px; ${transitionStyle}` :
116
+ (this.theme === 'MinimalInput' ?
117
+ `position: fixed; left: 0; right: 0; margin-left: auto; margin-right: auto; bottom: ${this.marginBottom}px; width: 95vw; max-width: ${this.width}px; z-index: 100000; max-height: 90vh; pointer-events: auto; ${transitionStyle}` :
118
+ `position: fixed; right: 1em; bottom: ${this.marginBottom}px; width: ${this.width}px; z-index: 100000; max-width:90vw; max-height: 90vh; pointer-events: auto; ${transitionStyle}`);
119
+
96
120
  const minimizedPositionStyle = this.position === 'in-place' ?
97
- `position: relative; width: 100%; height: ${this.minimizedHeight}; ${this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : ''}` :
98
- `position: fixed; right: 0; bottom: 0; width: 150px; height: ${this.minimizedHeight}; z-index: 100000; ${this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : ''}`;
121
+ `position: relative; width: 100%; height: ${this.minimizedHeight}; ${transitionStyle}` :
122
+ (this.theme === 'MinimalInput' ?
123
+ `position: fixed; left: 0; right: 0; margin-left: auto; margin-right: auto; bottom: ${this.marginBottom}px; width: 100vw; max-width: ${Math.min(300, this.width) + 40}px; height: ${this.minimizedHeight}; z-index: 100000; pointer-events: auto; ${transitionStyle}` :
124
+ `position: fixed; right: 0; bottom: ${this.marginBottom}px; width: 150px; height: ${this.minimizedHeight}; z-index: 100000; max-width: 95vw; pointer-events: auto; ${transitionStyle}`);
99
125
 
100
126
  const params = {
101
127
  isPreview: this.isPreview,
102
128
  mode: this.mode,
129
+ theme: this.theme,
103
130
  locale: this.locale,
104
131
  conversationId: this.conversationId,
105
132
  instanceId: this.instanceId,
@@ -138,17 +165,26 @@
138
165
  if (this.iframe) {
139
166
  await this.waitForIframeLoad(this.iframe);
140
167
  }
168
+ if (this.isDestroyed) return;
141
169
 
142
170
  this.addIframeEventListeners();
143
171
 
144
172
  // Send auth to iframes after load
145
173
  await this.postAuthToIframes();
174
+ if (this.isDestroyed) return;
146
175
 
147
- // restore your ContactForm logic
176
+ // Restore ContactForm logic
148
177
  if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') {
149
178
  this.iframe.style.maxWidth = '100%';
150
179
  this.iframe.style.display = 'block';
151
180
  }
181
+
182
+ // Apply pointer-events to container if theme is MinimalInput to prevent blocking page
183
+ if (this.theme === 'MinimalInput' && this.position !== 'in-place') {
184
+ this.containerDiv.style.pointerEvents = 'none';
185
+ } else {
186
+ this.containerDiv.style.pointerEvents = 'auto'; // Reset for other themes
187
+ }
152
188
  }
153
189
 
154
190
  addIframeEventListeners() {
@@ -163,41 +199,40 @@
163
199
  }
164
200
 
165
201
  addWindowEventListeners() {
166
-
167
- // Use a flag to ensure window listeners aren't duplicated for this specific instance if setMode is called (though it shouldn't be)
168
202
  if (this.windowListenersAdded) return;
169
203
 
170
- window.addEventListener("message", (e) => {
171
- // Only trust messages from our server (when targetOrigin is not "*")
172
- if (this.targetOrigin !== "*" && e.origin !== this.targetOrigin) return;
173
- this.handleMessage(e);
174
- });
175
-
176
- window.addEventListener("scroll", () => {
177
- if (this.minimizeOnScroll && !this.mouseInsideChat) {
178
- this.minimizeOnScrollAction();
179
- }
180
- });
204
+ this.listeners = [
205
+ { type: "message", fn: (e) => {
206
+ if (this.targetOrigin !== "*" && e.origin !== this.targetOrigin) return;
207
+ this.handleMessage(e);
208
+ }},
209
+ { type: "scroll", fn: () => {
210
+ if (this.minimizeOnScroll && !this.mouseInsideChat) {
211
+ this.queueDOMUpdate(() => this.minimizeOnScrollAction());
212
+ }
213
+ }},
214
+ { type: "resize", fn: () => this.queueDOMUpdate(() => this.postResizeMessage()) }
215
+ ];
181
216
 
182
- const handleResizeTrigger = () => {
183
- this.postResizeMessage();
184
- };
217
+ this.listeners.forEach(l => window.addEventListener(l.type, l.fn));
185
218
 
219
+ const loadFn = () => this.queueDOMUpdate(() => this.postResizeMessage());
186
220
  if (document.readyState === 'complete') {
187
- handleResizeTrigger();
221
+ loadFn();
188
222
  } else {
189
- window.addEventListener("load", handleResizeTrigger);
223
+ this.listeners.push({ type: "load", fn: loadFn });
224
+ window.addEventListener("load", loadFn);
190
225
  }
191
226
 
192
- window.addEventListener("resize", handleResizeTrigger);
193
227
  this.windowListenersAdded = true;
194
228
  }
195
229
 
196
230
  postResizeMessage() {
231
+ if (this.isDestroyed) return;
197
232
  if (this.iframe?.contentWindow) {
198
233
  this.iframe.contentWindow.postMessage({
199
234
  message: "resize",
200
- width: window.innerWidth, // Ensure correct size
235
+ width: window.innerWidth,
201
236
  height: window.innerHeight,
202
237
  instanceId: this.instanceId
203
238
  }, this.targetOrigin);
@@ -205,11 +240,9 @@
205
240
  }
206
241
 
207
242
  handleMessage(e) {
208
- // Only trust messages from our server (when targetOrigin is not "*")
209
243
  if (this.targetOrigin !== "*" && e.origin !== this.targetOrigin) return;
210
244
 
211
245
  if (typeof e.data !== "object") {
212
- // Preserve original handling for string messages
213
246
  if (typeof e.data === "string") {
214
247
  if (e.data === "minimize") {
215
248
  this.animateMinimize();
@@ -234,65 +267,76 @@
234
267
  const targetChatId = (this.chatId || "").toString().toLowerCase();
235
268
  const incomingInstanceId = data.instanceId;
236
269
 
237
- // Priority 1: Handle instance-specific routing
238
270
  if (incomingInstanceId && incomingInstanceId !== this.instanceId) return;
239
-
240
- // Priority 2: Bail out if chatId doesn't match and is provided (legacy/fallback)
241
271
  if (!incomingInstanceId && incomingChatId && targetChatId && incomingChatId !== targetChatId) return;
242
272
 
243
- // Allow the iframe to request a fresh token
244
273
  if (data.type === "requestAuth") {
245
274
  this.postAuthToIframes();
246
275
  return;
247
276
  }
248
277
 
249
- // Priority 1: Handle minimized size updates (CRITICAL: Bail out early and DO NOT pollute configuration)
250
278
  if (msg === "updateMinimizedSize") {
251
- // Support both new and legacy keys for compatibility with cached scripts
252
279
  const h = data.minimizedHeight || data.height;
253
280
  const w = data.minimizedWidth || data.width;
254
- this.updateMinimizedSize(h, w);
281
+ this.queueDOMUpdate(() => this.updateMinimizedSize(h, w));
255
282
  return;
256
283
  }
257
284
 
258
- const updates = {};
285
+ if (msg === "collapseMinimalInput") {
286
+ this.queueDOMUpdate(() => {
287
+ if (this.minimizedIframe) {
288
+ Object.assign(this.minimizedIframe.style, {
289
+ width: '80px', height: '80px', left: 'auto', right: '0',
290
+ marginLeft: '0', marginRight: '0', pointerEvents: 'auto'
291
+ });
292
+ }
293
+ });
294
+ return;
295
+ }
259
296
 
297
+ if (msg === "restoreMinimalInput") {
298
+ this.queueDOMUpdate(() => {
299
+ if (this.minimizedIframe) {
300
+ Object.assign(this.minimizedIframe.style, {
301
+ width: '100vw', maxWidth: `${Math.min(300, this.width) + 40}px`,
302
+ height: this.theme === 'MinimalInput' ? "75px" : this.minimizedHeight,
303
+ left: '0', right: '0', marginLeft: 'auto', marginRight: 'auto',
304
+ pointerEvents: 'auto'
305
+ });
306
+ }
307
+ if (this.theme === 'MinimalInput' && this.position !== 'in-place') {
308
+ this.containerDiv.style.pointerEvents = 'none';
309
+ }
310
+ });
311
+ return;
312
+ }
313
+
314
+ const updates = {};
260
315
  if (data.type === "setMinimizeOnScroll" || data.minimizeOnScroll !== undefined) {
261
316
  const minimizeValue = data.type === "setMinimizeOnScroll" ? data.value : data.minimizeOnScroll;
262
317
  this.minimizeOnScroll = minimizeValue === true || minimizeValue === "true";
263
318
  }
264
319
 
265
- if (data.locale) {
266
- updates.locale = data.locale;
267
- }
268
- if (data.width) {
269
- updates.width = data.width;
270
- }
271
- if (data.height) {
272
- updates.height = data.height;
273
- }
274
- if (data.marginBottom) {
275
- updates.marginBottom = data.marginBottom;
276
- }
277
- if (data.scale) {
278
- updates.scale = data.scale;
279
- }
320
+ if (data.locale) updates.locale = data.locale;
321
+ if (data.width) updates.width = data.width;
322
+ if (data.height) updates.height = data.height;
323
+ if (data.marginBottom) updates.marginBottom = data.marginBottom;
324
+ if (data.scale) updates.scale = data.scale;
325
+ if (data.theme) updates.theme = data.theme;
280
326
 
281
- // Apply iframe updates in one go
282
327
  if (Object.keys(updates).length > 0) {
283
- this.applyIframeUpdates(updates);
328
+ this.queueDOMUpdate(() => this.applyIframeUpdates(updates));
284
329
  }
285
330
 
286
- // Handle maximize/minimize/navigate
287
331
  if (msg === "minimize") {
288
332
  if (typeof this.options.onMinimize === 'function') {
289
333
  this.options.onMinimize();
290
334
  } else if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
291
- this.animateMinimize();
335
+ this.queueDOMUpdate(() => this.animateMinimize());
292
336
  }
293
337
  } else if (msg === "show" || msg === "maximize") {
294
338
  const animate = data.animate === true;
295
- this.showMaximized(animate);
339
+ this.queueDOMUpdate(() => this.showMaximized(animate, data.initialMessage));
296
340
  } else if (msg === "navigate" && data.url) {
297
341
  window.location.href = data.url;
298
342
  } else if (data.type === "switchToChat" || msg === "switchToChat") {
@@ -309,24 +353,24 @@
309
353
  }
310
354
  }
311
355
 
312
- // Handle conversationId update
313
356
  if (data.conversationId) {
314
357
  this.setConversationId(data.conversationId);
315
358
  }
316
359
  }
317
360
 
318
361
  animateMinimize() {
362
+ if (this.isDestroyed) return;
319
363
  if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
320
364
  this.iframe.style.height = this.minimizedHeight;
321
365
  this.iframe.style.opacity = '0';
322
366
  setTimeout(() => {
367
+ if (this.isDestroyed) return;
323
368
  this.iframe.style.display = "none";
324
369
  this.iframe.style.opacity = '1';
325
370
  this.minimizedIframe.style.display = "block";
326
371
  this.minimizedIframe.style.height = this.minimizedHeight;
327
372
  this.minimizedIframe.style.opacity = '1';
328
373
 
329
- // Request a height update after showing
330
374
  if (this.minimizedIframe.contentWindow) {
331
375
  this.minimizedIframe.contentWindow.postMessage({ message: "requestHeightUpdate", instanceId: this.instanceId }, this.targetOrigin);
332
376
  }
@@ -335,11 +379,11 @@
335
379
  }
336
380
 
337
381
  updateMinimizedSize(contentHeight, contentWidth) {
382
+ if (this.isDestroyed) return;
338
383
  if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') return;
339
384
 
340
385
  this.hasMeasuredSize = true;
341
386
 
342
- // Apply height update
343
387
  if (contentHeight && contentHeight >= 10) {
344
388
  const newHeight = `${contentHeight}px`;
345
389
  this.minimizedHeight = newHeight;
@@ -348,9 +392,8 @@
348
392
  }
349
393
  }
350
394
 
351
- // Apply width update
352
395
  if (contentWidth && contentWidth >= 10) {
353
- if (this.minimizedIframe) {
396
+ if (this.minimizedIframe && this.theme !== 'MinimalInput') {
354
397
  this.minimizedIframe.style.width = `${contentWidth}px`;
355
398
  }
356
399
  }
@@ -358,97 +401,127 @@
358
401
 
359
402
  animateMaximize() {
360
403
  if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
361
- // Start the animation for minimizing iframe
362
- this.minimizedIframe.style.height = `${this.height}px`;
363
- this.minimizedIframe.style.opacity = '0';
364
-
365
- setTimeout(() => {
366
- // Hide the minimized iframe
404
+ const onMinimizedAnimateEnd = () => {
405
+ this.minimizedIframe.removeEventListener("transitionend", onMinimizedAnimateEnd);
367
406
  this.minimizedIframe.style.display = "none";
368
- this.minimizedIframe.style.opacity = '1'; // Reset opacity
407
+ this.minimizedIframe.style.opacity = '1';
369
408
 
370
- // Show and animate the main iframe
371
409
  this.iframe.style.display = "block";
372
- this.iframe.style.height = this.minimizedHeight; // Start with minimized height
373
- this.iframe.style.opacity = '0'; // Start with zero opacity
410
+ this.iframe.style.height = this.minimizedHeight;
411
+ this.iframe.style.opacity = "0";
374
412
 
375
- // Trigger the maximize animation
376
- setTimeout(() => {
377
- this.iframe.style.height = `${this.height}px`; // Animate to full height
378
- this.iframe.style.opacity = '1'; // Animate opacity to full
379
- }, 10); // Small delay to allow display changes to take effect
380
- }, this.enableAnimation ? 300 : 0); // Match the minimize animation duration
413
+ this.iframe.offsetHeight;
414
+
415
+ this.iframe.style.height = `${this.height}px`;
416
+ this.iframe.style.opacity = "1";
417
+ };
418
+
419
+ if (this.enableAnimation) {
420
+ this.minimizedIframe.addEventListener("transitionend", onMinimizedAnimateEnd);
421
+ this.minimizedIframe.style.height = this.minimizedHeight;
422
+ this.minimizedIframe.style.opacity = "0";
423
+
424
+ // Notify iframe when maximize transition finishes to ensure final scroll-into-view
425
+ this.iframe.addEventListener("transitionend", (e) => {
426
+ if (e.propertyName === 'height' && this.iframe.style.display !== 'none') {
427
+ if (this.iframe.contentWindow) {
428
+ this.iframe.contentWindow.postMessage({ message: "maximizeComplete", instanceId: this.instanceId }, this.targetOrigin);
429
+ }
430
+ }
431
+ }, { once: true });
432
+ } else {
433
+ onMinimizedAnimateEnd();
434
+ }
381
435
  }
382
436
  }
383
437
 
384
- applyIframeUpdates({ locale, width, height, marginBottom, scale } = {}) {
385
- // Update locale if provided and different (but do not update the iframe URL)
438
+ applyIframeUpdates({ locale, width, height, marginBottom, scale, theme } = {}) {
439
+ if (this.isDestroyed) return;
440
+ if (theme && typeof theme === 'string' && theme !== this.theme) {
441
+ this.theme = theme;
442
+ console.log(`Theme updated to: ${this.theme}`);
443
+ this.minimizedHeight = this.theme === 'MinimalInput' ? "75px" : "80px";
444
+ this.updateIframes();
445
+ return;
446
+ }
447
+
386
448
  if (locale && typeof locale === 'string' && locale !== this.locale) {
387
449
  this.locale = locale;
388
450
  console.log(`Locale stored locally: ${this.locale}`);
389
451
  }
390
452
 
391
- // Update width if provided and different
392
453
  const parsedWidth = parseInt(width, 10);
393
454
  if (!isNaN(parsedWidth) && parsedWidth > 0 && parsedWidth !== this.width) {
394
455
  this.width = parsedWidth;
395
456
  console.log(`Width updated to: ${this.width}px`);
396
457
 
397
- // Apply width only if not in-place
398
458
  if (this.position !== 'in-place') {
399
- this.containerDiv.style.width = `${this.width}px`;
400
- if (this.iframe) {
401
- this.iframe.style.width = `${this.width}px`;
459
+ if (this.theme === 'MinimalInput') {
460
+ this.containerDiv.style.width = '100vw'; // Use 100vw but keep pointerEvents none
461
+ this.containerDiv.style.left = '0';
462
+ this.containerDiv.style.right = '0';
463
+ this.containerDiv.style.marginLeft = 'auto';
464
+ this.containerDiv.style.marginRight = 'auto';
465
+ this.containerDiv.style.pointerEvents = 'none'; // Prevent invisible area from blocking clicks
466
+ if (this.iframe) {
467
+ this.iframe.style.width = '95vw';
468
+ this.iframe.style.maxWidth = `${this.width}px`;
469
+ this.iframe.style.pointerEvents = 'auto';
470
+ this.iframe.style.left = '0';
471
+ this.iframe.style.right = '0';
472
+ this.iframe.style.margin = 'auto';
473
+ }
474
+ if (this.minimizedIframe) {
475
+ this.minimizedIframe.style.width = '100vw'; // Use 100vw to allow centering
476
+ this.minimizedIframe.style.maxWidth = `${Math.min(300, this.width) + 40}px`;
477
+ this.minimizedIframe.style.pointerEvents = 'auto';
478
+ this.minimizedIframe.style.left = '0';
479
+ this.minimizedIframe.style.right = '0';
480
+ this.minimizedIframe.style.margin = 'auto';
481
+ }
482
+ } else {
483
+ this.containerDiv.style.width = `${this.width}px`;
484
+ this.containerDiv.style.pointerEvents = 'auto'; // Ensure auto for other themes
485
+ if (this.iframe) this.iframe.style.width = `${this.width}px`;
402
486
  }
403
487
  } else {
404
488
  this.containerDiv.style.width = '100%';
405
- if (this.iframe) {
406
- this.iframe.style.width = '100%';
407
- }
489
+ this.containerDiv.style.pointerEvents = 'auto';
490
+ if (this.iframe) this.iframe.style.width = '100%';
408
491
  }
409
492
  }
410
493
 
411
- // Apply scale if provided
412
494
  if (typeof scale === 'number' && scale > 0) {
413
495
  this.applyScale(scale);
414
496
  console.log(`Scale applied: ${scale}`);
415
497
  }
416
498
 
417
- // Handle height and marginBottom
418
499
  let parsedHeight = parseInt(height, 10);
419
500
  let parsedMarginBottom = parseInt(marginBottom, 10);
420
501
 
421
- // Default to existing values if not provided or invalid
422
- if (isNaN(parsedHeight) || parsedHeight <= 0) {
423
- parsedHeight = this.height;
424
- }
502
+ if (isNaN(parsedHeight) || parsedHeight <= 0) parsedHeight = this.height;
425
503
  if (isNaN(parsedMarginBottom) || parsedMarginBottom < 0) {
426
504
  parsedMarginBottom = (typeof this.marginBottom === 'number') ? this.marginBottom : 0;
427
505
  }
428
506
 
429
- // Clamp so that height + marginBottom doesn't exceed 90% of viewport
430
507
  const viewportHeight = window.innerHeight;
431
508
  let effectiveHeight = parsedHeight;
432
509
  if (effectiveHeight + parsedMarginBottom > 0.9 * viewportHeight) {
433
510
  effectiveHeight = Math.max(100, 0.9 * viewportHeight - parsedMarginBottom);
434
511
  }
435
512
 
436
- // Only apply if there's an actual change
437
513
  if (parsedHeight !== this.height || parsedMarginBottom !== this.marginBottom) {
438
514
  this.height = parsedHeight;
439
515
  this.marginBottom = parsedMarginBottom;
440
516
 
441
- // Only use the 30% estimate if we haven't received a real measurement yet
442
517
  if (!this.hasMeasuredSize) {
443
- this.minimizedHeight = `${this.height * 0.3}px`;
518
+ this.minimizedHeight = this.theme === 'MinimalInput' ? "75px" : "80px";
444
519
  }
445
520
 
446
- console.log(`Height updated to: ${this.height}px (clamped to ${effectiveHeight}px for display), marginBottom: ${this.marginBottom}px`);
521
+ console.log(`Height updated to: ${this.height}px, marginBottom: ${this.marginBottom}px`);
447
522
 
448
- // Apply the updated height/marginBottom
449
523
  if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
450
524
  if (this.position === 'in-place') {
451
- // "in-place" uses full width, sets containerDiv height
452
525
  this.containerDiv.style.position = 'relative';
453
526
  this.containerDiv.style.width = '100%';
454
527
  this.containerDiv.style.height = `${effectiveHeight}px`;
@@ -467,8 +540,6 @@
467
540
  this.minimizedIframe.style.right = 'unset';
468
541
  }
469
542
  } else {
470
- // position: fixed (typical floating chat)
471
- // Update bottom offset, main height
472
543
  if (this.iframe) {
473
544
  this.iframe.style.height = `${this.height}px`;
474
545
  this.iframe.style.bottom = `${this.marginBottom}px`;
@@ -476,34 +547,38 @@
476
547
  if (this.minimizedIframe) {
477
548
  this.minimizedIframe.style.height = this.minimizedHeight;
478
549
  this.minimizedIframe.style.bottom = `${this.marginBottom}px`;
550
+ if (this.theme === 'MinimalInput') {
551
+ this.minimizedIframe.style.width = '100vw';
552
+ this.minimizedIframe.style.maxWidth = `${Math.min(300, this.width) + 40}px`;
553
+ }
479
554
  }
480
555
  }
481
556
  } else {
482
- // ContactForm mode
483
557
  this.containerDiv.style.height = `${this.height}px`;
484
- if (this.iframe) {
485
- this.iframe.style.height = `${this.height}px`;
486
- }
558
+ if (this.iframe) this.iframe.style.height = `${this.height}px`;
487
559
  }
488
560
  }
489
561
  }
490
562
 
491
- showMaximized(animate = false) {
563
+ showMaximized(animate = false, initialMessage = null) {
564
+ if (this.isDestroyed) return;
492
565
  const schedulePostMessage = () => {
493
566
  if (this.iframe.contentWindow) {
494
567
  this.iframe.contentWindow.postMessage({
495
568
  message: "show",
496
569
  sessionInfo: this.sessionInfo || null,
497
- instanceId: this.instanceId
570
+ instanceId: this.instanceId,
571
+ initialMessage: initialMessage
498
572
  }, this.targetOrigin);
499
573
  } else {
500
- // Schedule a single retry with a delay
501
574
  setTimeout(() => {
575
+ if (this.isDestroyed) return;
502
576
  if (this.iframe.contentWindow) {
503
577
  this.iframe.contentWindow.postMessage({
504
578
  message: "show",
505
579
  sessionInfo: this.sessionInfo || null,
506
- instanceId: this.instanceId
580
+ instanceId: this.instanceId,
581
+ initialMessage: initialMessage
507
582
  }, this.targetOrigin);
508
583
  } else {
509
584
  console.warn("iframe contentWindow is not available.");
@@ -516,8 +591,7 @@
516
591
  this.animateMaximize();
517
592
  } else {
518
593
  this.minimizedIframe.style.display = "none";
519
- this.minimizedIframe.style.height = `${this.height}px`;
520
- this.minimizedIframe.style.opacity = ''; // Reset opacity to unset
594
+ this.minimizedIframe.style.opacity = '';
521
595
 
522
596
  this.iframe.style.display = "block";
523
597
  this.iframe.style.height = `${this.height}px`;
@@ -529,7 +603,8 @@
529
603
 
530
604
  applyScale(scale) {
531
605
  if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
532
- const origin = this.position === 'in-place' ? 'top center' : 'bottom right';
606
+ let origin = this.position === 'in-place' ? 'top center' : 'bottom right';
607
+ if (this.theme === 'MinimalInput') origin = 'bottom center';
533
608
  if (this.iframe) {
534
609
  this.iframe.style.transform = `scale(${scale})`;
535
610
  this.iframe.style.transformOrigin = origin;
@@ -542,16 +617,14 @@
542
617
  }
543
618
 
544
619
  minimizeOnScrollAction() {
620
+ if (this.isDestroyed) return;
545
621
  if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers' && this.iframe.style.display !== "none") {
546
622
  this.animateMinimize();
547
623
  }
548
624
  }
549
625
 
550
626
  setSessionInfo(sessionInfo) {
551
- console.log('setSessionInfo called with sessionInfo:', sessionInfo);
552
627
  this.sessionInfo = sessionInfo;
553
-
554
- // Send session info immediately if the chat is already maximized
555
628
  if (this.iframe?.contentWindow && this.iframe.style.display !== "none") {
556
629
  this.iframe.contentWindow.postMessage({ type: 'setSessionInfo', sessionInfo: this.sessionInfo, instanceId: this.instanceId }, this.targetOrigin);
557
630
  }
@@ -565,78 +638,56 @@
565
638
  return chatContainer;
566
639
  }
567
640
 
641
+ maximizeWithText(text) {
642
+ this.showMaximized(true, text);
643
+ }
644
+
568
645
  buildIframeUrl(baseIframeUrl, params = {}) {
569
646
  const urlParams = new URLSearchParams();
570
-
571
- if (params.isPreview) {
572
- urlParams.set('isPreview', 'true');
573
- }
574
-
647
+ if (params.isPreview) urlParams.set('isPreview', 'true');
575
648
  if (params.mode === 'ContactForm' || params.mode === 'PartnerOffers' || params.mode === 'Chat') {
576
649
  urlParams.set('mode', params.mode);
577
650
  }
578
-
579
- if (params.locale) {
580
- urlParams.set('locale', params.locale);
581
- }
582
-
583
- if (params.conversationId) {
584
- urlParams.set('conversationId', params.conversationId);
585
- }
651
+ if (params.locale) urlParams.set('locale', params.locale);
652
+ if (params.conversationId) urlParams.set('conversationId', params.conversationId);
653
+ if (params.instanceId) urlParams.set('instanceId', params.instanceId);
586
654
 
587
655
  for (const [key, value] of Object.entries(params)) {
588
- if (key !== 'mode' && key !== 'isPreview' && key !== 'conversationId' && key !== 'userToken' && value !== null && value !== undefined) {
656
+ if (!['mode', 'isPreview', 'conversationId', 'instanceId', 'userToken'].includes(key) && value !== null && value !== undefined) {
589
657
  urlParams.set(key, value);
590
658
  }
591
659
  }
592
-
593
660
  return `${baseIframeUrl}?${urlParams.toString()}`;
594
661
  }
595
662
 
596
663
  waitForIframeLoad(iframe, timeout = 10000) {
597
664
  return new Promise((resolve) => {
598
- // Set up a timeout to avoid waiting indefinitely
599
665
  const timeoutId = setTimeout(() => {
600
666
  console.warn('EmbeddedChat: Iframe load timeout, proceeding with initialization.');
601
667
  resolve();
602
668
  }, timeout);
603
669
 
604
- // Add the load event listener
605
670
  iframe.addEventListener('load', () => {
606
- clearTimeout(timeoutId); // Clear the timeout
671
+ clearTimeout(timeoutId);
607
672
  resolve();
608
- }, { once: true }); // Ensure the listener is triggered only once
673
+ }, { once: true });
609
674
  });
610
675
  }
611
676
 
612
677
  async postAuthToIframes() {
613
- console.log('[EmbeddedChat] postAuthToIframes called');
614
- if (typeof this.userTokenProvider !== 'function') {
615
- console.warn('[EmbeddedChat] No userTokenProvider function provided');
616
- return;
617
- }
618
-
678
+ if (this.isDestroyed) return;
679
+ if (typeof this.userTokenProvider !== 'function') return;
619
680
  try {
620
681
  const userToken = await this.userTokenProvider();
621
-
622
- if (!userToken) {
623
- console.warn('[EmbeddedChat] userTokenProvider returned null/empty token');
624
- return;
625
- }
626
-
682
+ if (!userToken) return;
627
683
  if (this.iframe?.contentWindow) {
628
- console.log('[EmbeddedChat] Sending auth message to iframe with targetOrigin:', this.targetOrigin);
629
684
  this.iframe.contentWindow.postMessage({
630
685
  type: 'auth',
631
686
  userToken: userToken,
632
687
  chatId: this.chatId,
633
688
  instanceId: this.instanceId
634
689
  }, this.targetOrigin);
635
- console.log('[EmbeddedChat] Auth message sent successfully');
636
- } else {
637
- console.error('[EmbeddedChat] iframe.contentWindow is not available');
638
690
  }
639
- // No longer send auth to minimizedIframe
640
691
  } catch (err) {
641
692
  console.error('[EmbeddedChat] Failed to get user token:', err);
642
693
  }
@@ -647,9 +698,7 @@
647
698
 
648
699
  console.log(`[EmbeddedChat] Setting mode to ${newMode}`, options);
649
700
  this.mode = newMode;
650
- if (options.chatId) {
651
- this.chatId = options.chatId;
652
- }
701
+ if (options.chatId) this.chatId = options.chatId;
653
702
  this.options = { ...this.options, ...options };
654
703
  const transitionStyle = this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : '';
655
704
  if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') {
@@ -659,7 +708,9 @@
659
708
  } else {
660
709
  this.positionStyle = this.position === 'in-place' ?
661
710
  `position: relative; width:100%; height:${this.height}px; ${transitionStyle}` :
662
- `position: fixed; right: 1em; bottom: 0; width: ${this.width}px; z-index: 100000; max-width:90vw; max-height: 90vh; ${transitionStyle}`;
711
+ (this.theme === 'MinimalInput' ?
712
+ `position: fixed; left: 0; right: 0; margin-left: auto; margin-right: auto; bottom: ${this.marginBottom}px; width: ${this.width}px; z-index: 100000; max-width:90vw; max-height: 90vh; ${transitionStyle}` :
713
+ `position: fixed; right: 1em; bottom: ${this.marginBottom}px; width: ${this.width}px; z-index: 100000; max-width:90vw; max-height: 90vh; ${transitionStyle}`);
663
714
  if (this.position === 'in-place') {
664
715
  this.containerDiv.style.position = 'relative';
665
716
  this.containerDiv.style.width = '100%';
@@ -668,6 +719,27 @@
668
719
  }
669
720
  this.updateIframes();
670
721
  }
722
+
723
+ destroy() {
724
+ if (this.listeners) {
725
+ this.listeners.forEach(l => window.removeEventListener(l.type, l.fn));
726
+ this.listeners = null;
727
+ }
728
+
729
+ this.windowListenersAdded = false;
730
+ this.isDestroyed = true;
731
+
732
+ if (this.containerDiv) {
733
+ if (this.createdContainer && this.containerDiv.parentNode) {
734
+ this.containerDiv.parentNode.removeChild(this.containerDiv);
735
+ } else if (!this.createdContainer) {
736
+ this.containerDiv.innerHTML = '';
737
+ }
738
+ }
739
+ this.iframe = null;
740
+ this.minimizedIframe = null;
741
+ this.containerDiv = null;
742
+ }
671
743
  }
672
744
 
673
745
  export function initEmbeddedChat(containerDiv, chatId, options) {
@@ -684,4 +756,3 @@ export function initOffers(options = {}) {
684
756
  const mergedOptions = { ...options, mode: 'PartnerOffers', height: options.height || 500 };
685
757
  return new EmbeddedChat(options.container, chatId, mergedOptions);
686
758
  }
687
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "embeddedaichatux",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "A lightweight and customizable embedded AI chat UI component that seamlessly integrates into web applications, offering minimized and expanded views, with iframe-based content rendering.",
5
5
  "main": "EmbeddedChat.js",
6
6
  "scripts": {