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