@uxbertlabs/reportly 1.0.23 → 1.0.24

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
@@ -9297,51 +9297,144 @@ var parseBackgroundColor = function (context, element, backgroundColorOverride)
9297
9297
  // Screenshot capture using html2canvas
9298
9298
  class Screenshot {
9299
9299
  constructor() {
9300
+ this.captureDelay = 500; // Default delay before capture
9300
9301
  this.currentScreenshot = null;
9301
9302
  }
9303
+ /**
9304
+ * Wait for specified milliseconds
9305
+ */
9302
9306
  wait(ms) {
9303
9307
  return new Promise(resolve => setTimeout(resolve, ms));
9304
9308
  }
9309
+ /**
9310
+ * Wait for animations to complete and DOM to stabilize
9311
+ */
9312
+ async waitForStability() {
9313
+ // Wait for initial stabilization
9314
+ await this.wait(this.captureDelay);
9315
+ // Wait for images to load
9316
+ await this.waitForImages();
9317
+ // Wait for fonts to load
9318
+ await this.waitForFonts();
9319
+ // Additional short wait for any final rendering
9320
+ await this.wait(100);
9321
+ }
9322
+ /**
9323
+ * Wait for all images to finish loading
9324
+ */
9325
+ async waitForImages() {
9326
+ const images = Array.from(document.images);
9327
+ const imagePromises = images.map(img => {
9328
+ if (img.complete) {
9329
+ return Promise.resolve();
9330
+ }
9331
+ return new Promise(resolve => {
9332
+ img.addEventListener('load', () => resolve(), {
9333
+ once: true
9334
+ });
9335
+ img.addEventListener('error', () => resolve(), {
9336
+ once: true
9337
+ });
9338
+ // Timeout after 3 seconds for slow images
9339
+ setTimeout(() => resolve(), 3000);
9340
+ });
9341
+ });
9342
+ await Promise.all(imagePromises);
9343
+ }
9344
+ /**
9345
+ * Wait for fonts to load
9346
+ */
9347
+ async waitForFonts() {
9348
+ if ('fonts' in document) {
9349
+ try {
9350
+ await Promise.race([document.fonts.ready, this.wait(1000) // Timeout after 1 second
9351
+ ]);
9352
+ } catch (e) {
9353
+ // Font loading failed, continue anyway
9354
+ console.warn('Font loading check failed:', e);
9355
+ }
9356
+ }
9357
+ }
9358
+ /**
9359
+ * Force a reflow to ensure styles are applied
9360
+ */
9361
+ forceReflow() {
9362
+ // Reading offsetHeight forces a reflow
9363
+ document.body.offsetHeight;
9364
+ }
9305
9365
  async capture(mode = "fullpage") {
9306
9366
  try {
9307
- // Hide UXbert UI elements before capturing
9367
+ // Show loading screen immediately (before hiding UI)
9368
+ this.showLoadingScreen();
9369
+ // Wait for DOM to stabilize, animations to complete, and assets to load
9370
+ await this.waitForStability();
9371
+ // Hide loading screen and UXbert UI elements BEFORE capturing
9372
+ this.hideLoadingScreen();
9308
9373
  this.hideUXbertElements();
9374
+ // Force a reflow to ensure all styles are computed
9375
+ this.forceReflow();
9309
9376
  const originalScrollY = window.scrollY;
9310
9377
  let canvas;
9311
9378
  if (mode === "viewport") {
9312
9379
  // Capture only the current viewport
9313
9380
  canvas = await html2canvas(document.body, {
9314
- // foreignObjectRendering: true,
9315
9381
  useCORS: true,
9316
9382
  allowTaint: false,
9317
- logging: true,
9383
+ logging: false,
9318
9384
  width: window.innerWidth,
9319
9385
  height: window.innerHeight,
9320
9386
  windowWidth: window.innerWidth,
9321
9387
  windowHeight: window.innerHeight,
9322
9388
  x: window.scrollX,
9323
9389
  y: window.scrollY,
9324
- onclone: async () => {
9325
- // Show loading screen after clone (won't appear in screenshot)
9326
- this.showLoadingScreen();
9327
- // Wait 1 second after cloning to let animations complete
9328
- await this.wait(1000);
9390
+ // Improved rendering options
9391
+ backgroundColor: null,
9392
+ imageTimeout: 5000,
9393
+ removeContainer: true,
9394
+ scale: window.devicePixelRatio || 1,
9395
+ onclone: clonedDoc => {
9396
+ // Ensure all styles are applied in the cloned document
9397
+ const clonedBody = clonedDoc.body;
9398
+ // Force all animations to their final state
9399
+ const animatedElements = clonedBody.querySelectorAll('*');
9400
+ animatedElements.forEach(el => {
9401
+ const element = el;
9402
+ // Pause all animations
9403
+ element.style.animationPlayState = 'paused';
9404
+ element.style.animationDelay = '-9999s';
9405
+ element.style.animationIterationCount = '1';
9406
+ // Disable transitions
9407
+ element.style.transition = 'none';
9408
+ });
9409
+ // Ensure lazy-loaded images are visible
9410
+ const images = clonedBody.querySelectorAll('img');
9411
+ images.forEach(img => {
9412
+ img.style.opacity = '1';
9413
+ img.style.visibility = 'visible';
9414
+ });
9329
9415
  },
9330
9416
  ignoreElements: element => {
9417
+ // Ignore loading screen by ID
9418
+ if (element.id === 'uxbert-screenshot-loading') {
9419
+ return true;
9420
+ }
9421
+ // Ignore Uxbert elements
9422
+ if (element.getAttribute('data-uxbert-reportly') !== null || element.className?.toString().includes('uxbert-') || element.id?.includes('uxbert-')) {
9423
+ return true;
9424
+ }
9331
9425
  // Skip cross-origin images that might cause issues
9332
9426
  if (element.tagName === "IMG") {
9333
9427
  const img = element;
9334
9428
  try {
9335
9429
  // Test if image is accessible
9336
- const canvas = document.createElement("canvas");
9337
- const ctx = canvas.getContext("2d");
9338
- canvas.width = 1;
9339
- canvas.height = 1;
9430
+ const testCanvas = document.createElement("canvas");
9431
+ const ctx = testCanvas.getContext("2d");
9432
+ testCanvas.width = 1;
9433
+ testCanvas.height = 1;
9340
9434
  ctx?.drawImage(img, 0, 0, 1, 1);
9341
- canvas.toDataURL(); // This will throw if tainted
9435
+ testCanvas.toDataURL();
9342
9436
  return false; // Include the image
9343
9437
  } catch (e) {
9344
- // Image is tainted, skip it
9345
9438
  console.warn("Skipping cross-origin image:", img.src);
9346
9439
  return true;
9347
9440
  }
@@ -9352,6 +9445,8 @@ class Screenshot {
9352
9445
  } else {
9353
9446
  // Scroll to top to capture full page
9354
9447
  window.scrollTo(0, 0);
9448
+ // Wait a bit for scroll to settle
9449
+ await this.wait(100);
9355
9450
  // Get full page dimensions
9356
9451
  const fullPageHeight = Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight);
9357
9452
  const fullPageWidth = Math.max(document.body.scrollWidth, document.body.offsetWidth, document.documentElement.clientWidth, document.documentElement.scrollWidth, document.documentElement.offsetWidth);
@@ -9362,25 +9457,56 @@ class Screenshot {
9362
9457
  logging: false,
9363
9458
  width: fullPageWidth,
9364
9459
  height: fullPageHeight,
9365
- onclone: async () => {
9366
- // Show loading screen after clone (won't appear in screenshot)
9367
- this.showLoadingScreen();
9460
+ windowWidth: fullPageWidth,
9461
+ windowHeight: fullPageHeight,
9462
+ // Improved rendering options
9463
+ backgroundColor: null,
9464
+ imageTimeout: 5000,
9465
+ removeContainer: true,
9466
+ scale: window.devicePixelRatio || 1,
9467
+ onclone: clonedDoc => {
9468
+ // Ensure all styles are applied in the cloned document
9469
+ const clonedBody = clonedDoc.body;
9470
+ // Force all animations to their final state
9471
+ const animatedElements = clonedBody.querySelectorAll('*');
9472
+ animatedElements.forEach(el => {
9473
+ const element = el;
9474
+ // Pause all animations at their end state
9475
+ element.style.animationPlayState = 'paused';
9476
+ element.style.animationDelay = '-9999s';
9477
+ element.style.animationIterationCount = '1';
9478
+ // Disable transitions
9479
+ element.style.transition = 'none';
9480
+ });
9481
+ // Ensure lazy-loaded images are visible
9482
+ const images = clonedBody.querySelectorAll('img');
9483
+ images.forEach(img => {
9484
+ img.style.opacity = '1';
9485
+ img.style.visibility = 'visible';
9486
+ });
9368
9487
  },
9369
9488
  ignoreElements: element => {
9489
+ // Ignore loading screen by ID
9490
+ if (element.id === 'uxbert-screenshot-loading') {
9491
+ return true;
9492
+ }
9493
+ // Ignore Uxbert elements
9494
+ if (element.getAttribute('data-uxbert-reportly') !== null || element.className?.toString().includes('uxbert-') || element.id?.includes('uxbert-')) {
9495
+ return true;
9496
+ }
9370
9497
  // Skip cross-origin images that might cause issues
9371
9498
  if (element.tagName === "IMG") {
9372
9499
  const img = element;
9373
9500
  try {
9374
9501
  // Test if image is accessible
9375
- const canvas = document.createElement("canvas");
9376
- const ctx = canvas.getContext("2d");
9377
- canvas.width = 1;
9378
- canvas.height = 1;
9502
+ const testCanvas = document.createElement("canvas");
9503
+ const ctx = testCanvas.getContext("2d");
9504
+ testCanvas.width = 1;
9505
+ testCanvas.height = 1;
9379
9506
  ctx?.drawImage(img, 0, 0, 1, 1);
9380
- canvas.toDataURL(); // This will throw if tainted
9507
+ testCanvas.toDataURL();
9381
9508
  return false; // Include the image
9382
9509
  } catch (e) {
9383
- // Image is tainted, skip it
9384
9510
  console.warn("Skipping cross-origin image:", img.src);
9385
9511
  return true;
9386
9512
  }
@@ -9393,15 +9519,13 @@ class Screenshot {
9393
9519
  }
9394
9520
  // Show UXbert UI elements again
9395
9521
  this.showUXbertElements();
9396
- // Hide loading screen
9397
- this.hideLoadingScreen();
9398
9522
  // Convert to base64
9399
9523
  this.currentScreenshot = canvas.toDataURL("image/png");
9400
9524
  return this.currentScreenshot;
9401
9525
  } catch (error) {
9402
9526
  console.error("Screenshot capture failed:", error);
9403
- this.showUXbertElements();
9404
9527
  this.hideLoadingScreen();
9528
+ this.showUXbertElements();
9405
9529
  throw error;
9406
9530
  }
9407
9531
  }
@@ -9485,6 +9609,19 @@ class Screenshot {
9485
9609
  loadingOverlay.remove();
9486
9610
  }
9487
9611
  }
9612
+ /**
9613
+ * Set the delay before capture in milliseconds
9614
+ * @param ms - Delay in milliseconds (default: 500)
9615
+ */
9616
+ setCaptureDelay(ms) {
9617
+ this.captureDelay = Math.max(0, Math.min(ms, 5000)); // Clamp between 0-5000ms
9618
+ }
9619
+ /**
9620
+ * Get the current capture delay
9621
+ */
9622
+ getCaptureDelay() {
9623
+ return this.captureDelay;
9624
+ }
9488
9625
  getScreenshot() {
9489
9626
  return this.currentScreenshot;
9490
9627
  }