@weldsuite/helpdesk-widget-sdk 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.d.ts CHANGED
@@ -504,6 +504,10 @@ declare class WeldSDK {
504
504
  * Handle launcher click messages from iframe
505
505
  */
506
506
  private handleLauncherClickMessage;
507
+ /**
508
+ * Show fullscreen image lightbox on the parent page
509
+ */
510
+ private showImageLightbox;
507
511
  /**
508
512
  * Initialize the SDK and render widget
509
513
  */
@@ -768,7 +772,7 @@ declare class IframeManager {
768
772
  */
769
773
  private createWidgetIframe;
770
774
  /**
771
- * Create backdrop iframe
775
+ * Create backdrop iframe — disabled, widget stays non-modal so users can interact with the page
772
776
  */
773
777
  private createBackdropIframe;
774
778
  /**
package/dist/index.esm.js CHANGED
@@ -508,14 +508,16 @@ class IframeManager {
508
508
  const container = document.createElement('div');
509
509
  container.className = 'weld-launcher-frame';
510
510
  container.setAttribute('data-state', 'visible');
511
+ // Container is larger than the button to allow hover animations (scale, shadow) without clipping
512
+ const launcherPadding = 10;
511
513
  container.style.cssText = `
512
514
  position: fixed;
513
- bottom: ${launcher.position.bottom};
514
- right: ${launcher.position.right};
515
- width: ${launcher.size};
516
- height: ${launcher.size};
515
+ bottom: calc(${launcher.position.bottom} - ${launcherPadding}px);
516
+ right: calc(${launcher.position.right} - ${launcherPadding}px);
517
+ width: calc(${launcher.size} + ${launcherPadding * 2}px);
518
+ height: calc(${launcher.size} + ${launcherPadding * 2}px);
517
519
  z-index: 2147483003;
518
- pointer-events: auto;
520
+ pointer-events: none;
519
521
  display: block;
520
522
  `;
521
523
  // Create iframe
@@ -529,6 +531,7 @@ class IframeManager {
529
531
  border: none;
530
532
  background: transparent;
531
533
  display: block;
534
+ pointer-events: auto;
532
535
  `;
533
536
  iframe.setAttribute('allow', 'clipboard-write');
534
537
  iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
@@ -649,43 +652,10 @@ class IframeManager {
649
652
  this.logger.debug('Widget iframe created');
650
653
  }
651
654
  /**
652
- * Create backdrop iframe
655
+ * Create backdrop iframe — disabled, widget stays non-modal so users can interact with the page
653
656
  */
654
657
  async createBackdropIframe() {
655
- if (!this.config.iframes.backdrop?.enabled) {
656
- this.logger.debug('Backdrop disabled, skipping creation');
657
- return;
658
- }
659
- // Create container
660
- const container = document.createElement('div');
661
- container.className = 'weld-backdrop-frame';
662
- container.setAttribute('data-state', 'hidden');
663
- container.style.cssText = `
664
- position: fixed;
665
- top: 0;
666
- left: 0;
667
- right: 0;
668
- bottom: 0;
669
- z-index: 2147483000;
670
- background: transparent;
671
- pointer-events: none;
672
- opacity: 0;
673
- transition: opacity 200ms ease;
674
- `;
675
- this.appContainer?.appendChild(container);
676
- // Store metadata (backdrop doesn't have an iframe, just a div)
677
- // We'll create a minimal "iframe" reference for consistency
678
- const dummyIframe = document.createElement('iframe');
679
- dummyIframe.style.display = 'none';
680
- this.iframes.set(IframeType.BACKDROP, {
681
- type: IframeType.BACKDROP,
682
- element: dummyIframe,
683
- container,
684
- ready: true, // Backdrop is always ready
685
- visible: false,
686
- createdAt: Date.now(),
687
- });
688
- this.logger.debug('Backdrop created');
658
+ this.logger.debug('Backdrop disabled, skipping creation');
689
659
  }
690
660
  /**
691
661
  * Build iframe URL with parameters
@@ -2395,7 +2365,7 @@ class StateCoordinator {
2395
2365
  }
2396
2366
  }
2397
2367
 
2398
- var version = "1.0.15";
2368
+ var version = "1.0.16";
2399
2369
  var packageJson = {
2400
2370
  version: version};
2401
2371
 
@@ -2478,6 +2448,242 @@ class WeldSDK {
2478
2448
  console.log('[Weld SDK] Widget close requested');
2479
2449
  this.close();
2480
2450
  }
2451
+ if (event.data?.type === 'weld:image:open' && event.data?.url) {
2452
+ this.showImageLightbox(event.data.url);
2453
+ }
2454
+ }
2455
+ /**
2456
+ * Show fullscreen image lightbox on the parent page
2457
+ */
2458
+ showImageLightbox(url) {
2459
+ // Remove existing lightbox if any
2460
+ const existing = document.getElementById('weld-image-lightbox');
2461
+ if (existing)
2462
+ existing.remove();
2463
+ // Zoom / pan state
2464
+ let scale = 1;
2465
+ let translateX = 0;
2466
+ let translateY = 0;
2467
+ let isDragging = false;
2468
+ let dragStartX = 0;
2469
+ let dragStartY = 0;
2470
+ let lastTranslateX = 0;
2471
+ let lastTranslateY = 0;
2472
+ const applyTransform = () => {
2473
+ img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
2474
+ };
2475
+ const resetTransform = () => {
2476
+ scale = 1;
2477
+ translateX = 0;
2478
+ translateY = 0;
2479
+ applyTransform();
2480
+ img.style.cursor = 'zoom-in';
2481
+ };
2482
+ const overlay = document.createElement('div');
2483
+ overlay.id = 'weld-image-lightbox';
2484
+ overlay.style.cssText = `
2485
+ position: fixed;
2486
+ inset: 0;
2487
+ z-index: 2147483647;
2488
+ background: rgba(0, 0, 0, 0.92);
2489
+ display: flex;
2490
+ align-items: center;
2491
+ justify-content: center;
2492
+ padding: 16px;
2493
+ cursor: pointer;
2494
+ overflow: hidden;
2495
+ `;
2496
+ // Close button
2497
+ const closeBtn = document.createElement('button');
2498
+ 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>`;
2499
+ closeBtn.style.cssText = `
2500
+ position: absolute;
2501
+ top: 16px;
2502
+ right: 16px;
2503
+ width: 40px;
2504
+ height: 40px;
2505
+ border-radius: 50%;
2506
+ border: none;
2507
+ background: rgba(255, 255, 255, 0.1);
2508
+ color: white;
2509
+ cursor: pointer;
2510
+ display: flex;
2511
+ align-items: center;
2512
+ justify-content: center;
2513
+ transition: background 0.15s;
2514
+ `;
2515
+ closeBtn.onmouseenter = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.2)'; };
2516
+ closeBtn.onmouseleave = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.1)'; };
2517
+ // Download button
2518
+ const downloadBtn = document.createElement('a');
2519
+ downloadBtn.href = url;
2520
+ downloadBtn.download = '';
2521
+ downloadBtn.target = '_blank';
2522
+ downloadBtn.rel = 'noopener noreferrer';
2523
+ 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>`;
2524
+ downloadBtn.style.cssText = `
2525
+ position: absolute;
2526
+ top: 16px;
2527
+ right: 64px;
2528
+ width: 40px;
2529
+ height: 40px;
2530
+ border-radius: 50%;
2531
+ border: none;
2532
+ background: rgba(255, 255, 255, 0.1);
2533
+ color: white;
2534
+ cursor: pointer;
2535
+ display: flex;
2536
+ align-items: center;
2537
+ justify-content: center;
2538
+ transition: background 0.15s;
2539
+ text-decoration: none;
2540
+ `;
2541
+ downloadBtn.onmouseenter = () => { downloadBtn.style.background = 'rgba(255, 255, 255, 0.2)'; };
2542
+ downloadBtn.onmouseleave = () => { downloadBtn.style.background = 'rgba(255, 255, 255, 0.1)'; };
2543
+ // Image
2544
+ const img = document.createElement('img');
2545
+ img.src = url;
2546
+ img.alt = 'Full size';
2547
+ img.draggable = false;
2548
+ img.style.cssText = `
2549
+ max-width: 100%;
2550
+ max-height: 100%;
2551
+ object-fit: contain;
2552
+ border-radius: 8px;
2553
+ cursor: zoom-in;
2554
+ transition: transform 0.2s ease;
2555
+ user-select: none;
2556
+ `;
2557
+ // Click to toggle zoom
2558
+ img.addEventListener('click', (e) => {
2559
+ e.stopPropagation();
2560
+ if (scale === 1) {
2561
+ // Zoom in to 2.5x centered on click position
2562
+ const rect = img.getBoundingClientRect();
2563
+ const clickX = e.clientX - rect.left - rect.width / 2;
2564
+ const clickY = e.clientY - rect.top - rect.height / 2;
2565
+ scale = 2.5;
2566
+ translateX = -clickX * 1.5;
2567
+ translateY = -clickY * 1.5;
2568
+ applyTransform();
2569
+ img.style.cursor = 'zoom-out';
2570
+ }
2571
+ else {
2572
+ // Zoom out - reset
2573
+ resetTransform();
2574
+ }
2575
+ });
2576
+ // Mouse wheel zoom
2577
+ overlay.addEventListener('wheel', (e) => {
2578
+ e.preventDefault();
2579
+ const delta = e.deltaY > 0 ? -0.25 : 0.25;
2580
+ const newScale = Math.min(Math.max(scale + delta, 1), 5);
2581
+ if (newScale === 1) {
2582
+ resetTransform();
2583
+ }
2584
+ else {
2585
+ scale = newScale;
2586
+ applyTransform();
2587
+ img.style.cursor = 'zoom-out';
2588
+ }
2589
+ }, { passive: false });
2590
+ // Drag to pan when zoomed
2591
+ img.addEventListener('mousedown', (e) => {
2592
+ if (scale <= 1)
2593
+ return;
2594
+ e.preventDefault();
2595
+ isDragging = true;
2596
+ dragStartX = e.clientX;
2597
+ dragStartY = e.clientY;
2598
+ lastTranslateX = translateX;
2599
+ lastTranslateY = translateY;
2600
+ img.style.cursor = 'grabbing';
2601
+ img.style.transition = 'none';
2602
+ });
2603
+ window.addEventListener('mousemove', (e) => {
2604
+ if (!isDragging)
2605
+ return;
2606
+ translateX = lastTranslateX + (e.clientX - dragStartX);
2607
+ translateY = lastTranslateY + (e.clientY - dragStartY);
2608
+ applyTransform();
2609
+ });
2610
+ window.addEventListener('mouseup', () => {
2611
+ if (!isDragging)
2612
+ return;
2613
+ isDragging = false;
2614
+ img.style.cursor = scale > 1 ? 'zoom-out' : 'zoom-in';
2615
+ img.style.transition = 'transform 0.2s ease';
2616
+ });
2617
+ // Touch: pinch to zoom + drag to pan
2618
+ let lastTouchDist = 0;
2619
+ let lastTouchScale = 1;
2620
+ overlay.addEventListener('touchstart', (e) => {
2621
+ if (e.touches.length === 2) {
2622
+ e.preventDefault();
2623
+ const dx = e.touches[0].clientX - e.touches[1].clientX;
2624
+ const dy = e.touches[0].clientY - e.touches[1].clientY;
2625
+ lastTouchDist = Math.hypot(dx, dy);
2626
+ lastTouchScale = scale;
2627
+ }
2628
+ else if (e.touches.length === 1 && scale > 1) {
2629
+ isDragging = true;
2630
+ dragStartX = e.touches[0].clientX;
2631
+ dragStartY = e.touches[0].clientY;
2632
+ lastTranslateX = translateX;
2633
+ lastTranslateY = translateY;
2634
+ img.style.transition = 'none';
2635
+ }
2636
+ }, { passive: false });
2637
+ overlay.addEventListener('touchmove', (e) => {
2638
+ if (e.touches.length === 2) {
2639
+ e.preventDefault();
2640
+ const dx = e.touches[0].clientX - e.touches[1].clientX;
2641
+ const dy = e.touches[0].clientY - e.touches[1].clientY;
2642
+ const dist = Math.hypot(dx, dy);
2643
+ scale = Math.min(Math.max(lastTouchScale * (dist / lastTouchDist), 1), 5);
2644
+ if (scale === 1) {
2645
+ translateX = 0;
2646
+ translateY = 0;
2647
+ }
2648
+ applyTransform();
2649
+ }
2650
+ else if (e.touches.length === 1 && isDragging) {
2651
+ e.preventDefault();
2652
+ translateX = lastTranslateX + (e.touches[0].clientX - dragStartX);
2653
+ translateY = lastTranslateY + (e.touches[0].clientY - dragStartY);
2654
+ applyTransform();
2655
+ }
2656
+ }, { passive: false });
2657
+ overlay.addEventListener('touchend', (e) => {
2658
+ if (e.touches.length < 2) {
2659
+ lastTouchDist = 0;
2660
+ }
2661
+ if (e.touches.length === 0) {
2662
+ isDragging = false;
2663
+ img.style.transition = 'transform 0.2s ease';
2664
+ }
2665
+ });
2666
+ const close = () => {
2667
+ document.removeEventListener('keydown', handleKeyDown);
2668
+ overlay.remove();
2669
+ };
2670
+ // Only close on backdrop click when not zoomed (prevent accidental close while panning)
2671
+ overlay.addEventListener('click', (e) => {
2672
+ if (e.target === overlay && scale <= 1)
2673
+ close();
2674
+ });
2675
+ downloadBtn.addEventListener('click', (e) => e.stopPropagation());
2676
+ closeBtn.addEventListener('click', (e) => { e.stopPropagation(); close(); });
2677
+ // Close on Escape
2678
+ const handleKeyDown = (e) => {
2679
+ if (e.key === 'Escape')
2680
+ close();
2681
+ };
2682
+ document.addEventListener('keydown', handleKeyDown);
2683
+ overlay.appendChild(closeBtn);
2684
+ overlay.appendChild(downloadBtn);
2685
+ overlay.appendChild(img);
2686
+ document.body.appendChild(overlay);
2481
2687
  }
2482
2688
  /**
2483
2689
  * Initialize the SDK and render widget
@@ -2589,8 +2795,6 @@ class WeldSDK {
2589
2795
  console.log('[Weld SDK] Opening widget...');
2590
2796
  this.stateCoordinator.openWidget();
2591
2797
  this.iframeManager.showIframe(IframeType.WIDGET);
2592
- this.iframeManager.showIframe(IframeType.BACKDROP);
2593
- // Keep launcher visible so user can click it to close the widget
2594
2798
  // Send open message to the widget iframe
2595
2799
  const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
2596
2800
  if (widgetIframe?.element?.contentWindow) {
@@ -2611,8 +2815,6 @@ class WeldSDK {
2611
2815
  console.log('[Weld SDK] Closing widget...');
2612
2816
  this.stateCoordinator.closeWidget();
2613
2817
  this.iframeManager.hideIframe(IframeType.WIDGET);
2614
- this.iframeManager.hideIframe(IframeType.BACKDROP);
2615
- // Launcher stays visible
2616
2818
  // Send close message to the widget iframe
2617
2819
  const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
2618
2820
  if (widgetIframe?.element?.contentWindow) {