@buni.ai/chatbot-core 1.0.11 → 1.0.13

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