@weldsuite/helpdesk-widget-sdk 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.
@@ -510,14 +510,16 @@ class IframeManager {
510
510
  const container = document.createElement('div');
511
511
  container.className = 'weld-launcher-frame';
512
512
  container.setAttribute('data-state', 'visible');
513
+ // Container is larger than the button to allow hover animations (scale, shadow) without clipping
514
+ const launcherPadding = 10;
513
515
  container.style.cssText = `
514
516
  position: fixed;
515
- bottom: ${launcher.position.bottom};
516
- right: ${launcher.position.right};
517
- width: ${launcher.size};
518
- height: ${launcher.size};
517
+ bottom: calc(${launcher.position.bottom} - ${launcherPadding}px);
518
+ right: calc(${launcher.position.right} - ${launcherPadding}px);
519
+ width: calc(${launcher.size} + ${launcherPadding * 2}px);
520
+ height: calc(${launcher.size} + ${launcherPadding * 2}px);
519
521
  z-index: 2147483003;
520
- pointer-events: auto;
522
+ pointer-events: none;
521
523
  display: block;
522
524
  `;
523
525
  // Create iframe
@@ -531,6 +533,7 @@ class IframeManager {
531
533
  border: none;
532
534
  background: transparent;
533
535
  display: block;
536
+ pointer-events: auto;
534
537
  `;
535
538
  iframe.setAttribute('allow', 'clipboard-write');
536
539
  iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
@@ -651,43 +654,10 @@ class IframeManager {
651
654
  this.logger.debug('Widget iframe created');
652
655
  }
653
656
  /**
654
- * Create backdrop iframe
657
+ * Create backdrop iframe — disabled, widget stays non-modal so users can interact with the page
655
658
  */
656
659
  async createBackdropIframe() {
657
- if (!this.config.iframes.backdrop?.enabled) {
658
- this.logger.debug('Backdrop disabled, skipping creation');
659
- return;
660
- }
661
- // Create container
662
- const container = document.createElement('div');
663
- container.className = 'weld-backdrop-frame';
664
- container.setAttribute('data-state', 'hidden');
665
- container.style.cssText = `
666
- position: fixed;
667
- top: 0;
668
- left: 0;
669
- right: 0;
670
- bottom: 0;
671
- z-index: 2147483000;
672
- background: transparent;
673
- pointer-events: none;
674
- opacity: 0;
675
- transition: opacity 200ms ease;
676
- `;
677
- this.appContainer?.appendChild(container);
678
- // Store metadata (backdrop doesn't have an iframe, just a div)
679
- // We'll create a minimal "iframe" reference for consistency
680
- const dummyIframe = document.createElement('iframe');
681
- dummyIframe.style.display = 'none';
682
- this.iframes.set(IframeType.BACKDROP, {
683
- type: IframeType.BACKDROP,
684
- element: dummyIframe,
685
- container,
686
- ready: true, // Backdrop is always ready
687
- visible: false,
688
- createdAt: Date.now(),
689
- });
690
- this.logger.debug('Backdrop created');
660
+ this.logger.debug('Backdrop disabled, skipping creation');
691
661
  }
692
662
  /**
693
663
  * Build iframe URL with parameters
@@ -1025,11 +995,7 @@ class SecurityManager {
1025
995
  * Validate message origin
1026
996
  */
1027
997
  isOriginAllowed(origin) {
1028
- // If no allowed origins specified, only allow same origin
1029
- if (this.config.allowedOrigins?.length === 0) {
1030
- return origin === window.location.origin;
1031
- }
1032
- // Check if origin is in allowed list
998
+ // Check if origin is in allowed list (includes same-origin and programmatically added origins)
1033
999
  if (this.allowedOrigins.has(origin)) {
1034
1000
  return true;
1035
1001
  }
@@ -2192,7 +2158,7 @@ class StateCoordinator {
2192
2158
  }
2193
2159
  }
2194
2160
 
2195
- var version = "1.0.14";
2161
+ var version = "1.0.16";
2196
2162
  var packageJson = {
2197
2163
  version: version};
2198
2164
 
@@ -2275,6 +2241,242 @@ class WeldSDK {
2275
2241
  console.log('[Weld SDK] Widget close requested');
2276
2242
  this.close();
2277
2243
  }
2244
+ if (event.data?.type === 'weld:image:open' && event.data?.url) {
2245
+ this.showImageLightbox(event.data.url);
2246
+ }
2247
+ }
2248
+ /**
2249
+ * Show fullscreen image lightbox on the parent page
2250
+ */
2251
+ showImageLightbox(url) {
2252
+ // Remove existing lightbox if any
2253
+ const existing = document.getElementById('weld-image-lightbox');
2254
+ if (existing)
2255
+ existing.remove();
2256
+ // Zoom / pan state
2257
+ let scale = 1;
2258
+ let translateX = 0;
2259
+ let translateY = 0;
2260
+ let isDragging = false;
2261
+ let dragStartX = 0;
2262
+ let dragStartY = 0;
2263
+ let lastTranslateX = 0;
2264
+ let lastTranslateY = 0;
2265
+ const applyTransform = () => {
2266
+ img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
2267
+ };
2268
+ const resetTransform = () => {
2269
+ scale = 1;
2270
+ translateX = 0;
2271
+ translateY = 0;
2272
+ applyTransform();
2273
+ img.style.cursor = 'zoom-in';
2274
+ };
2275
+ const overlay = document.createElement('div');
2276
+ overlay.id = 'weld-image-lightbox';
2277
+ overlay.style.cssText = `
2278
+ position: fixed;
2279
+ inset: 0;
2280
+ z-index: 2147483647;
2281
+ background: rgba(0, 0, 0, 0.92);
2282
+ display: flex;
2283
+ align-items: center;
2284
+ justify-content: center;
2285
+ padding: 16px;
2286
+ cursor: pointer;
2287
+ overflow: hidden;
2288
+ `;
2289
+ // Close button
2290
+ const closeBtn = document.createElement('button');
2291
+ closeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`;
2292
+ closeBtn.style.cssText = `
2293
+ position: absolute;
2294
+ top: 16px;
2295
+ right: 16px;
2296
+ width: 40px;
2297
+ height: 40px;
2298
+ border-radius: 50%;
2299
+ border: none;
2300
+ background: rgba(255, 255, 255, 0.1);
2301
+ color: white;
2302
+ cursor: pointer;
2303
+ display: flex;
2304
+ align-items: center;
2305
+ justify-content: center;
2306
+ transition: background 0.15s;
2307
+ `;
2308
+ closeBtn.onmouseenter = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.2)'; };
2309
+ closeBtn.onmouseleave = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.1)'; };
2310
+ // Download button
2311
+ const downloadBtn = document.createElement('a');
2312
+ downloadBtn.href = url;
2313
+ downloadBtn.download = '';
2314
+ downloadBtn.target = '_blank';
2315
+ downloadBtn.rel = 'noopener noreferrer';
2316
+ downloadBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>`;
2317
+ downloadBtn.style.cssText = `
2318
+ position: absolute;
2319
+ top: 16px;
2320
+ right: 64px;
2321
+ width: 40px;
2322
+ height: 40px;
2323
+ border-radius: 50%;
2324
+ border: none;
2325
+ background: rgba(255, 255, 255, 0.1);
2326
+ color: white;
2327
+ cursor: pointer;
2328
+ display: flex;
2329
+ align-items: center;
2330
+ justify-content: center;
2331
+ transition: background 0.15s;
2332
+ text-decoration: none;
2333
+ `;
2334
+ downloadBtn.onmouseenter = () => { downloadBtn.style.background = 'rgba(255, 255, 255, 0.2)'; };
2335
+ downloadBtn.onmouseleave = () => { downloadBtn.style.background = 'rgba(255, 255, 255, 0.1)'; };
2336
+ // Image
2337
+ const img = document.createElement('img');
2338
+ img.src = url;
2339
+ img.alt = 'Full size';
2340
+ img.draggable = false;
2341
+ img.style.cssText = `
2342
+ max-width: 100%;
2343
+ max-height: 100%;
2344
+ object-fit: contain;
2345
+ border-radius: 8px;
2346
+ cursor: zoom-in;
2347
+ transition: transform 0.2s ease;
2348
+ user-select: none;
2349
+ `;
2350
+ // Click to toggle zoom
2351
+ img.addEventListener('click', (e) => {
2352
+ e.stopPropagation();
2353
+ if (scale === 1) {
2354
+ // Zoom in to 2.5x centered on click position
2355
+ const rect = img.getBoundingClientRect();
2356
+ const clickX = e.clientX - rect.left - rect.width / 2;
2357
+ const clickY = e.clientY - rect.top - rect.height / 2;
2358
+ scale = 2.5;
2359
+ translateX = -clickX * 1.5;
2360
+ translateY = -clickY * 1.5;
2361
+ applyTransform();
2362
+ img.style.cursor = 'zoom-out';
2363
+ }
2364
+ else {
2365
+ // Zoom out - reset
2366
+ resetTransform();
2367
+ }
2368
+ });
2369
+ // Mouse wheel zoom
2370
+ overlay.addEventListener('wheel', (e) => {
2371
+ e.preventDefault();
2372
+ const delta = e.deltaY > 0 ? -0.25 : 0.25;
2373
+ const newScale = Math.min(Math.max(scale + delta, 1), 5);
2374
+ if (newScale === 1) {
2375
+ resetTransform();
2376
+ }
2377
+ else {
2378
+ scale = newScale;
2379
+ applyTransform();
2380
+ img.style.cursor = 'zoom-out';
2381
+ }
2382
+ }, { passive: false });
2383
+ // Drag to pan when zoomed
2384
+ img.addEventListener('mousedown', (e) => {
2385
+ if (scale <= 1)
2386
+ return;
2387
+ e.preventDefault();
2388
+ isDragging = true;
2389
+ dragStartX = e.clientX;
2390
+ dragStartY = e.clientY;
2391
+ lastTranslateX = translateX;
2392
+ lastTranslateY = translateY;
2393
+ img.style.cursor = 'grabbing';
2394
+ img.style.transition = 'none';
2395
+ });
2396
+ window.addEventListener('mousemove', (e) => {
2397
+ if (!isDragging)
2398
+ return;
2399
+ translateX = lastTranslateX + (e.clientX - dragStartX);
2400
+ translateY = lastTranslateY + (e.clientY - dragStartY);
2401
+ applyTransform();
2402
+ });
2403
+ window.addEventListener('mouseup', () => {
2404
+ if (!isDragging)
2405
+ return;
2406
+ isDragging = false;
2407
+ img.style.cursor = scale > 1 ? 'zoom-out' : 'zoom-in';
2408
+ img.style.transition = 'transform 0.2s ease';
2409
+ });
2410
+ // Touch: pinch to zoom + drag to pan
2411
+ let lastTouchDist = 0;
2412
+ let lastTouchScale = 1;
2413
+ overlay.addEventListener('touchstart', (e) => {
2414
+ if (e.touches.length === 2) {
2415
+ e.preventDefault();
2416
+ const dx = e.touches[0].clientX - e.touches[1].clientX;
2417
+ const dy = e.touches[0].clientY - e.touches[1].clientY;
2418
+ lastTouchDist = Math.hypot(dx, dy);
2419
+ lastTouchScale = scale;
2420
+ }
2421
+ else if (e.touches.length === 1 && scale > 1) {
2422
+ isDragging = true;
2423
+ dragStartX = e.touches[0].clientX;
2424
+ dragStartY = e.touches[0].clientY;
2425
+ lastTranslateX = translateX;
2426
+ lastTranslateY = translateY;
2427
+ img.style.transition = 'none';
2428
+ }
2429
+ }, { passive: false });
2430
+ overlay.addEventListener('touchmove', (e) => {
2431
+ if (e.touches.length === 2) {
2432
+ e.preventDefault();
2433
+ const dx = e.touches[0].clientX - e.touches[1].clientX;
2434
+ const dy = e.touches[0].clientY - e.touches[1].clientY;
2435
+ const dist = Math.hypot(dx, dy);
2436
+ scale = Math.min(Math.max(lastTouchScale * (dist / lastTouchDist), 1), 5);
2437
+ if (scale === 1) {
2438
+ translateX = 0;
2439
+ translateY = 0;
2440
+ }
2441
+ applyTransform();
2442
+ }
2443
+ else if (e.touches.length === 1 && isDragging) {
2444
+ e.preventDefault();
2445
+ translateX = lastTranslateX + (e.touches[0].clientX - dragStartX);
2446
+ translateY = lastTranslateY + (e.touches[0].clientY - dragStartY);
2447
+ applyTransform();
2448
+ }
2449
+ }, { passive: false });
2450
+ overlay.addEventListener('touchend', (e) => {
2451
+ if (e.touches.length < 2) {
2452
+ lastTouchDist = 0;
2453
+ }
2454
+ if (e.touches.length === 0) {
2455
+ isDragging = false;
2456
+ img.style.transition = 'transform 0.2s ease';
2457
+ }
2458
+ });
2459
+ const close = () => {
2460
+ document.removeEventListener('keydown', handleKeyDown);
2461
+ overlay.remove();
2462
+ };
2463
+ // Only close on backdrop click when not zoomed (prevent accidental close while panning)
2464
+ overlay.addEventListener('click', (e) => {
2465
+ if (e.target === overlay && scale <= 1)
2466
+ close();
2467
+ });
2468
+ downloadBtn.addEventListener('click', (e) => e.stopPropagation());
2469
+ closeBtn.addEventListener('click', (e) => { e.stopPropagation(); close(); });
2470
+ // Close on Escape
2471
+ const handleKeyDown = (e) => {
2472
+ if (e.key === 'Escape')
2473
+ close();
2474
+ };
2475
+ document.addEventListener('keydown', handleKeyDown);
2476
+ overlay.appendChild(closeBtn);
2477
+ overlay.appendChild(downloadBtn);
2478
+ overlay.appendChild(img);
2479
+ document.body.appendChild(overlay);
2278
2480
  }
2279
2481
  /**
2280
2482
  * Initialize the SDK and render widget
@@ -2386,8 +2588,6 @@ class WeldSDK {
2386
2588
  console.log('[Weld SDK] Opening widget...');
2387
2589
  this.stateCoordinator.openWidget();
2388
2590
  this.iframeManager.showIframe(IframeType.WIDGET);
2389
- this.iframeManager.showIframe(IframeType.BACKDROP);
2390
- // Keep launcher visible so user can click it to close the widget
2391
2591
  // Send open message to the widget iframe
2392
2592
  const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
2393
2593
  if (widgetIframe?.element?.contentWindow) {
@@ -2408,8 +2608,6 @@ class WeldSDK {
2408
2608
  console.log('[Weld SDK] Closing widget...');
2409
2609
  this.stateCoordinator.closeWidget();
2410
2610
  this.iframeManager.hideIframe(IframeType.WIDGET);
2411
- this.iframeManager.hideIframe(IframeType.BACKDROP);
2412
- // Launcher stays visible
2413
2611
  // Send close message to the widget iframe
2414
2612
  const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
2415
2613
  if (widgetIframe?.element?.contentWindow) {