@buni.ai/chatbot-angular 1.0.14 → 1.0.16

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.
package/dist/index.js CHANGED
@@ -49,7 +49,8 @@ class BuniChatWidget {
49
49
  };
50
50
  this.eventListeners = new Map();
51
51
  this.widgetElement = null;
52
- this.iframe = null;
52
+ this.triggerIframe = null;
53
+ this.chatIframe = null;
53
54
  this.customerData = null;
54
55
  this.sessionVariables = null;
55
56
  }
@@ -95,160 +96,400 @@ class BuniChatWidget {
95
96
  };
96
97
  const widthValue = ensureUnits(config.width, "350px");
97
98
  const heightValue = ensureUnits(config.height, "650px");
98
- // Check if widget should start minimized
99
- const shouldStartMinimized = config.initialMinimized === true;
99
+ // Configuration
100
100
  const showTriggerText = config.showTriggerText !== false;
101
101
  const hasTriggerText = !!config.triggerText;
102
- // Detect if we're on mobile
102
+ const primaryColor = config.primaryColor || "#795548";
103
+ const triggerText = config.triggerText || "";
104
+ const companyName = config.companyName || "Chat Support";
105
+ // Responsive breakpoints
103
106
  const isMobile = window.innerWidth <= 768;
104
- // Calculate initial dimensions based on minimized state
105
- // On mobile, enforce responsive sizing; on desktop, respect custom dimensions
106
- const initialWidth = shouldStartMinimized
107
- ? showTriggerText && hasTriggerText
108
- ? "auto"
109
- : "52px"
110
- : isMobile
111
- ? "min(calc(100vw - 2rem), 370px)"
112
- : widthValue;
113
- const initialHeight = shouldStartMinimized
114
- ? "52px"
115
- : isMobile
116
- ? "min(calc(100vh - 2rem), 580px)"
117
- : heightValue;
118
- const initialMinWidth = shouldStartMinimized && showTriggerText && hasTriggerText ? "auto" : "";
107
+ // Calculate trigger dimensions
108
+ const triggerWidth = showTriggerText && hasTriggerText ? "auto" : "52px";
109
+ const triggerHeight = "52px";
110
+ const triggerMinWidth = showTriggerText && hasTriggerText ? "auto" : "";
111
+ // Container starts as trigger size
119
112
  container.style.cssText = `
120
113
  position: fixed;
121
114
  pointer-events: none;
122
115
  z-index: 999999;
123
- width: ${initialWidth};
124
- height: ${initialHeight};
125
- ${initialMinWidth ? `min-width: ${initialMinWidth};` : ""}
126
- ${isMobile ? "max-width: 370px; max-height: 580px;" : ""}
127
- transition: all 0.3s ease;
128
- ${this.getPositionStyles(config.position || "bottom-right")}
116
+ width: ${triggerWidth};
117
+ height: ${triggerHeight};
118
+ ${triggerMinWidth ? `min-width: ${triggerMinWidth};` : ""}
119
+ transition: width 0.3s ease, height 0.3s ease, border-radius 0.3s ease;
120
+ ${this.getPositionStyles(config.position || "bottom-right", true)}
129
121
  display: ${config.hideDefaultTrigger ? "none" : "block"};
130
- overflow: ${shouldStartMinimized ? "hidden" : "visible"};
122
+ overflow: hidden;
123
+ box-sizing: border-box;
131
124
  `;
