@zumer/snapdom 1.9.6-dev.0 → 1.9.6-dev.2

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/snapdom.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.9.6-dev.0
4
+ * v.1.9.6-dev.2
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -440,120 +440,154 @@ function isSlotElement(node) {
440
440
  return node.nodeType === Node.ELEMENT_NODE && node.tagName === "SLOT";
441
441
  }
442
442
  function deepClone(node, compress, options = {}, originalRoot) {
443
- if (node.nodeType === Node.TEXT_NODE) {
444
- return node.cloneNode(true);
445
- }
446
- if (node.nodeType !== Node.ELEMENT_NODE) return node.cloneNode(true);
447
- if (node.getAttribute("data-capture") === "exclude") {
448
- const spacer = document.createElement("div");
449
- const rect = node.getBoundingClientRect();
450
- spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
451
- return spacer;
452
- }
453
- if (options.exclude && Array.isArray(options.exclude)) {
454
- for (const selector of options.exclude) {
443
+ if (!node) {
444
+ throw new Error("Invalid node: node is null or undefined");
445
+ }
446
+ try {
447
+ if (node.nodeType === Node.TEXT_NODE) {
448
+ return node.cloneNode(true);
449
+ }
450
+ if (node.nodeType !== Node.ELEMENT_NODE) {
451
+ return node.cloneNode(true);
452
+ }
453
+ if (node.getAttribute("data-capture") === "exclude") {
454
+ const spacer = document.createElement("div");
455
+ const rect = node.getBoundingClientRect();
456
+ spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
457
+ return spacer;
458
+ }
459
+ if (options.exclude && Array.isArray(options.exclude)) {
460
+ for (const selector of options.exclude) {
461
+ try {
462
+ if (node.matches?.(selector)) {
463
+ const spacer = document.createElement("div");
464
+ const rect = node.getBoundingClientRect();
465
+ spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
466
+ return spacer;
467
+ }
468
+ } catch (err) {
469
+ console.warn(`Invalid selector in exclude option: ${selector}`, err);
470
+ }
471
+ }
472
+ }
473
+ if (typeof options.filter === "function") {
455
474
  try {
456
- if (node.matches?.(selector)) {
475
+ if (!options.filter(node, originalRoot || node)) {
457
476
  const spacer = document.createElement("div");
458
477
  const rect = node.getBoundingClientRect();
459
478
  spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
460
479
  return spacer;
461
480
  }
462
481
  } catch (err) {
463
- console.warn(`Invalid selector in exclude option: ${selector}`, err);
482
+ console.warn("Error in filter function:", err);
464
483
  }
465
484
  }
466
- }
467
- if (typeof options.filter === "function") {
485
+ if (node.tagName === "IFRAME") {
486
+ const fallback = document.createElement("div");
487
+ fallback.textContent = "";
488
+ fallback.style.cssText = `width: ${node.offsetWidth}px; height: ${node.offsetHeight}px; background-image: repeating-linear-gradient(45deg, #ddd, #ddd 5px, #f9f9f9 5px, #f9f9f9 10px);display: flex;align-items: center;justify-content: center;font-size: 12px;color: #555; border: 1px solid #aaa;`;
489
+ return fallback;
490
+ }
491
+ if (node.getAttribute("data-capture") === "placeholder") {
492
+ const clone2 = node.cloneNode(false);
493
+ cache.preNodeMap.set(clone2, node);
494
+ inlineAllStyles(node, clone2, compress);
495
+ const placeholder = document.createElement("div");
496
+ placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
497
+ placeholder.style.cssText = `color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;`;
498
+ clone2.appendChild(placeholder);
499
+ return clone2;
500
+ }
501
+ if (node.tagName === "CANVAS") {
502
+ const dataURL = node.toDataURL();
503
+ const img = document.createElement("img");
504
+ img.src = dataURL;
505
+ img.width = node.width;
506
+ img.height = node.height;
507
+ cache.preNodeMap.set(img, node);
508
+ inlineAllStyles(node, img, compress);
509
+ return img;
510
+ }
511
+ let clone;
468
512
  try {
469
- if (!options.filter(node, originalRoot || node)) {
470
- const spacer = document.createElement("div");
471
- const rect = node.getBoundingClientRect();
472
- spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
473
- return spacer;
513
+ clone = node.cloneNode(false);
514
+ if (!clone) {
515
+ throw new Error("Failed to clone node: cloneNode returned null");
474
516
  }
517
+ cache.preNodeMap.set(clone, node);
475
518
  } catch (err) {
476
- console.warn("Error in filter function:", err);
477
- }
478
- }
479
- if (node.tagName === "IFRAME") {
480
- const fallback = document.createElement("div");
481
- fallback.textContent = "";
482
- fallback.style.cssText = `width: ${node.offsetWidth}px; height: ${node.offsetHeight}px; background-image: repeating-linear-gradient(45deg, #ddd, #ddd 5px, #f9f9f9 5px, #f9f9f9 10px);display: flex;align-items: center;justify-content: center;font-size: 12px;color: #555; border: 1px solid #aaa;`;
483
- return fallback;
484
- }
485
- if (node.getAttribute("data-capture") === "placeholder") {
486
- const clone2 = node.cloneNode(false);
487
- cache.preNodeMap.set(clone2, node);
488
- inlineAllStyles(node, clone2, compress);
489
- const placeholder = document.createElement("div");
490
- placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
491
- placeholder.style.cssText = `color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;`;
492
- clone2.appendChild(placeholder);
493
- return clone2;
494
- }
495
- if (node.tagName === "CANVAS") {
496
- const dataURL = node.toDataURL();
497
- const img = document.createElement("img");
498
- img.src = dataURL;
499
- img.width = node.width;
500
- img.height = node.height;
501
- img.style.display = "inline-block";
502
- img.style.width = node.style.width || `${node.width}px`;
503
- img.style.height = node.style.height || `${node.height}px`;
504
- return img;
505
- }
506
- const clone = node.cloneNode(false);
507
- cache.preNodeMap.set(clone, node);
508
- if (node instanceof HTMLInputElement) {
509
- clone.value = node.value;
510
- clone.setAttribute("value", node.value);
511
- if (node.checked !== void 0) {
512
- clone.checked = node.checked;
513
- if (node.checked) clone.setAttribute("checked", "");
519
+ console.error("[Snapdom] Failed to clone node:", node, err);
520
+ throw err;
514
521
  }
515
- } else if (node instanceof HTMLTextAreaElement) {
516
- const rect = node.getBoundingClientRect();
517
- clone.textContent = node.value;
518
- clone.style.width = `${rect.width}px`;
519
- clone.style.height = `${rect.height}px`;
520
- } else if (node instanceof HTMLSelectElement) {
521
- clone.value = node.value;
522
- Array.from(clone.options).forEach((opt) => {
523
- if (opt.value === node.value) {
524
- opt.setAttribute("selected", "");
525
- } else {
526
- opt.removeAttribute("selected");
522
+ if (node instanceof HTMLInputElement) {
523
+ clone.value = node.value;
524
+ clone.setAttribute("value", node.value);
525
+ if (node.checked !== void 0) {
526
+ clone.checked = node.checked;
527
+ if (node.checked) clone.setAttribute("checked", "");
527
528
  }
528
- });
529
- }
530
- inlineAllStyles(node, clone, compress);
531
- if (isSlotElement(node)) {
532
- const assigned = node.assignedNodes?.({ flatten: true }) || [];
533
- const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
534
- const fragment = document.createDocumentFragment();
535
- for (const child of nodesToClone) {
536
- const clonedChild = deepClone(child, compress, options, originalRoot || node);
537
- if (clonedChild) fragment.appendChild(clonedChild);
538
- }
539
- return fragment;
540
- } else if (node instanceof HTMLTextAreaElement) {
541
- } else {
542
- const baseChildren = node.shadowRoot ? node.shadowRoot.childNodes : node.childNodes;
543
- for (const child of baseChildren) {
544
- const clonedChild = deepClone(child, compress, options, originalRoot || node);
545
- if (clonedChild) clone.appendChild(clonedChild);
529
+ } else if (node instanceof HTMLTextAreaElement) {
530
+ const rect = node.getBoundingClientRect();
531
+ clone.textContent = node.value;
532
+ clone.style.width = `${rect.width}px`;
533
+ clone.style.height = `${rect.height}px`;
534
+ } else if (node instanceof HTMLSelectElement) {
535
+ clone.value = node.value;
536
+ Array.from(clone.options).forEach((opt) => {
537
+ if (opt.value === node.value) {
538
+ opt.setAttribute("selected", "");
539
+ } else {
540
+ opt.removeAttribute("selected");
541
+ }
542
+ });
546
543
  }
547
- if (node.shadowRoot && node.childNodes.length > 0 && !node.shadowRoot.querySelector("slot")) {
548
- const lightDomContent = document.createDocumentFragment();
549
- for (const child of node.childNodes) {
550
- const clonedChild = deepClone(child, compress, options, originalRoot || node);
551
- if (clonedChild) lightDomContent.appendChild(clonedChild);
544
+ inlineAllStyles(node, clone, compress);
545
+ try {
546
+ if (isSlotElement(node)) {
547
+ const assigned = node.assignedNodes?.({ flatten: true }) || [];
548
+ const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
549
+ const fragment = document.createDocumentFragment();
550
+ for (const child of nodesToClone) {
551
+ try {
552
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
553
+ if (clonedChild) fragment.appendChild(clonedChild);
554
+ } catch (err) {
555
+ console.warn("[Snapdom] Failed to clone slot content:", child, err);
556
+ }
557
+ }
558
+ return fragment;
559
+ } else {
560
+ const baseChildren = node.shadowRoot ? node.shadowRoot.childNodes : node.childNodes;
561
+ for (const child of baseChildren) {
562
+ try {
563
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
564
+ if (clonedChild) clone.appendChild(clonedChild);
565
+ } catch (err) {
566
+ console.warn("[Snapdom] Failed to clone child node:", child, err);
567
+ }
568
+ }
569
+ if (node.shadowRoot && node.childNodes.length > 0 && !node.shadowRoot.querySelector("slot")) {
570
+ const lightDomContent = document.createDocumentFragment();
571
+ for (const child of node.childNodes) {
572
+ try {
573
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
574
+ if (clonedChild) lightDomContent.appendChild(clonedChild);
575
+ } catch (err) {
576
+ console.warn("[Snapdom] Failed to clone light DOM child:", child, err);
577
+ }
578
+ }
579
+ clone.appendChild(lightDomContent);
580
+ }
552
581
  }
553
- clone.appendChild(lightDomContent);
582
+ } catch (err) {
583
+ console.error("[Snapdom] Error cloning children:", err);
584
+ throw err;
554
585
  }
586
+ return clone;
587
+ } catch (error) {
588
+ console.error("[Snapdom] Error in deepClone:", error);
589
+ throw error;
555
590
  }
556
- return clone;
557
591
  }
558
592
 
559
593
  // src/modules/iconFonts.js
@@ -940,7 +974,8 @@ function inlineExternalDef(root) {
940
974
 
941
975
  // src/core/prepare.js
942
976
  async function prepareClone(element, compress = false, embedFonts = false, options = {}) {
943
- let clone, classCSS;
977
+ let clone;
978
+ let classCSS = "";
944
979
  try {
945
980
  clone = deepClone(element, compress, options, element);
946
981
  } catch (e) {
@@ -1247,8 +1282,10 @@ async function createBackground(url, { dpr = 1, scale = 1 }, backgroundColor) {
1247
1282
  ctx.drawImage(baseCanvas, 0, 0);
1248
1283
  return temp;
1249
1284
  }
1250
- async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor = "#fff", quality }, format = "png") {
1251
- const canvas = await createBackground(url, { dpr, scale }, backgroundColor);
1285
+ async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor, quality }, format = "png") {
1286
+ const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
1287
+ const finalBg = backgroundColor ?? defaultBg;
1288
+ const canvas = await createBackground(url, { dpr, scale }, finalBg);
1252
1289
  const img = new Image();
1253
1290
  img.src = canvas.toDataURL(`image/${format}`, quality);
1254
1291
  await img.decode();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zumer/snapdom",
3
- "version": "1.9.6-dev.0",
3
+ "version": "1.9.6-dev.2",
4
4
  "description": "snapDOM captures HTML elements to images with exceptional speed and accuracy.",
5
5
  "main": "dist/snapdom.js",
6
6
  "module": "dist/snapdom.mjs",
@@ -15,6 +15,9 @@
15
15
  "bump": "npx @zumerbox/bump && npx @zumerbox/changelog",
16
16
  "prebuild": "git add CHANGELOG.md && git commit -m \"Bumped version\" && git push --follow-tags"
17
17
  },
18
+ "engines": {
19
+ "node": ">= 20"
20
+ },
18
21
  "repository": {
19
22
  "type": "git",
20
23
  "url": "git+https://github.com/zumerlab/snapdom.git"
@@ -23,7 +23,7 @@ declare module "@zumer/snapdom" {
23
23
  toRaw(): string;
24
24
  toImg(): Promise<HTMLImageElement>;
25
25
  toCanvas(): Promise<HTMLCanvasElement>;
26
- toBlob(): Promise<Blob>;
26
+ toBlob(options?: SnapOptions): Promise<Blob>;
27
27
  toPng(options?: SnapOptions): Promise<HTMLImageElement>;
28
28
  toJpg(options?: SnapOptions): Promise<HTMLImageElement>;
29
29
  toWebp(options?: SnapOptions): Promise<HTMLImageElement>;