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