@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.
package/dist/angular.d.ts CHANGED
@@ -463,6 +463,10 @@ declare class WeldSDK {
463
463
  * Handle launcher click messages from iframe
464
464
  */
465
465
  private handleLauncherClickMessage;
466
+ /**
467
+ * Show fullscreen image lightbox on the parent page
468
+ */
469
+ private showImageLightbox;
466
470
  /**
467
471
  * Initialize the SDK and render widget
468
472
  */
@@ -570,14 +570,16 @@ class IframeManager {
570
570
  const container = document.createElement('div');
571
571
  container.className = 'weld-launcher-frame';
572
572
  container.setAttribute('data-state', 'visible');
573
+ // Container is larger than the button to allow hover animations (scale, shadow) without clipping
574
+ const launcherPadding = 10;
573
575
  container.style.cssText = `
574
576
  position: fixed;
575
- bottom: ${launcher.position.bottom};
576
- right: ${launcher.position.right};
577
- width: ${launcher.size};
578
- height: ${launcher.size};
577
+ bottom: calc(${launcher.position.bottom} - ${launcherPadding}px);
578
+ right: calc(${launcher.position.right} - ${launcherPadding}px);
579
+ width: calc(${launcher.size} + ${launcherPadding * 2}px);
580
+ height: calc(${launcher.size} + ${launcherPadding * 2}px);
579
581
  z-index: 2147483003;
580
- pointer-events: auto;
582
+ pointer-events: none;
581
583
  display: block;
582
584
  `;
583
585
  // Create iframe
@@ -591,6 +593,7 @@ class IframeManager {
591
593
  border: none;
592
594
  background: transparent;
593
595
  display: block;
596
+ pointer-events: auto;
594
597
  `;
595
598
  iframe.setAttribute('allow', 'clipboard-write');
596
599
  iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
@@ -711,43 +714,10 @@ class IframeManager {
711
714
  this.logger.debug('Widget iframe created');
712
715
  }
713
716
  /**
714
- * Create backdrop iframe
717
+ * Create backdrop iframe — disabled, widget stays non-modal so users can interact with the page
715
718
  */
716
719
  async createBackdropIframe() {
717
- if (!this.config.iframes.backdrop?.enabled) {
718
- this.logger.debug('Backdrop disabled, skipping creation');
719
- return;
720
- }
721
- // Create container
722
- const container = document.createElement('div');
723
- container.className = 'weld-backdrop-frame';
724
- container.setAttribute('data-state', 'hidden');
725
- container.style.cssText = `
726
- position: fixed;
727
- top: 0;
728
- left: 0;
729
- right: 0;
730
- bottom: 0;
731
- z-index: 2147483000;
732
- background: transparent;
733
- pointer-events: none;
734
- opacity: 0;
735
- transition: opacity 200ms ease;
736
- `;
737
- this.appContainer?.appendChild(container);
738
- // Store metadata (backdrop doesn't have an iframe, just a div)
739
- // We'll create a minimal "iframe" reference for consistency
740
- const dummyIframe = document.createElement('iframe');
741
- dummyIframe.style.display = 'none';
742
- this.iframes.set(IframeType.BACKDROP, {
743
- type: IframeType.BACKDROP,
744
- element: dummyIframe,
745
- container,
746
- ready: true, // Backdrop is always ready
747
- visible: false,
748
- createdAt: Date.now(),
749
- });
750
- this.logger.debug('Backdrop created');
720
+ this.logger.debug('Backdrop disabled, skipping creation');
751
721
  }
752
722
  /**
753
723
  * Build iframe URL with parameters
@@ -1085,11 +1055,7 @@ class SecurityManager {
1085
1055
  * Validate message origin
1086
1056
  */
1087
1057
  isOriginAllowed(origin) {
1088
- // If no allowed origins specified, only allow same origin
1089
- if (this.config.allowedOrigins?.length === 0) {
1090
- return origin === window.location.origin;
1091
- }
1092
- // Check if origin is in allowed list
1058
+ // Check if origin is in allowed list (includes same-origin and programmatically added origins)
1093
1059
  if (this.allowedOrigins.has(origin)) {
1094
1060
  return true;
1095
1061
  }
@@ -2252,7 +2218,7 @@ class StateCoordinator {
2252
2218
  }
2253
2219
  }
2254
2220
 
2255
- var version = "1.0.14";
2221
+ var version = "1.0.16";
2256
2222
  var packageJson = {
2257
2223
  version: version};
2258
2224
 
@@ -2335,6 +2301,242 @@ class WeldSDK {
2335
2301
  console.log('[Weld SDK] Widget close requested');
2336
2302
  this.close();
2337
2303
  }
2304
+ if (event.data?.type === 'weld:image:open' && event.data?.url) {
2305
+ this.showImageLightbox(event.data.url);
2306
+ }
2307
+ }
2308
+ /**
2309
+ * Show fullscreen image lightbox on the parent page
2310
+ */
2311
+ showImageLightbox(url) {
2312
+ // Remove existing lightbox if any
2313
+ const existing = document.getElementById('weld-image-lightbox');
2314
+ if (existing)
2315
+ existing.remove();
2316
+ // Zoom / pan state
2317
+ let scale = 1;
2318
+ let translateX = 0;
2319
+ let translateY = 0;
2320
+ let isDragging = false;
2321
+ let dragStartX = 0;
2322
+ let dragStartY = 0;
2323
+ let lastTranslateX = 0;
2324
+ let lastTranslateY = 0;
2325
+ const applyTransform = () => {
2326
+ img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
2327
+ };
2328
+ const resetTransform = () => {
2329
+ scale = 1;
2330
+ translateX = 0;
2331
+ translateY = 0;
2332
+ applyTransform();
2333
+ img.style.cursor = 'zoom-in';
2334
+ };
2335
+ const overlay = document.createElement('div');
2336
+ overlay.id = 'weld-image-lightbox';
2337
+ overlay.style.cssText = `
2338
+ position: fixed;
2339
+ inset: 0;
2340
+ z-index: 2147483647;
2341
+ background: rgba(0, 0, 0, 0.92);
2342
+ display: flex;
2343
+ align-items: center;
2344
+ justify-content: center;
2345
+ padding: 16px;
2346
+ cursor: pointer;
2347
+ overflow: hidden;
2348
+ `;
2349
+ // Close button
2350
+ const closeBtn = document.createElement('button');
2351
+ 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>`;
2352
+ closeBtn.style.cssText = `
2353
+ position: absolute;
2354
+ top: 16px;
2355
+ right: 16px;
2356
+ width: 40px;
2357
+ height: 40px;
2358
+ border-radius: 50%;
2359
+ border: none;
2360
+ background: rgba(255, 255, 255, 0.1);
2361
+ color: white;
2362
+ cursor: pointer;
2363
+ display: flex;
2364
+ align-items: center;
2365
+ justify-content: center;
2366
+ transition: background 0.15s;
2367
+ `;
2368
+ closeBtn.onmouseenter = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.2)'; };
2369
+ closeBtn.onmouseleave = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.1)'; };
2370
+ // Download button
2371
+ const downloadBtn = document.createElement('a');
2372
+ downloadBtn.href = url;
2373
+ downloadBtn.download = '';
2374
+ downloadBtn.target = '_blank';
2375
+ downloadBtn.rel = 'noopener noreferrer';
2376
+ 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>`;
2377
+ downloadBtn.style.cssText = `
2378
+ position: absolute;
2379
+ top: 16px;
2380
+ right: 64px;
2381
+ width: 40px;
2382
+ height: 40px;
2383
+ border-radius: 50%;
2384
+ border: none;
2385
+ background: rgba(255, 255, 255, 0.1);
2386
+ color: white;
2387
+ cursor: pointer;
2388
+ display: flex;
2389
+ align-items: center;
2390
+ justify-content: center;
2391
+ transition: background 0.15s;
2392
+ text-decoration: none;
2393
+ `;
2394
+ downloadBtn.onmouseenter = () => { downloadBtn.style.background = 'rgba(255, 255, 255, 0.2)'; };
2395
+ downloadBtn.onmouseleave = () => { downloadBtn.style.background = 'rgba(255, 255, 255, 0.1)'; };
2396
+ // Image
2397
+ const img = document.createElement('img');
2398
+ img.src = url;
2399
+ img.alt = 'Full size';
2400
+ img.draggable = false;
2401
+ img.style.cssText = `
2402
+ max-width: 100%;
2403
+ max-height: 100%;
2404
+ object-fit: contain;
2405
+ border-radius: 8px;
2406
+ cursor: zoom-in;
2407
+ transition: transform 0.2s ease;
2408
+ user-select: none;
2409
+ `;
2410
+ // Click to toggle zoom
2411
+ img.addEventListener('click', (e) => {
2412
+ e.stopPropagation();
2413
+ if (scale === 1) {
2414
+ // Zoom in to 2.5x centered on click position
2415
+ const rect = img.getBoundingClientRect();
2416
+ const clickX = e.clientX - rect.left - rect.width / 2;
2417
+ const clickY = e.clientY - rect.top - rect.height / 2;
2418
+ scale = 2.5;
2419
+ translateX = -clickX * 1.5;
2420
+ translateY = -clickY * 1.5;
2421
+ applyTransform();
2422
+ img.style.cursor = 'zoom-out';
2423
+ }
2424
+ else {
2425
+ // Zoom out - reset
2426
+ resetTransform();
2427
+ }
2428
+ });
2429
+ // Mouse wheel zoom
2430
+ overlay.addEventListener('wheel', (e) => {
2431
+ e.preventDefault();
2432
+ const delta = e.deltaY > 0 ? -0.25 : 0.25;
2433
+ const newScale = Math.min(Math.max(scale + delta, 1), 5);
2434
+ if (newScale === 1) {
2435
+ resetTransform();
2436
+ }
2437
+ else {
2438
+ scale = newScale;
2439
+ applyTransform();
2440
+ img.style.cursor = 'zoom-out';
2441
+ }
2442
+ }, { passive: false });
2443
+ // Drag to pan when zoomed
2444
+ img.addEventListener('mousedown', (e) => {
2445
+ if (scale <= 1)
2446
+ return;
2447
+ e.preventDefault();
2448
+ isDragging = true;
2449
+ dragStartX = e.clientX;
2450
+ dragStartY = e.clientY;
2451
+ lastTranslateX = translateX;
2452
+ lastTranslateY = translateY;
2453
+ img.style.cursor = 'grabbing';
2454
+ img.style.transition = 'none';
2455
+ });
2456
+ window.addEventListener('mousemove', (e) => {
2457
+ if (!isDragging)
2458
+ return;
2459
+ translateX = lastTranslateX + (e.clientX - dragStartX);
2460
+ translateY = lastTranslateY + (e.clientY - dragStartY);
2461
+ applyTransform();
2462
+ });
2463
+ window.addEventListener('mouseup', () => {
2464
+ if (!isDragging)
2465
+ return;
2466
+ isDragging = false;
2467
+ img.style.cursor = scale > 1 ? 'zoom-out' : 'zoom-in';
2468
+ img.style.transition = 'transform 0.2s ease';
2469
+ });
2470
+ // Touch: pinch to zoom + drag to pan
2471
+ let lastTouchDist = 0;
2472
+ let lastTouchScale = 1;
2473
+ overlay.addEventListener('touchstart', (e) => {
2474
+ if (e.touches.length === 2) {
2475
+ e.preventDefault();
2476
+ const dx = e.touches[0].clientX - e.touches[1].clientX;
2477
+ const dy = e.touches[0].clientY - e.touches[1].clientY;
2478
+ lastTouchDist = Math.hypot(dx, dy);
2479
+ lastTouchScale = scale;
2480
+ }
2481
+ else if (e.touches.length === 1 && scale > 1) {
2482
+ isDragging = true;
2483
+ dragStartX = e.touches[0].clientX;
2484
+ dragStartY = e.touches[0].clientY;
2485
+ lastTranslateX = translateX;
2486
+ lastTranslateY = translateY;
2487
+ img.style.transition = 'none';
2488
+ }
2489
+ }, { passive: false });
2490
+ overlay.addEventListener('touchmove', (e) => {
2491
+ if (e.touches.length === 2) {
2492
+ e.preventDefault();
2493
+ const dx = e.touches[0].clientX - e.touches[1].clientX;
2494
+ const dy = e.touches[0].clientY - e.touches[1].clientY;
2495
+ const dist = Math.hypot(dx, dy);
2496
+ scale = Math.min(Math.max(lastTouchScale * (dist / lastTouchDist), 1), 5);
2497
+ if (scale === 1) {
2498
+ translateX = 0;
2499
+ translateY = 0;
2500
+ }
2501
+ applyTransform();
2502
+ }
2503
+ else if (e.touches.length === 1 && isDragging) {
2504
+ e.preventDefault();
2505
+ translateX = lastTranslateX + (e.touches[0].clientX - dragStartX);
2506
+ translateY = lastTranslateY + (e.touches[0].clientY - dragStartY);
2507
+ applyTransform();
2508
+ }
2509
+ }, { passive: false });
2510
+ overlay.addEventListener('touchend', (e) => {
2511
+ if (e.touches.length < 2) {
2512
+ lastTouchDist = 0;
2513
+ }
2514
+ if (e.touches.length === 0) {
2515
+ isDragging = false;
2516
+ img.style.transition = 'transform 0.2s ease';
2517
+ }
2518
+ });
2519
+ const close = () => {
2520
+ document.removeEventListener('keydown', handleKeyDown);
2521
+ overlay.remove();
2522
+ };
2523
+ // Only close on backdrop click when not zoomed (prevent accidental close while panning)
2524
+ overlay.addEventListener('click', (e) => {
2525
+ if (e.target === overlay && scale <= 1)
2526
+ close();
2527
+ });
2528
+ downloadBtn.addEventListener('click', (e) => e.stopPropagation());
2529
+ closeBtn.addEventListener('click', (e) => { e.stopPropagation(); close(); });
2530
+ // Close on Escape
2531
+ const handleKeyDown = (e) => {
2532
+ if (e.key === 'Escape')
2533
+ close();
2534
+ };
2535
+ document.addEventListener('keydown', handleKeyDown);
2536
+ overlay.appendChild(closeBtn);
2537
+ overlay.appendChild(downloadBtn);
2538
+ overlay.appendChild(img);
2539
+ document.body.appendChild(overlay);
2338
2540
  }
2339
2541
  /**
2340
2542
  * Initialize the SDK and render widget
@@ -2446,8 +2648,6 @@ class WeldSDK {
2446
2648
  console.log('[Weld SDK] Opening widget...');
2447
2649
  this.stateCoordinator.openWidget();
2448
2650
  this.iframeManager.showIframe(IframeType.WIDGET);
2449
- this.iframeManager.showIframe(IframeType.BACKDROP);
2450
- // Keep launcher visible so user can click it to close the widget
2451
2651
  // Send open message to the widget iframe
2452
2652
  const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
2453
2653
  if (widgetIframe?.element?.contentWindow) {
@@ -2468,8 +2668,6 @@ class WeldSDK {
2468
2668
  console.log('[Weld SDK] Closing widget...');
2469
2669
  this.stateCoordinator.closeWidget();
2470
2670
  this.iframeManager.hideIframe(IframeType.WIDGET);
2471
- this.iframeManager.hideIframe(IframeType.BACKDROP);
2472
- // Launcher stays visible
2473
2671
  // Send close message to the widget iframe
2474
2672
  const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
2475
2673
  if (widgetIframe?.element?.contentWindow) {