@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.
@@ -1,12 +1,41 @@
1
1
  declare class Screenshot {
2
2
  private currentScreenshot;
3
+ private captureDelay;
3
4
  constructor();
5
+ /**
6
+ * Wait for specified milliseconds
7
+ */
4
8
  private wait;
9
+ /**
10
+ * Wait for animations to complete and DOM to stabilize
11
+ */
12
+ private waitForStability;
13
+ /**
14
+ * Wait for all images to finish loading
15
+ */
16
+ private waitForImages;
17
+ /**
18
+ * Wait for fonts to load
19
+ */
20
+ private waitForFonts;
21
+ /**
22
+ * Force a reflow to ensure styles are applied
23
+ */
24
+ private forceReflow;
5
25
  capture(mode?: "viewport" | "fullpage"): Promise<string>;
6
26
  private hideUXbertElements;
7
27
  private showUXbertElements;
8
28
  private showLoadingScreen;
9
29
  private hideLoadingScreen;
30
+ /**
31
+ * Set the delay before capture in milliseconds
32
+ * @param ms - Delay in milliseconds (default: 500)
33
+ */
34
+ setCaptureDelay(ms: number): void;
35
+ /**
36
+ * Get the current capture delay
37
+ */
38
+ getCaptureDelay(): number;
10
39
  getScreenshot(): string | null;
11
40
  clear(): void;
12
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/features/screenshot.ts"],"names":[],"mappings":"AAGA,cAAM,UAAU;IACd,OAAO,CAAC,iBAAiB,CAAgB;;IAKzC,OAAO,CAAC,IAAI;IAGN,OAAO,CAAC,IAAI,GAAE,UAAU,GAAG,UAAuB,GAAG,OAAO,CAAC,MAAM,CAAC;IA8H1E,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,iBAAiB;IA6DzB,OAAO,CAAC,iBAAiB;IAOzB,aAAa,IAAI,MAAM,GAAG,IAAI;IAI9B,KAAK,IAAI,IAAI;CAGd;AAED,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/features/screenshot.ts"],"names":[],"mappings":"AAGA,cAAM,UAAU;IACd,OAAO,CAAC,iBAAiB,CAAgB;IACzC,OAAO,CAAC,YAAY,CAAe;;IAMnC;;OAEG;IACH,OAAO,CAAC,IAAI;IAIZ;;OAEG;YACW,gBAAgB;IAc9B;;OAEG;YACW,aAAa;IAgB3B;;OAEG;YACW,YAAY;IAc1B;;OAEG;IACH,OAAO,CAAC,WAAW;IAKb,OAAO,CAAC,IAAI,GAAE,UAAU,GAAG,UAAuB,GAAG,OAAO,CAAC,MAAM,CAAC;IA4M1E,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,iBAAiB;IA6DzB,OAAO,CAAC,iBAAiB;IAOzB;;;OAGG;IACH,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIjC;;OAEG;IACH,eAAe,IAAI,MAAM;IAIzB,aAAa,IAAI,MAAM,GAAG,IAAI;IAI9B,KAAK,IAAI,IAAI;CAGd;AAED,eAAe,UAAU,CAAC"}
package/dist/index.cjs.js CHANGED
@@ -9301,51 +9301,144 @@ var parseBackgroundColor = function (context, element, backgroundColorOverride)
9301
9301
  // Screenshot capture using html2canvas
9302
9302
  class Screenshot {
9303
9303
  constructor() {
9304
+ this.captureDelay = 500; // Default delay before capture
9304
9305
  this.currentScreenshot = null;
9305
9306
  }
9307
+ /**
9308
+ * Wait for specified milliseconds
9309
+ */
9306
9310
  wait(ms) {
9307
9311
  return new Promise(resolve => setTimeout(resolve, ms));
9308
9312
  }
9313
+ /**
9314
+ * Wait for animations to complete and DOM to stabilize
9315
+ */
9316
+ async waitForStability() {
9317
+ // Wait for initial stabilization
9318
+ await this.wait(this.captureDelay);
9319
+ // Wait for images to load
9320
+ await this.waitForImages();
9321
+ // Wait for fonts to load
9322
+ await this.waitForFonts();
9323
+ // Additional short wait for any final rendering
9324
+ await this.wait(100);
9325
+ }
9326
+ /**
9327
+ * Wait for all images to finish loading
9328
+ */
9329
+ async waitForImages() {
9330
+ const images = Array.from(document.images);
9331
+ const imagePromises = images.map(img => {
9332
+ if (img.complete) {
9333
+ return Promise.resolve();
9334
+ }
9335
+ return new Promise(resolve => {
9336
+ img.addEventListener('load', () => resolve(), {
9337
+ once: true
9338
+ });
9339
+ img.addEventListener('error', () => resolve(), {
9340
+ once: true
9341
+ });
9342
+ // Timeout after 3 seconds for slow images
9343
+ setTimeout(() => resolve(), 3000);
9344
+ });
9345
+ });
9346
+ await Promise.all(imagePromises);
9347
+ }
9348
+ /**
9349
+ * Wait for fonts to load
9350
+ */
9351
+ async waitForFonts() {
9352
+ if ('fonts' in document) {
9353
+ try {
9354
+ await Promise.race([document.fonts.ready, this.wait(1000) // Timeout after 1 second
9355
+ ]);
9356
+ } catch (e) {
9357
+ // Font loading failed, continue anyway
9358
+ console.warn('Font loading check failed:', e);
9359
+ }
9360
+ }
9361
+ }
9362
+ /**
9363
+ * Force a reflow to ensure styles are applied
9364
+ */
9365
+ forceReflow() {
9366
+ // Reading offsetHeight forces a reflow
9367
+ document.body.offsetHeight;
9368
+ }
9309
9369
  async capture(mode = "fullpage") {
9310
9370
  try {
9311
- // Hide UXbert UI elements before capturing
9371
+ // Show loading screen immediately (before hiding UI)
9372
+ this.showLoadingScreen();
9373
+ // Wait for DOM to stabilize, animations to complete, and assets to load
9374
+ await this.waitForStability();
9375
+ // Hide loading screen and UXbert UI elements BEFORE capturing
9376
+ this.hideLoadingScreen();
9312
9377
  this.hideUXbertElements();
9378
+ // Force a reflow to ensure all styles are computed
9379
+ this.forceReflow();
9313
9380
  const originalScrollY = window.scrollY;
9314
9381
  let canvas;
9315
9382
  if (mode === "viewport") {
9316
9383
  // Capture only the current viewport
9317
9384
  canvas = await html2canvas(document.body, {
9318
- // foreignObjectRendering: true,
9319
9385
  useCORS: true,
9320
9386
  allowTaint: false,
9321
- logging: true,
9387
+ logging: false,
9322
9388
  width: window.innerWidth,
9323
9389
  height: window.innerHeight,
9324
9390
  windowWidth: window.innerWidth,
9325
9391
  windowHeight: window.innerHeight,
9326
9392
  x: window.scrollX,
9327
9393
  y: window.scrollY,
9328
- onclone: async () => {
9329
- // Show loading screen after clone (won't appear in screenshot)
9330
- this.showLoadingScreen();
9331
- // Wait 1 second after cloning to let animations complete
9332
- await this.wait(1000);
9394
+ // Improved rendering options
9395
+ backgroundColor: null,
9396
+ imageTimeout: 5000,
9397
+ removeContainer: true,
9398
+ scale: window.devicePixelRatio || 1,
9399
+ onclone: clonedDoc => {
9400
+ // Ensure all styles are applied in the cloned document
9401
+ const clonedBody = clonedDoc.body;
9402
+ // Force all animations to their final state
9403
+ const animatedElements = clonedBody.querySelectorAll('*');
9404
+ animatedElements.forEach(el => {
9405
+ const element = el;
9406
+ // Pause all animations
9407
+ element.style.animationPlayState = 'paused';
9408
+ element.style.animationDelay = '-9999s';
9409
+ element.style.animationIterationCount = '1';
9410
+ // Disable transitions
9411
+ element.style.transition = 'none';
9412
+ });
9413
+ // Ensure lazy-loaded images are visible
9414
+ const images = clonedBody.querySelectorAll('img');
9415
+ images.forEach(img => {
9416
+ img.style.opacity = '1';
9417
+ img.style.visibility = 'visible';
9418
+ });
9333
9419
  },
9334
9420
  ignoreElements: element => {
9421
+ // Ignore loading screen by ID
9422
+ if (element.id === 'uxbert-screenshot-loading') {
9423
+ return true;
9424
+ }
9425
+ // Ignore Uxbert elements
9426
+ if (element.getAttribute('data-uxbert-reportly') !== null || element.className?.toString().includes('uxbert-') || element.id?.includes('uxbert-')) {
9427
+ return true;
9428
+ }
9335
9429
  // Skip cross-origin images that might cause issues
9336
9430
  if (element.tagName === "IMG") {
9337
9431
  const img = element;
9338
9432
  try {
9339
9433
  // Test if image is accessible
9340
- const canvas = document.createElement("canvas");
9341
- const ctx = canvas.getContext("2d");
9342
- canvas.width = 1;
9343
- canvas.height = 1;
9434
+ const testCanvas = document.createElement("canvas");
9435
+ const ctx = testCanvas.getContext("2d");
9436
+ testCanvas.width = 1;
9437
+ testCanvas.height = 1;
9344
9438
  ctx?.drawImage(img, 0, 0, 1, 1);
9345
- canvas.toDataURL(); // This will throw if tainted
9439
+ testCanvas.toDataURL();
9346
9440
  return false; // Include the image
9347
9441
  } catch (e) {
9348
- // Image is tainted, skip it
9349
9442
  console.warn("Skipping cross-origin image:", img.src);
9350
9443
  return true;
9351
9444
  }
@@ -9356,6 +9449,8 @@ class Screenshot {
9356
9449
  } else {
9357
9450
  // Scroll to top to capture full page
9358
9451
  window.scrollTo(0, 0);
9452
+ // Wait a bit for scroll to settle
9453
+ await this.wait(100);
9359
9454
  // Get full page dimensions
9360
9455
  const fullPageHeight = Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight);
9361
9456
  const fullPageWidth = Math.max(document.body.scrollWidth, document.body.offsetWidth, document.documentElement.clientWidth, document.documentElement.scrollWidth, document.documentElement.offsetWidth);
@@ -9366,25 +9461,56 @@ class Screenshot {
9366
9461
  logging: false,
9367
9462
  width: fullPageWidth,
9368
9463
  height: fullPageHeight,
9369
- onclone: async () => {
9370
- // Show loading screen after clone (won't appear in screenshot)
9371
- this.showLoadingScreen();
9464
+ windowWidth: fullPageWidth,
9465
+ windowHeight: fullPageHeight,
9466
+ // Improved rendering options
9467
+ backgroundColor: null,
9468
+ imageTimeout: 5000,
9469
+ removeContainer: true,
9470
+ scale: window.devicePixelRatio || 1,
9471
+ onclone: clonedDoc => {
9472
+ // Ensure all styles are applied in the cloned document
9473
+ const clonedBody = clonedDoc.body;
9474
+ // Force all animations to their final state
9475
+ const animatedElements = clonedBody.querySelectorAll('*');
9476
+ animatedElements.forEach(el => {
9477
+ const element = el;
9478
+ // Pause all animations at their end state
9479
+ element.style.animationPlayState = 'paused';
9480
+ element.style.animationDelay = '-9999s';
9481
+ element.style.animationIterationCount = '1';
9482
+ // Disable transitions
9483
+ element.style.transition = 'none';
9484
+ });
9485
+ // Ensure lazy-loaded images are visible
9486
+ const images = clonedBody.querySelectorAll('img');
9487
+ images.forEach(img => {
9488
+ img.style.opacity = '1';
9489
+ img.style.visibility = 'visible';
9490
+ });
9372
9491
  },
9373
9492
  ignoreElements: element => {
9493
+ // Ignore loading screen by ID
9494
+ if (element.id === 'uxbert-screenshot-loading') {
9495
+ return true;
9496
+ }
9497
+ // Ignore Uxbert elements
9498
+ if (element.getAttribute('data-uxbert-reportly') !== null || element.className?.toString().includes('uxbert-') || element.id?.includes('uxbert-')) {
9499
+ return true;
9500
+ }
9374
9501
  // Skip cross-origin images that might cause issues
9375
9502
  if (element.tagName === "IMG") {
9376
9503
  const img = element;
9377
9504
  try {
9378
9505
  // Test if image is accessible
9379
- const canvas = document.createElement("canvas");
9380
- const ctx = canvas.getContext("2d");
9381
- canvas.width = 1;
9382
- canvas.height = 1;
9506
+ const testCanvas = document.createElement("canvas");
9507
+ const ctx = testCanvas.getContext("2d");
9508
+ testCanvas.width = 1;
9509
+ testCanvas.height = 1;
9383
9510
  ctx?.drawImage(img, 0, 0, 1, 1);
9384
- canvas.toDataURL(); // This will throw if tainted
9511
+ testCanvas.toDataURL();
9385
9512
  return false; // Include the image
9386
9513
  } catch (e) {
9387
- // Image is tainted, skip it
9388
9514
  console.warn("Skipping cross-origin image:", img.src);
9389
9515
  return true;
9390
9516
  }
@@ -9397,15 +9523,13 @@ class Screenshot {
9397
9523
  }
9398
9524
  // Show UXbert UI elements again
9399
9525
  this.showUXbertElements();
9400
- // Hide loading screen
9401
- this.hideLoadingScreen();
9402
9526
  // Convert to base64
9403
9527
  this.currentScreenshot = canvas.toDataURL("image/png");
9404
9528
  return this.currentScreenshot;
9405
9529
  } catch (error) {
9406
9530
  console.error("Screenshot capture failed:", error);
9407
- this.showUXbertElements();
9408
9531
  this.hideLoadingScreen();
9532
+ this.showUXbertElements();
9409
9533
  throw error;
9410
9534
  }
9411
9535
  }
@@ -9489,6 +9613,19 @@ class Screenshot {
9489
9613
  loadingOverlay.remove();
9490
9614
  }
9491
9615
  }
9616
+ /**
9617
+ * Set the delay before capture in milliseconds
9618
+ * @param ms - Delay in milliseconds (default: 500)
9619
+ */
9620
+ setCaptureDelay(ms) {
9621
+ this.captureDelay = Math.max(0, Math.min(ms, 5000)); // Clamp between 0-5000ms
9622
+ }
9623
+ /**
9624
+ * Get the current capture delay
9625
+ */
9626
+ getCaptureDelay() {
9627
+ return this.captureDelay;
9628
+ }
9492
9629
  getScreenshot() {
9493
9630
  return this.currentScreenshot;
9494
9631
  }