132
- // Create iframe for secure embedding
133
- const iframe = document.createElement("iframe");
134
- // Build URL with configuration parameters
135
- const params = new URLSearchParams({
136
- token: this.options.token,
137
- embedded: "true", // Enable postMessage communication
138
- source: "package",
139
- framework: this.options.framework || "vanilla",
140
- });
141
- // Add string parameters only if provided
142
- if (config.theme)
143
- params.set("theme", config.theme);
144
- if (config.primaryColor)
145
- params.set("primaryColor", config.primaryColor);
146
- if (config.secondaryColor)
147
- params.set("secondaryColor", config.secondaryColor);
148
- if (config.position)
149
- params.set("position", config.position);
150
- if (widthValue && config.width !== undefined)
151
- params.set("width", widthValue);
152
- if (heightValue && config.height !== undefined)
153
- params.set("height", heightValue);
154
- if (config.customAvatar)
155
- params.set("customAvatar", config.customAvatar);
156
- if (config.companyName)
157
- params.set("companyName", config.companyName);
158
- if (config.welcomeMessage)
159
- params.set("welcomeMessage", config.welcomeMessage);
160
- if (config.triggerText)
161
- params.set("triggerText", config.triggerText);
162
- if (config.borderRadius)
163
- params.set("borderRadius", config.borderRadius);
164
- if (config.avatarType)
165
- params.set("avatarType", config.avatarType);
166
- if (config.avatarText)
167
- params.set("avatarText", config.avatarText);
168
- // Boolean options - only pass if explicitly defined
169
- if (config.showBranding !== undefined) {
170
- params.set("showBranding", String(config.showBranding));
171
- }
172
- if (config.autoOpen !== undefined) {
173
- params.set("autoOpen", String(config.autoOpen));
174
- }
175
- if (config.minimized !== undefined) {
176
- params.set("minimized", String(config.minimized));
177
- }
178
- if (config.initialMinimized !== undefined) {
179
- params.set("initialMinimized", String(config.initialMinimized));
180
- }
181
- if (config.allowMinimize !== undefined) {
182
- params.set("allowMinimize", String(config.allowMinimize));
183
- }
184
- if (config.showMinimize !== undefined) {
185
- params.set("showMinimize", String(config.showMinimize));
186
- }
187
- if (config.allowClose !== undefined) {
188
- params.set("allowClose", String(config.allowClose));
189
- }
190
- if (config.enableFileUpload !== undefined) {
191
- params.set("enableFileUpload", String(config.enableFileUpload));
192
- }
193
- if (config.showTimestamps !== undefined) {
194
- params.set("showTimestamps", String(config.showTimestamps));
195
- }
196
- if (config.showTriggerText !== undefined) {
197
- params.set("showTriggerText", String(config.showTriggerText));
198
- }
199
- if (config.hideDefaultTrigger !== undefined) {
200
- params.set("hideDefaultTrigger", String(config.hideDefaultTrigger));
201
- }
202
- if (config.enableMobile !== undefined) {
203
- params.set("enableMobile", String(config.enableMobile));
204
- }
205
- // Pre-chat form and start button - only pass if explicitly defined
206
- if (config.showPreChatForm !== undefined) {
207
- params.set("showPreChatForm", String(config.showPreChatForm));
208
- }
209
- if (config.showStartButton !== undefined) {
210
- params.set("showStartButton", String(config.showStartButton));
211
- }
212
- if (config.startButtonText) {
213
- params.set("startButtonText", config.startButtonText);
214
- }
215
- if (config.preChatFormFields) {
216
- params.set("preChatFormFields", JSON.stringify(config.preChatFormFields));
217
- }
218
- iframe.src = `${this.getBaseUrl()}/embed/chat?${params.toString()}`;
219
- // Set styles individually - iframe fills container 100%
220
- iframe.style.position = "absolute";
221
- iframe.style.top = "0";
222
- iframe.style.left = "0";
223
- iframe.style.border = "none";
224
- iframe.style.width = "100%";
225
- iframe.style.height = "100%";
226
- // Use circular border for icon-only minimized, rounded for text button or expanded
227
- iframe.style.borderRadius =
228
- shouldStartMinimized && initialWidth === "52px" ? "50%" : "12px";
229
- iframe.style.transition = "all 0.3s ease";
230
- iframe.style.pointerEvents = "auto";
231
- iframe.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
232
- iframe.setAttribute("allow", "clipboard-write");
233
- iframe.setAttribute("title", "BuniAI Chat Widget");
234
- iframe.onload = () => {
125
+ // Create inline trigger iframe (no src)
126
+ const triggerIframe = document.createElement("iframe");
127
+ triggerIframe.id = "buni-trigger-iframe";
128
+ triggerIframe.style.cssText = `
129
+ position: absolute;
130
+ top: 0;
131
+ left: 0;
132
+ border: none;
133
+ width: 100%;
134
+ height: 100%;
135
+ box-sizing: border-box;
136
+ border-radius: ${showTriggerText && hasTriggerText ? "26px" : "50%"};
137
+ transition: border-radius 0.3s ease, box-shadow 0.3s ease;
138
+ pointer-events: auto;
139
+ `;
140
+ triggerIframe.setAttribute("title", "BuniAI Chat Trigger");
141
+ // Inject trigger HTML content directly (no src)
142
+ container.appendChild(triggerIframe);
143
+ document.body.appendChild(container);
144
+ triggerIframe.onload = () => {
145
+ var _a;
146
+ const triggerDoc = triggerIframe.contentDocument ||
147
+ ((_a = triggerIframe.contentWindow) === null || _a === void 0 ? void 0 : _a.document);
148
+ if (!triggerDoc) {
149
+ reject(new Error("Failed to access trigger iframe document"));
150
+ return;
151
+ }
152
+ // Build inline HTML for trigger
153
+ const triggerHTML = this.buildTriggerHTML(primaryColor, triggerText || companyName, showTriggerText && hasTriggerText, config.customAvatar, config.avatarType, config.avatarText);
154
+ triggerDoc.open();
155
+ triggerDoc.write(triggerHTML);
156
+ triggerDoc.close();
157
+ // Create chat iframe (initially hidden)
158
+ const chatIframe = document.createElement("iframe");
159
+ chatIframe.id = "buni-chat-iframe";
160
+ // Build URL with configuration parameters
161
+ const params = new URLSearchParams({
162
+ token: this.options.token,
163
+ embedded: "true",
164
+ source: "package",
165
+ framework: this.options.framework || "vanilla",
166
+ });
167
+ // Add all configuration parameters
168
+ if (config.theme)
169
+ params.set("theme", config.theme);
170
+ if (config.primaryColor)
171
+ params.set("primaryColor", config.primaryColor);
172
+ if (config.secondaryColor)
173
+ params.set("secondaryColor", config.secondaryColor);
174
+ if (config.position)
175
+ params.set("position", config.position);
176
+ if (widthValue && config.width !== undefined)
177
+ params.set("width", widthValue);
178
+ if (heightValue && config.height !== undefined)
179
+ params.set("height", heightValue);
180
+ if (config.customAvatar)
181
+ params.set("customAvatar", config.customAvatar);
182
+ if (config.companyName)
183
+ params.set("companyName", config.companyName);
184
+ if (config.welcomeMessage)
185
+ params.set("welcomeMessage", config.welcomeMessage);
186
+ if (config.triggerText)
187
+ params.set("triggerText", config.triggerText);
188
+ if (config.borderRadius)
189
+ params.set("borderRadius", config.borderRadius);
190
+ if (config.avatarType)
191
+ params.set("avatarType", config.avatarType);
192
+ if (config.avatarText)
193
+ params.set("avatarText", config.avatarText);
194
+ // Boolean options
195
+ if (config.showBranding !== undefined)
196
+ params.set("showBranding", String(config.showBranding));
197
+ if (config.autoOpen !== undefined)
198
+ params.set("autoOpen", String(config.autoOpen));
199
+ if (config.allowMinimize !== undefined)
200
+ params.set("allowMinimize", String(config.allowMinimize));
201
+ if (config.showMinimize !== undefined)
202
+ params.set("showMinimize", String(config.showMinimize));
203
+ if (config.allowClose !== undefined)
204
+ params.set("allowClose", String(config.allowClose));
205
+ if (config.enableFileUpload !== undefined)
206
+ params.set("enableFileUpload", String(config.enableFileUpload));
207
+ if (config.showTimestamps !== undefined)
208
+ params.set("showTimestamps", String(config.showTimestamps));
209
+ if (config.enableMobile !== undefined)
210
+ params.set("enableMobile", String(config.enableMobile));
211
+ if (config.showPreChatForm !== undefined)
212
+ params.set("showPreChatForm", String(config.showPreChatForm));
213
+ if (config.showStartButton !== undefined)
214
+ params.set("showStartButton", String(config.showStartButton));
215
+ if (config.startButtonText)
216
+ params.set("startButtonText", config.startButtonText);
217
+ if (config.preChatFormFields)
218
+ params.set("preChatFormFields", JSON.stringify(config.preChatFormFields));
219
+ chatIframe.src = `${this.getBaseUrl()}/embed/chat?${params.toString()}`;
220
+ // Chat iframe styling - initially hidden
221
+ chatIframe.style.cssText = `
222
+ position: absolute;
223
+ top: 0;
224
+ left: 0;
225
+ border: none;
226
+ width: 100%;
227
+ height: 100%;
228
+ box-sizing: border-box;
229
+ border-radius: ${isMobile ? "0" : "16px"};
230
+ transition: border-radius 0.3s ease, box-shadow 0.3s ease;
231
+ pointer-events: auto;
232
+ box-shadow: ${isMobile ? "none" : "0 8px 32px rgba(0,0,0,0.15)"};
233
+ display: none;
234
+ `;
235
+ chatIframe.setAttribute("allow", "clipboard-write");
236
+ chatIframe.setAttribute("title", "BuniAI Chat Widget");
237
+ container.appendChild(chatIframe);
238
+ chatIframe.onload = () => {
239
+ // Set visibility hidden after iframe loads to allow content initialization
240
+ chatIframe.style.visibility = "hidden";
241
+ this.setupPostMessageAPI(chatIframe);
242
+ };
235
243
  this.widgetElement = container;
236
- this.iframe = iframe;
237
- this.state.isMinimized = shouldStartMinimized;
238
- this.setupPostMessageAPI(iframe);
244
+ this.triggerIframe = triggerIframe;
245
+ this.chatIframe = chatIframe;
246
+ this.state.isMinimized = true;
247
+ this.state.isLoaded = true;
239
248
  resolve();
240
249
  };
241
- iframe.onerror = () => {
242
- reject(new Error("Failed to load BuniAI widget iframe"));
243
- };
244
- container.appendChild(iframe);
245
- document.body.appendChild(container);
250
+ // Trigger the load event
251
+ triggerIframe.src = "about:blank";
252
+ // Set up communication with trigger iframe
253
+ window.addEventListener("message", (event) => {
254
+ // Check if message is from trigger iframe
255
+ if (event.data.type === "trigger_clicked" &&
256
+ event.source === triggerIframe.contentWindow) {
257
+ this.openChat();
258
+ }
259
+ });
246
260
  });
