@uxbertlabs/reportly 1.0.34 → 1.0.36

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
@@ -9324,36 +9324,60 @@ class Screenshot {
9324
9324
  * Wait for animations to complete and DOM to stabilize
9325
9325
  */
9326
9326
  async waitForStability() {
9327
+ console.log("⏳ Waiting for page stability...");
9327
9328
  // Wait for initial stabilization
9328
9329
  await this.wait(this.captureDelay);
9329
- // Wait for images to load
9330
+ // Wait for currently loading images (NO scrolling to avoid misalignment)
9331
+ // The onclone callback will handle image src copying
9330
9332
  await this.waitForImages();
9331
9333
  // Wait for fonts to load
9332
9334
  await this.waitForFonts();
9333
- // Additional short wait for any final rendering
9335
+ // Brief wait for final rendering
9334
9336
  await this.wait(100);
9337
+ console.log("✅ Page stabilized and ready for capture");
9335
9338
  }
9336
9339
  /**
9337
- * Wait for all images to finish loading
9340
+ * Wait for visible images to finish loading (no page scrolling to avoid annotation misalignment)
9338
9341
  */
9339
9342
  async waitForImages() {
9340
9343
  const images = Array.from(document.images);
9341
- const imagePromises = images.map(img => {
9342
- if (img.complete) {
9343
- return Promise.resolve();
9344
- }
9344
+ // Filter to only images that are currently loading
9345
+ const loadingImages = images.filter(img => !img.complete || img.naturalHeight === 0);
9346
+ if (loadingImages.length === 0) {
9347
+ console.log("✅ All visible images already loaded");
9348
+ return;
9349
+ }
9350
+ console.log(`⏳ Waiting for ${loadingImages.length} loading images...`);
9351
+ const imagePromises = loadingImages.map(img => {
9345
9352
  return new Promise(resolve => {
9346
- img.addEventListener("load", () => resolve(), {
9353
+ if (img.complete && img.naturalHeight !== 0) {
9354
+ resolve();
9355
+ return;
9356
+ }
9357
+ const onLoad = () => {
9358
+ console.log(`✅ Image loaded`);
9359
+ resolve();
9360
+ };
9361
+ const onError = () => {
9362
+ console.warn(`❌ Image failed to load`);
9363
+ resolve(); // Still resolve to not block
9364
+ };
9365
+ const onTimeout = () => {
9366
+ console.warn(`⏰ Image timed out after 2s`);
9367
+ resolve();
9368
+ };
9369
+ img.addEventListener("load", onLoad, {
9347
9370
  once: true
9348
9371
  });
9349
- img.addEventListener("error", () => resolve(), {
9372
+ img.addEventListener("error", onError, {
9350
9373
  once: true
9351
9374
  });
9352
- // Timeout after 3 seconds for slow images
9353
- setTimeout(() => resolve(), 3000);
9375
+ // Shorter timeout - 2 seconds max
9376
+ setTimeout(onTimeout, 2000);
9354
9377
  });
9355
9378
  });
9356
9379
  await Promise.all(imagePromises);
9380
+ console.log(`✅ All ${loadingImages.length} images processed`);
9357
9381
  }
9358
9382
  /**
9359
9383
  * Wait for fonts to load
@@ -9414,7 +9438,8 @@ class Screenshot {
9414
9438
  y: window.scrollY,
9415
9439
  // Improved rendering options
9416
9440
  backgroundColor: null,
9417
- imageTimeout: 5000,
9441
+ imageTimeout: 15000,
9442
+ // Increased timeout for slow-loading images
9418
9443
  removeContainer: true,
9419
9444
  scale: window.devicePixelRatio || 1,
9420
9445
  onclone: clonedDoc => {
@@ -9431,11 +9456,25 @@ class Screenshot {
9431
9456
  // Disable transitions
9432
9457
  element.style.transition = "none";
9433
9458
  });
9434
- // Ensure lazy-loaded images are visible
9435
- const images = clonedBody.querySelectorAll("img");
9436
- images.forEach(img => {
9437
- img.style.opacity = "1";
9438
- img.style.visibility = "visible";
9459
+ // Process images in the cloned document
9460
+ const originalImages = document.body.querySelectorAll("img");
9461
+ const clonedImages = clonedBody.querySelectorAll("img");
9462
+ clonedImages.forEach((clonedImg, index) => {
9463
+ const originalImg = originalImages[index];
9464
+ if (originalImg) {
9465
+ // Copy the current src from original to ensure it loads
9466
+ clonedImg.src = originalImg.currentSrc || originalImg.src;
9467
+ // Force visibility
9468
+ clonedImg.style.opacity = "1";
9469
+ clonedImg.style.visibility = "visible";
9470
+ clonedImg.style.display = originalImg.style.display || "inline";
9471
+ if (originalImg.width) clonedImg.width = originalImg.width;
9472
+ if (originalImg.height) clonedImg.height = originalImg.height;
9473
+ // Remove lazy loading attributes that might interfere
9474
+ clonedImg.removeAttribute("loading");
9475
+ clonedImg.removeAttribute("data-src");
9476
+ clonedImg.removeAttribute("data-srcset");
9477
+ }
9439
9478
  });
9440
9479
  },
9441
9480
  ignoreElements: element => {
@@ -9468,8 +9507,6 @@ class Screenshot {
9468
9507
  }
9469
9508
  });
9470
9509
  } else {
9471
- // Wait a bit for scroll to settle
9472
- await this.wait(1000);
9473
9510
  // Capture the full page
9474
9511
  canvas = await html2canvas(document.body, {
9475
9512
  useCORS: true,
@@ -9477,7 +9514,8 @@ class Screenshot {
9477
9514
  logging: false,
9478
9515
  // Improved rendering options
9479
9516
  backgroundColor: null,
9480
- imageTimeout: 5000,
9517
+ imageTimeout: 15000,
9518
+ // Increased timeout for slow-loading images
9481
9519
  removeContainer: true,
9482
9520
  scale: window.devicePixelRatio || 1,
9483
9521
  onclone: clonedDoc => {
@@ -9494,11 +9532,25 @@ class Screenshot {
9494
9532
  // Disable transitions
9495
9533
  element.style.transition = "none";
9496
9534
  });
9497
- // Ensure lazy-loaded images are visible
9498
- const images = clonedBody.querySelectorAll("img");
9499
- images.forEach(img => {
9500
- img.style.opacity = "1";
9501
- img.style.visibility = "visible";
9535
+ // Process images in the cloned document
9536
+ const originalImages = document.body.querySelectorAll("img");
9537
+ const clonedImages = clonedBody.querySelectorAll("img");
9538
+ clonedImages.forEach((clonedImg, index) => {
9539
+ const originalImg = originalImages[index];
9540
+ if (originalImg) {
9541
+ // Copy the current src from original to ensure it loads
9542
+ clonedImg.src = originalImg.currentSrc || originalImg.src;
9543
+ // Force visibility
9544
+ clonedImg.style.opacity = "1";
9545
+ clonedImg.style.visibility = "visible";
9546
+ clonedImg.style.display = originalImg.style.display || "inline";
9547
+ if (originalImg.width) clonedImg.width = originalImg.width;
9548
+ if (originalImg.height) clonedImg.height = originalImg.height;
9549
+ // Remove lazy loading attributes that might interfere
9550
+ clonedImg.removeAttribute("loading");
9551
+ clonedImg.removeAttribute("data-src");
9552
+ clonedImg.removeAttribute("data-srcset");
9553
+ }
9502
9554
  });
9503
9555
  },
9504
9556
  ignoreElements: element => {
@@ -11630,28 +11682,74 @@ class AnnotationManager {
11630
11682
  if (tempCtx && this.canvas) {
11631
11683
  // Draw the base screenshot
11632
11684
  tempCtx.drawImage(img, 0, 0);
11633
- // If mode is specified and different from current canvas mode, we need to adjust
11634
- const isCanvasInViewportMode = this.canvas.classList.contains('viewport-mode');
11635
- const isTargetFullpage = mode === 'fullpage';
11636
- if (isCanvasInViewportMode && isTargetFullpage) {
11637
- // User drew in viewport mode, but we're capturing fullpage
11638
- // Need to offset annotations by current scroll position
11639
- const scrollOffsetX = window.scrollX;
11640
- const scrollOffsetY = window.scrollY;
11641
- // Create a temporary canvas for adjusted annotations
11685
+ // Determine the actual capture mode used
11686
+ const currentCanvasMode = this.getCurrentMode();
11687
+ const captureMode = mode || currentCanvasMode;
11688
+ console.log('📸 Exporting annotated screenshot:', {
11689
+ currentCanvasMode,
11690
+ captureMode,
11691
+ scrollPosition: {
11692
+ x: window.scrollX,
11693
+ y: window.scrollY
11694
+ },
11695
+ initialScrollPosition: this.initialScrollPosition,
11696
+ canvasSize: {
11697
+ width: this.canvas.width,
11698
+ height: this.canvas.height
11699
+ },
11700
+ screenshotSize: {
11701
+ width: img.width,
11702
+ height: img.height
11703
+ }
11704
+ });
11705
+ // We need to determine if annotations need to be offset
11706
+ // Annotations are stored in absolute page coordinates (with scroll offset added)
11707
+ // Screenshot might be:
11708
+ // - fullpage: captures entire page (no offset needed)
11709
+ // - viewport: captures current viewport (annotations need to be adjusted)
11710
+ if (captureMode === 'viewport') {
11711
+ // Screenshot is only the viewport, so we need to subtract scroll offset from annotations
11712
+ // to align them correctly with the viewport screenshot
11713
+ const scrollOffsetX = this.initialScrollPosition?.x ?? window.scrollX;
11714
+ const scrollOffsetY = this.initialScrollPosition?.y ?? window.scrollY;
11715
+ // Create a temporary canvas with viewport dimensions
11642
11716
  const adjustedCanvas = document.createElement('canvas');
11643
- adjustedCanvas.width = tempCanvas.width;
11644
- adjustedCanvas.height = tempCanvas.height;
11717
+ adjustedCanvas.width = window.innerWidth;
11718
+ adjustedCanvas.height = window.innerHeight;
11645
11719
  const adjustedCtx = adjustedCanvas.getContext('2d');
11646
11720
  if (adjustedCtx) {
11647
- // Draw annotations with scroll offset
11648
- adjustedCtx.drawImage(this.canvas, scrollOffsetX, scrollOffsetY);
11721
+ // Translate context to subtract scroll offset
11722
+ adjustedCtx.translate(-scrollOffsetX, -scrollOffsetY);
11723
+ // Draw the annotations canvas (which is in absolute page coordinates)
11724
+ adjustedCtx.drawImage(this.canvas, 0, 0);
11725
+ // Reset translation
11726
+ adjustedCtx.setTransform(1, 0, 0, 1, 0, 0);
11649
11727
  // Draw the adjusted annotations on top of screenshot
11650
- tempCtx.drawImage(adjustedCanvas, 0, 0);
11728
+ // Scale if screenshot dimensions don't match viewport (due to device pixel ratio)
11729
+ const scaleX = img.width / window.innerWidth;
11730
+ const scaleY = img.height / window.innerHeight;
11731
+ if (scaleX !== 1 || scaleY !== 1) {
11732
+ tempCtx.save();
11733
+ tempCtx.scale(scaleX, scaleY);
11734
+ tempCtx.drawImage(adjustedCanvas, 0, 0);
11735
+ tempCtx.restore();
11736
+ } else {
11737
+ tempCtx.drawImage(adjustedCanvas, 0, 0);
11738
+ }
11651
11739
  }
11652
11740
  } else {
11653
- // Draw annotations directly on top
11654
- tempCtx.drawImage(this.canvas, 0, 0);
11741
+ // Fullpage mode - annotations and screenshot are both in absolute page coordinates
11742
+ // Just need to handle potential scaling due to device pixel ratio
11743
+ const scaleX = img.width / this.canvas.width;
11744
+ const scaleY = img.height / this.canvas.height;
11745
+ if (scaleX !== 1 || scaleY !== 1) {
11746
+ tempCtx.save();
11747
+ tempCtx.scale(scaleX, scaleY);
11748
+ tempCtx.drawImage(this.canvas, 0, 0);
11749
+ tempCtx.restore();
11750
+ } else {
11751
+ tempCtx.drawImage(this.canvas, 0, 0);
11752
+ }
11655
11753
  }
11656
11754
  resolve(tempCanvas.toDataURL('image/png'));
11657
11755
  }