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