247
261
  }
248
- getPositionStyles(position) {
249
- // Use smaller margins on mobile for better space utilization
262
+ buildTriggerHTML(primaryColor, text, showText, customAvatar, avatarType, avatarText) {
263
+ const avatarContent = customAvatar
264
+ ? `<img src="${customAvatar}" alt="Avatar" style="width: 32px; height: 32px; border-radius: 50%;" />`
265
+ : avatarType === "text" && avatarText
266
+ ? `<div style="width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,0.3); display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 600; color: white;">${avatarText.substring(0, 2).toUpperCase()}</div>`
267
+ : `<svg width="28" height="28" viewBox="0 0 24 24" fill="white"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1 0-.9-2-2zm0 14H6l-2 2V4h16v12z"/></svg>`;
268
+ return `<!DOCTYPE html>
269
+ <html>
270
+ <head>
271
+ <meta charset="UTF-8">
272
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
273
+ <style>
274
+ * { margin: 0; padding: 0; box-sizing: border-box; }
275
+ body {
276
+ width: 100%;
277
+ height: 100%;
278
+ display: flex;
279
+ align-items: center;
280
+ justify-content: center;
281
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
282
+ overflow: hidden;
283
+ }
284
+ .trigger {
285
+ display: flex;
286
+ align-items: center;
287
+ justify-content: center;
288
+ gap: ${showText ? "12px" : "0"};
289
+ padding: ${showText ? "10px 20px" : "10px"};
290
+ background: ${primaryColor};
291
+ color: white;
292
+ border-radius: ${showText ? "26px" : "50%"};
293
+ cursor: pointer;
294
+ transition: all 0.3s ease;
295
+ box-shadow: 0 4px 16px rgba(0,0,0,0.15);
296
+ animation: pulse 2s infinite;
297
+ ${showText ? "" : "width: 52px; height: 52px;"}
298
+ }
299
+ .trigger:hover {
300
+ transform: translateY(-2px);
301
+ filter: brightness(1.1);
302
+ box-shadow: 0 6px 24px ${primaryColor}99;
303
+ }
304
+ .trigger:active {
305
+ transform: translateY(0);
306
+ }
307
+ .avatar {
308
+ display: flex;
309
+ align-items: center;
310
+ justify-content: center;
311
+ flex-shrink: 0;
312
+ }
313
+ .text {
314
+ font-size: 14px;
315
+ font-weight: 600;
316
+ white-space: nowrap;
317
+ }
318
+ .badge {
319
+ position: absolute;
320
+ top: -4px;
321
+ right: -4px;
322
+ background: #f44336;
323
+ color: white;
324
+ border-radius: 10px;
325
+ padding: 2px 6px;
326
+ font-size: 11px;
327
+ font-weight: 600;
328
+ min-width: 18px;
329
+ text-align: center;
330
+ display: none;
331
+ animation: pulse 2s infinite;
332
+ }
333
+ .badge.show {
334
+ display: block;
335
+ }
336
+ @keyframes pulse {
337
+ 0%, 100% {
338
+ transform: scale(1);
339
+ box-shadow: 0 4px 16px rgba(0,0,0,0.15);
340
+ }
341
+ 50% {
342
+ transform: scale(1.02);
343
+ box-shadow: 0 6px 24px ${primaryColor}99;
344
+ }
345
+ }
346
+ </style>
347
+ </head>
348
+ <body>
349
+ <div style="position: relative;">
350
+ <div class="trigger" onclick="handleClick()">
351
+ <div class="avatar">${avatarContent}</div>
352
+ ${showText ? `<span class="text">${text}</span>` : ""}
353
+ </div>
354
+ <div class="badge" id="badge">0</div>
355
+ </div>
356
+ <script>
357
+ function handleClick() {
358
+ window.parent.postMessage({ type: 'trigger_clicked' }, '*');
359
+ }
360
+
361
+ // Listen for unread count updates
362
+ window.addEventListener('message', function(event) {
363
+ if (event.data.type === 'updateUnreadCount') {
364
+ const badge = document.getElementById('badge');
365
+ const count = event.data.count || 0;
366
+ badge.textContent = count;
367
+ if (count > 0) {
368
+ badge.classList.add('show');
369
+ } else {
370
+ badge.classList.remove('show');
371
+ }
372
+ }
373
+ });
374
+ </script>
375
+ </body>
376
+ </html>`;
377
+ }
378
+ async openChat() {
379
+ if (!this.chatIframe || !this.widgetElement || !this.triggerIframe)
380
+ return;
381
+ if (this.state.isOpen)
382
+ return; // Already open
383
+ const config = this.options.config || {};
250
384
  const isMobile = window.innerWidth <= 768;
251
- const margin = isMobile ? "8px" : "20px";
385
+ const isTablet = window.innerWidth > 768 && window.innerWidth <= 1024;
386
+ const ensureUnits = (value, defaultValue) => {
387
+ if (!value)
388
+ return defaultValue;
389
+ const str = String(value);
390
+ if (str.match(/^[\d.]+\s*(px|em|rem|%|vh|vw)$/))
391
+ return str;
392
+ if (str.match(/^[\d.]+$/))
393
+ return `${str}px`;
394
+ return str;
395
+ };
396
+ const widthValue = ensureUnits(config.width, "350px");
397
+ const heightValue = ensureUnits(config.height, "650px");
398
+ // Calculate chat dimensions
399
+ const chatWidth = isMobile
400
+ ? "100vw"
401
+ : isTablet
402
+ ? "min(calc(100vw - 3rem), 370px)"
403
+ : widthValue;
404
+ const chatHeight = isMobile
405
+ ? "100vh"
406
+ : isTablet
407
+ ? "min(calc(100vh - 3rem), 620px)"
408
+ : heightValue;
409
+ // Resize container for chat
410
+ this.widgetElement.style.cssText = `
411
+ position: fixed;
412
+ pointer-events: none;
413
+ z-index: 999999;
414
+ width: ${chatWidth};
415
+ height: ${chatHeight};
416
+ ${isMobile ? "max-width: 100vw; max-height: 100vh;" : ""}
417
+ transition: width 0.3s ease, height 0.3s ease, border-radius 0.3s ease;
418
+ ${this.getPositionStyles(config.position || "bottom-right", false)}
419
+ display: block;
420
+ overflow: visible;
421
+ box-sizing: border-box;
422
+ `;
423
+ // Hide trigger, show chat
424
+ this.triggerIframe.style.display = "none";
425
+ this.chatIframe.style.display = "block";
426
+ this.chatIframe.style.visibility = "visible";
427
+ this.state.isMinimized = false;
428
+ this.state.isOpen = true;
429
+ this.emit("maximized", { timestamp: Date.now() });
430
+ }
431
+ closeChat() {
432
+ if (!this.chatIframe || !this.widgetElement || !this.triggerIframe)
433
+ return;
434
+ const config = this.options.config || {};
435
+ const showTriggerText = config.showTriggerText !== false;
436
+ const hasTriggerText = !!config.triggerText;
437
+ // Hide chat iframe
438
+ this.chatIframe.style.display = "none";
439
+ this.chatIframe.style.visibility = "hidden";
440
+ // Resize container back to trigger size
441
+ const triggerWidth = showTriggerText && hasTriggerText ? "auto" : "52px";
442
+ const triggerHeight = "52px";
443
+ const triggerMinWidth = showTriggerText && hasTriggerText ? "auto" : "";
444
+ this.widgetElement.style.cssText = `
445
+ position: fixed;
446
+ pointer-events: none;
447
+ z-index: 999999;
448
+ width: ${triggerWidth};
449
+ height: ${triggerHeight};
450
+ ${triggerMinWidth ? `min-width: ${triggerMinWidth};` : ""}
451
+ transition: width 0.3s ease, height 0.3s ease, border-radius 0.3s ease;
452
+ ${this.getPositionStyles(config.position || "bottom-right", true)}
453
+ display: block;
454
+ overflow: hidden;
455
+ box-sizing: border-box;
456
+ `;
457
+ // Show trigger again
458
+ this.triggerIframe.style.display = "block";
459
+ this.state.isMinimized = true;
460
+ this.state.isOpen = false;
461
+ this.emit("minimized", { timestamp: Date.now() });
462
+ }
463
+ getPositionStyles(position, isMinimized = false) {
464
+ // Responsive positioning following mobile-first best practices
465
+ const viewportWidth = window.innerWidth;
466
+ const isMobile = viewportWidth <= 768;
467
+ const isTablet = viewportWidth > 768 && viewportWidth <= 1024;
468
+ // For extra small devices (Galaxy S8+, iPhone SE, etc.), use minimal or no margins
469
+ // For mobile, use small margins for breathing room
470
+ // For tablet/desktop, use larger margins for floating appearance
471
+ let margin;
472
+ if (isMinimized) {
473
+ // Minimized button always needs some space from edges
474
+ margin = isMobile ? "12px" : "20px";
475
+ }
476
+ else if (isMobile) {
477
+ // Full screen on all mobile devices (no margins)
478
+ margin = "0";
479
+ }
480
+ else if (isTablet) {
481
+ // Medium margins on tablets
482
+ margin = "16px";
483
+ }
484
+ else {
485
+ // Larger margins on desktop for floating effect
486
+ margin = "24px";
487
+ }
488
+ // On mobile screens when not minimized, position at edges for full coverage
489
+ if (isMobile && !isMinimized) {
490
+ return `top: 0; left: 0; right: 0; bottom: 0;`;
491
+ }
492
+ // Standard positioning for larger screens or minimized state
252
493
  switch (position) {
253
494
  case "bottom-right":
254
495
  return `bottom: ${margin}; right: ${margin};`;
@@ -265,7 +506,7 @@ class BuniChatWidget {
265
506
  setupPostMessageAPI(iframe) {
266
507
  // Listen for messages from the iframe
267
508
  window.addEventListener("message", (event) => {
268
- var _a, _b;
509
+ var _a;
269
510
  // Verify origin for security
270
511
  if (event.origin !== this.getBaseUrl()) {
271
512
  return;
@@ -296,60 +537,15 @@ class BuniChatWidget {
296
537
  }
297
538
  break;
298
539
  case "minimized":
299
- this.state.isMinimized = true;
300
- if (this.widgetElement && data.dimensions) {
301
- // Resize container to trigger button size when minimized
302
- this.widgetElement.style.width = data.dimensions.width;
303
- this.widgetElement.style.height = data.dimensions.height;
304
- this.widgetElement.style.overflow = "hidden";
305
- if (data.dimensions.minWidth) {
306
- this.widgetElement.style.minWidth = data.dimensions.minWidth;
307
- }
308
- }
309
- if (this.iframe) {
310
- // Use circular border for icon-only, rounded for text button
311
- this.iframe.style.borderRadius =
312
- ((_a = data.dimensions) === null || _a === void 0 ? void 0 : _a.width) === ((_b = data.dimensions) === null || _b === void 0 ? void 0 : _b.height)
313
- ? "50%"
314
- : "26px";
315
- }
316
- this.emit("minimized", data);
540
+ // User clicked minimize in chat - close chat and show trigger
541
+ this.closeChat();
317
542
  break;
318
- case "maximized":
319
- this.state.isMinimized = false;
320
- if (this.widgetElement) {
321
- // Check if we're on mobile
322
- const isMobile = window.innerWidth <= 768;
323
- const config = this.options.config || {};
324
- if (isMobile) {
325
- // On mobile, enforce responsive sizing
326
- this.widgetElement.style.width = "min(calc(100vw - 2rem), 370px)";
327
- this.widgetElement.style.height =
328
- "min(calc(100vh - 2rem), 680px)";
329
- this.widgetElement.style.maxWidth = "370px";
330
- this.widgetElement.style.maxHeight = "680px";
331
- }
332
- else {
333
- // On desktop, respect custom dimensions
334
- const ensureUnits = (value, defaultValue) => {
335
- if (!value)
336
- return defaultValue;
337
- const str = String(value);
338
- if (str.match(/^[\d.]+\s*(px|em|rem|%|vh|vw)$/))
339
- return str;
340
- if (str.match(/^[\d.]+$/))
341
- return `${str}px`;
342
- return str;
343
- };
344
- this.widgetElement.style.width = ensureUnits(config.width, "350px");
345
- this.widgetElement.style.height = ensureUnits(config.height, "650px");
346
- }
347
- this.widgetElement.style.overflow = "visible";
543
+ case "new_unread_message":
544
+ // Update unread count in trigger
545
+ this.state.unreadCount++;
546
+ if ((_a = this.triggerIframe) === null || _a === void 0 ? void 0 : _a.contentWindow) {
547
+ this.triggerIframe.contentWindow.postMessage({ type: "updateUnreadCount", count: this.state.unreadCount }, "*");
348
548
  }
349
- if (this.iframe) {
350
- this.iframe.style.borderRadius = "12px";
351
- }
352
- this.emit("maximized", data);
353
549
  break;
354
550
  case "customer_data_updated":
355
551
  this.customerData = data;
@@ -370,8 +566,8 @@ class BuniChatWidget {
370
566
  }
371
567
  postMessageToWidget(type, data) {
372
568
  var _a;
373
- if (this.widgetElement && this.widgetElement instanceof HTMLIFrameElement) {
374
- (_a = this.widgetElement.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage({ type, data }, this.getBaseUrl());
569
+ if ((_a = this.chatIframe) === null || _a === void 0 ? void 0 : _a.contentWindow) {
570
+ this.chatIframe.contentWindow.postMessage({ type, data }, this.getBaseUrl());
375
571
  }
376
572
  }
377
573
  getBaseUrl() {
@@ -384,44 +580,49 @@ class BuniChatWidget {
384
580
  this.widgetElement.remove();
385
581
  this.widgetElement = null;
386
582
  }
583
+ this.triggerIframe = null;
584
+ this.chatIframe = null;
387
585
  this.eventListeners.clear();
388
586
  this.state.isLoaded = false;
389
587
  this.customerData = null;
390
588
  this.sessionVariables = null;
391
589
  }
392
590
  show() {
393
- // Show the iframe if it was hidden (hideDefaultTrigger mode)
394
- if (this.widgetElement instanceof HTMLIFrameElement) {
591
+ // Show the widget container if it was hidden (hideDefaultTrigger mode)
592
+ if (this.widgetElement) {
395
593
  this.widgetElement.style.display = "block";
396
594
  }
397
- this.postMessageToWidget("show");
398
- this.state.isOpen = true;
399
- this.state.unreadCount = 0;
400
- this.emit("visibility_changed", { visibility: "visible" });
595
+ // Open chat if not already open
596
+ if (!this.chatIframe && !this.state.isOpen) {
597
+ this.openChat();
598
+ }
401
599
  }
402
600
  hide() {
403
601
  var _a;
404
- this.postMessageToWidget("hide");
405
- // If hideDefaultTrigger is enabled, completely hide the iframe
406
- if (((_a = this.options.config) === null || _a === void 0 ? void 0 : _a.hideDefaultTrigger) &&
407
- this.widgetElement instanceof HTMLIFrameElement) {
602
+ // Close chat if open
603
+ if (this.chatIframe) {
604
+ this.closeChat();
605
+ }
606
+ // If hideDefaultTrigger is enabled, completely hide the container
607
+ if (((_a = this.options.config) === null || _a === void 0 ? void 0 : _a.hideDefaultTrigger) && this.widgetElement) {
408
608
  this.widgetElement.style.display = "none";
409
609
  }
410
610
  this.state.isOpen = false;
411
611
  this.emit("visibility_changed", { visibility: "hidden" });
412
612
  }
413
613
  toggle() {
414
- this.state.isOpen ? this.hide() : this.show();
614
+ if (this.chatIframe || this.state.isOpen) {
615
+ this.closeChat();
616
+ }
617
+ else {
618
+ this.openChat();
619
+ }
415
620
  }
416
621
  minimize() {
417
- this.postMessageToWidget("minimize");
418
- this.state.isMinimized = true;
419
- this.emit("minimized", { timestamp: Date.now() });
622
+ this.closeChat();
420
623
  }
421
624
  maximize() {
422
- this.postMessageToWidget("maximize");
423
- this.state.isMinimized = false;
424
- this.emit("maximized", { timestamp: Date.now() });
625
+ this.openChat();
425
626
  }
426
627
  setCustomerData(data) {
427
628
  this.customerData = { ...this.customerData, ...data };