@buni.ai/chatbot-angular 1.0.15 → 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,193 +96,373 @@ 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
- // Responsive breakpoints following mobile-first best practices
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
- const isExtraSmall = window.innerWidth <= 375;
105
- const isTablet = window.innerWidth > 768 && window.innerWidth <= 1024;
106
- // Calculate initial dimensions based on minimized state with responsive scaling
107
- // Mobile-first approach: Full viewport on mobile, constrained on desktop
108
- const initialWidth = shouldStartMinimized
109
- ? showTriggerText && hasTriggerText
110
- ? "auto"
111
- : "52px"
112
- : isExtraSmall
113
- ? "100vw" // Full width on very small devices
114
- : isMobile
115
- ? "min(100vw, 370px)" // Constrained width on mobile
116
- : isTablet
117
- ? "min(calc(100vw - 3rem), 370px)" // Slightly smaller on tablets
118
- : widthValue; // Custom width on desktop
119
- const initialHeight = shouldStartMinimized
120
- ? "52px"
121
- : isExtraSmall
122
- ? "100vh" // Full height on very small devices
123
- : isMobile
124
- ? "min(100vh, 600px)" // Constrained height on mobile
125
- : isTablet
126
- ? "min(calc(100vh - 3rem), 620px)" // Slightly smaller on tablets
127
- : heightValue; // Custom height on desktop
128
- const initialMinWidth = shouldStartMinimized && showTriggerText && hasTriggerText ? "auto" : "";
129
- // Use CSS custom properties for dynamic styling
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
130
112
  container.style.cssText = `
131
113
  position: fixed;
132
114
  pointer-events: none;
133
115
  z-index: 999999;
134
- width: ${initialWidth};
135
- height: ${initialHeight};
136
- ${initialMinWidth ? `min-width: ${initialMinWidth};` : ""}
137
- ${isMobile && !shouldStartMinimized ? "max-width: 100vw; max-height: 100vh;" : ""}
116
+ width: ${triggerWidth};
117
+ height: ${triggerHeight};
118
+ ${triggerMinWidth ? `min-width: ${triggerMinWidth};` : ""}
138
119
  transition: width 0.3s ease, height 0.3s ease, border-radius 0.3s ease;
139
- ${this.getPositionStyles(config.position || "bottom-right", shouldStartMinimized)}
120
+ ${this.getPositionStyles(config.position || "bottom-right", true)}
140
121
  display: ${config.hideDefaultTrigger ? "none" : "block"};
141
- overflow: ${shouldStartMinimized ? "hidden" : "visible"};
122
+ overflow: hidden;
142
123
  box-sizing: border-box;
143
124
  `;
144
- // Create iframe for secure embedding
145
- const iframe = document.createElement("iframe");
146
- // Build URL with configuration parameters
147
- const params = new URLSearchParams({
148
- token: this.options.token,
149
- embedded: "true", // Enable postMessage communication
150
- source: "package",
151
- framework: this.options.framework || "vanilla",
152
- });
153
- // Add string parameters only if provided
154
- if (config.theme)
155
- params.set("theme", config.theme);
156
- if (config.primaryColor)
157
- params.set("primaryColor", config.primaryColor);
158
- if (config.secondaryColor)
159
- params.set("secondaryColor", config.secondaryColor);
160
- if (config.position)
161
- params.set("position", config.position);
162
- if (widthValue && config.width !== undefined)
163
- params.set("width", widthValue);
164
- if (heightValue && config.height !== undefined)
165
- params.set("height", heightValue);
166
- if (config.customAvatar)
167
- params.set("customAvatar", config.customAvatar);
168
- if (config.companyName)
169
- params.set("companyName", config.companyName);
170
- if (config.welcomeMessage)
171
- params.set("welcomeMessage", config.welcomeMessage);
172
- if (config.triggerText)
173
- params.set("triggerText", config.triggerText);
174
- if (config.borderRadius)
175
- params.set("borderRadius", config.borderRadius);
176
- if (config.avatarType)
177
- params.set("avatarType", config.avatarType);
178
- if (config.avatarText)
179
- params.set("avatarText", config.avatarText);
180
- // Boolean options - only pass if explicitly defined
181
- if (config.showBranding !== undefined) {
182
- params.set("showBranding", String(config.showBranding));
183
- }
184
- if (config.autoOpen !== undefined) {
185
- params.set("autoOpen", String(config.autoOpen));
186
- }
187
- if (config.minimized !== undefined) {
188
- params.set("minimized", String(config.minimized));
189
- }
190
- if (config.initialMinimized !== undefined) {
191
- params.set("initialMinimized", String(config.initialMinimized));
192
- }
193
- if (config.allowMinimize !== undefined) {
194
- params.set("allowMinimize", String(config.allowMinimize));
195
- }
196
- if (config.showMinimize !== undefined) {
197
- params.set("showMinimize", String(config.showMinimize));
198
- }
199
- if (config.allowClose !== undefined) {
200
- params.set("allowClose", String(config.allowClose));
201
- }
202
- if (config.enableFileUpload !== undefined) {
203
- params.set("enableFileUpload", String(config.enableFileUpload));
204
- }
205
- if (config.showTimestamps !== undefined) {
206
- params.set("showTimestamps", String(config.showTimestamps));
207
- }
208
- if (config.showTriggerText !== undefined) {
209
- params.set("showTriggerText", String(config.showTriggerText));
210
- }
211
- if (config.hideDefaultTrigger !== undefined) {
212
- params.set("hideDefaultTrigger", String(config.hideDefaultTrigger));
213
- }
214
- if (config.enableMobile !== undefined) {
215
- params.set("enableMobile", String(config.enableMobile));
216
- }
217
- // Pre-chat form and start button - only pass if explicitly defined
218
- if (config.showPreChatForm !== undefined) {
219
- params.set("showPreChatForm", String(config.showPreChatForm));
220
- }
221
- if (config.showStartButton !== undefined) {
222
- params.set("showStartButton", String(config.showStartButton));
223
- }
224
- if (config.startButtonText) {
225
- params.set("startButtonText", config.startButtonText);
226
- }
227
- if (config.preChatFormFields) {
228
- params.set("preChatFormFields", JSON.stringify(config.preChatFormFields));
229
- }
230
- iframe.src = `${this.getBaseUrl()}/embed/chat?${params.toString()}`;
231
- // Responsive iframe styling with smooth transitions
232
- iframe.style.position = "absolute";
233
- iframe.style.top = "0";
234
- iframe.style.left = "0";
235
- iframe.style.border = "none";
236
- iframe.style.width = "100%";
237
- iframe.style.height = "100%";
238
- iframe.style.boxSizing = "border-box";
239
- // Adaptive border radius: circular for icon, rounded for button/expanded
240
- // No radius on extra small full-screen mode
241
- if (isExtraSmall && !shouldStartMinimized) {
242
- iframe.style.borderRadius = "0"; // Full screen, no radius
243
- }
244
- else if (shouldStartMinimized && initialWidth === "52px") {
245
- iframe.style.borderRadius = "50%"; // Circular for icon-only
246
- }
247
- else if (shouldStartMinimized) {
248
- iframe.style.borderRadius = "26px"; // Pill shape for text button
249
- }
250
- else {
251
- iframe.style.borderRadius = isMobile ? "0" : "16px"; // Rounded on desktop, square on mobile
252
- }
253
- iframe.style.transition = "border-radius 0.3s ease, box-shadow 0.3s ease";
254
- iframe.style.pointerEvents = "auto";
255
- // Adaptive shadow: larger on desktop, minimal on mobile
256
- if (shouldStartMinimized) {
257
- iframe.style.boxShadow = "0 4px 16px rgba(0,0,0,0.15)";
258
- }
259
- else if (isMobile) {
260
- iframe.style.boxShadow = isExtraSmall ? "none" : "0 2px 8px rgba(0,0,0,0.1)";
261
- }
262
- else {
263
- iframe.style.boxShadow = "0 8px 32px rgba(0,0,0,0.15)";
264
- }
265
- iframe.setAttribute("allow", "clipboard-write");
266
- iframe.setAttribute("title", "BuniAI Chat Widget");
267
- 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
+ };
268
243
  this.widgetElement = container;
269
- this.iframe = iframe;
270
- this.state.isMinimized = shouldStartMinimized;
271
- this.setupPostMessageAPI(iframe);
244
+ this.triggerIframe = triggerIframe;
245
+ this.chatIframe = chatIframe;
246
+ this.state.isMinimized = true;
247
+ this.state.isLoaded = true;
272
248
  resolve();
273
249
  };
274
- iframe.onerror = () => {
275
- reject(new Error("Failed to load BuniAI widget iframe"));
276
- };
277
- container.appendChild(iframe);
278
- 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
+ });
279
260
  });
280
261
  }
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 || {};
384
+ const isMobile = window.innerWidth <= 768;
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
+ }
281
463
  getPositionStyles(position, isMinimized = false) {
282
464
  // Responsive positioning following mobile-first best practices
283
465
  const viewportWidth = window.innerWidth;
284
- const isExtraSmall = viewportWidth <= 375;
285
466
  const isMobile = viewportWidth <= 768;
286
467
  const isTablet = viewportWidth > 768 && viewportWidth <= 1024;
287
468
  // For extra small devices (Galaxy S8+, iPhone SE, etc.), use minimal or no margins
@@ -292,13 +473,9 @@ class BuniChatWidget {
292
473
  // Minimized button always needs some space from edges
293
474
  margin = isMobile ? "12px" : "20px";
294
475
  }
295
- else if (isExtraSmall) {
296
- // Full screen on extra small devices (no margins)
297
- margin = "0";
298
- }
299
476
  else if (isMobile) {
300
- // Small margins on mobile for breathing room
301
- margin = "8px";
477
+ // Full screen on all mobile devices (no margins)
478
+ margin = "0";
302
479
  }
303
480
  else if (isTablet) {
304
481
  // Medium margins on tablets
@@ -308,18 +485,9 @@ class BuniChatWidget {
308
485
  // Larger margins on desktop for floating effect
309
486
  margin = "24px";
310
487
  }
311
- // On extra small screens when not minimized, position at edges for full coverage
312
- if (isExtraSmall && !isMinimized) {
313
- switch (position) {
314
- case "bottom-right":
315
- case "bottom-left":
316
- return `bottom: 0; left: 0; right: 0;`;
317
- case "top-right":
318
- case "top-left":
319
- return `top: 0; left: 0; right: 0;`;
320
- default:
321
- return `bottom: 0; left: 0; right: 0;`;
322
- }
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;`;
323
491
  }
324
492
  // Standard positioning for larger screens or minimized state
325
493
  switch (position) {
@@ -338,7 +506,7 @@ class BuniChatWidget {
338
506
  setupPostMessageAPI(iframe) {
339
507
  // Listen for messages from the iframe
340
508
  window.addEventListener("message", (event) => {
341
- var _a, _b;
509
+ var _a;
342
510
  // Verify origin for security
343
511
  if (event.origin !== this.getBaseUrl()) {
344
512
  return;
@@ -369,60 +537,15 @@ class BuniChatWidget {
369
537
  }
370
538
  break;
371
539
  case "minimized":
372
- this.state.isMinimized = true;
373
- if (this.widgetElement && data.dimensions) {
374
- // Resize container to trigger button size when minimized
375
- this.widgetElement.style.width = data.dimensions.width;
376
- this.widgetElement.style.height = data.dimensions.height;
377
- this.widgetElement.style.overflow = "hidden";
378
- if (data.dimensions.minWidth) {
379
- this.widgetElement.style.minWidth = data.dimensions.minWidth;
380
- }
381
- }
382
- if (this.iframe) {
383
- // Use circular border for icon-only, rounded for text button
384
- this.iframe.style.borderRadius =
385
- ((_a = data.dimensions) === null || _a === void 0 ? void 0 : _a.width) === ((_b = data.dimensions) === null || _b === void 0 ? void 0 : _b.height)
386
- ? "50%"
387
- : "26px";
388
- }
389
- this.emit("minimized", data);
540
+ // User clicked minimize in chat - close chat and show trigger
541
+ this.closeChat();
390
542
  break;
391
- case "maximized":
392
- this.state.isMinimized = false;
393
- if (this.widgetElement) {
394
- // Check if we're on mobile
395
- const isMobile = window.innerWidth <= 768;
396
- const config = this.options.config || {};
397
- if (isMobile) {
398
- // On mobile, enforce responsive sizing
399
- this.widgetElement.style.width = "min(calc(100vw - 2rem), 370px)";
400
- this.widgetElement.style.height =
401
- "min(calc(100vh - 2rem), 680px)";
402
- this.widgetElement.style.maxWidth = "370px";
403
- this.widgetElement.style.maxHeight = "680px";
404
- }
405
- else {
406
- // On desktop, respect custom dimensions
407
- const ensureUnits = (value, defaultValue) => {
408
- if (!value)
409
- return defaultValue;
410
- const str = String(value);
411
- if (str.match(/^[\d.]+\s*(px|em|rem|%|vh|vw)$/))
412
- return str;
413
- if (str.match(/^[\d.]+$/))
414
- return `${str}px`;
415
- return str;
416
- };
417
- this.widgetElement.style.width = ensureUnits(config.width, "350px");
418
- this.widgetElement.style.height = ensureUnits(config.height, "650px");
419
- }
420
- this.widgetElement.style.overflow = "visible";
421
- }
422
- if (this.iframe) {
423
- this.iframe.style.borderRadius = "12px";
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 }, "*");
424
548
  }
425
- this.emit("maximized", data);
426
549
  break;
427
550
  case "customer_data_updated":
428
551
  this.customerData = data;
@@ -443,8 +566,8 @@ class BuniChatWidget {
443
566
  }
444
567
  postMessageToWidget(type, data) {
445
568
  var _a;
446
- if (this.widgetElement && this.widgetElement instanceof HTMLIFrameElement) {
447
- (_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());
448
571
  }
449
572
  }
450
573
  getBaseUrl() {
@@ -457,44 +580,49 @@ class BuniChatWidget {
457
580
  this.widgetElement.remove();
458
581
  this.widgetElement = null;
459
582
  }
583
+ this.triggerIframe = null;
584
+ this.chatIframe = null;
460
585
  this.eventListeners.clear();
461
586
  this.state.isLoaded = false;
462
587
  this.customerData = null;
463
588
  this.sessionVariables = null;
464
589
  }
465
590
  show() {
466
- // Show the iframe if it was hidden (hideDefaultTrigger mode)
467
- if (this.widgetElement instanceof HTMLIFrameElement) {
591
+ // Show the widget container if it was hidden (hideDefaultTrigger mode)
592
+ if (this.widgetElement) {
468
593
  this.widgetElement.style.display = "block";
469
594
  }
470
- this.postMessageToWidget("show");
471
- this.state.isOpen = true;
472
- this.state.unreadCount = 0;
473
- this.emit("visibility_changed", { visibility: "visible" });
595
+ // Open chat if not already open
596
+ if (!this.chatIframe && !this.state.isOpen) {
597
+ this.openChat();
598
+ }
474
599
  }
475
600
  hide() {
476
601
  var _a;
477
- this.postMessageToWidget("hide");
478
- // If hideDefaultTrigger is enabled, completely hide the iframe
479
- if (((_a = this.options.config) === null || _a === void 0 ? void 0 : _a.hideDefaultTrigger) &&
480
- 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) {
481
608
  this.widgetElement.style.display = "none";
482
609
  }
483
610
  this.state.isOpen = false;
484
611
  this.emit("visibility_changed", { visibility: "hidden" });
485
612
  }
486
613
  toggle() {
487
- this.state.isOpen ? this.hide() : this.show();
614
+ if (this.chatIframe || this.state.isOpen) {
615
+ this.closeChat();
616
+ }
617
+ else {
618
+ this.openChat();
619
+ }
488
620
  }
489
621
  minimize() {
490
- this.postMessageToWidget("minimize");
491
- this.state.isMinimized = true;
492
- this.emit("minimized", { timestamp: Date.now() });
622
+ this.closeChat();
493
623
  }
494
624
  maximize() {
495
- this.postMessageToWidget("maximize");
496
- this.state.isMinimized = false;
497
- this.emit("maximized", { timestamp: Date.now() });
625
+ this.openChat();
498
626
  }
499
627
  setCustomerData(data) {
500
628
  this.customerData = { ...this.customerData, ...data };