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

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
@@ -440,120 +440,155 @@ 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
+ img.style.display = "inline-block";
508
+ img.style.width = node.style.width || `${node.width}px`;
509
+ img.style.height = node.style.height || `${node.height}px`;
510
+ return img;
511
+ }
512
+ let clone;
468
513
  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;
514
+ clone = node.cloneNode(false);
515
+ if (!clone) {
516
+ throw new Error("Failed to clone node: cloneNode returned null");
474
517
  }
518
+ cache.preNodeMap.set(clone, node);
475
519
  } 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", "");
520
+ console.error("[Snapdom] Failed to clone node:", node, err);
521
+ throw err;
514
522
  }
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");
523
+ if (node instanceof HTMLInputElement) {
524
+ clone.value = node.value;
525
+ clone.setAttribute("value", node.value);
526
+ if (node.checked !== void 0) {
527
+ clone.checked = node.checked;
528
+ if (node.checked) clone.setAttribute("checked", "");
527
529
  }
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);
530
+ } else if (node instanceof HTMLTextAreaElement) {
531
+ const rect = node.getBoundingClientRect();
532
+ clone.textContent = node.value;
533
+ clone.style.width = `${rect.width}px`;
534
+ clone.style.height = `${rect.height}px`;
535
+ } else if (node instanceof HTMLSelectElement) {
536
+ clone.value = node.value;
537
+ Array.from(clone.options).forEach((opt) => {
538
+ if (opt.value === node.value) {
539
+ opt.setAttribute("selected", "");
540
+ } else {
541
+ opt.removeAttribute("selected");
542
+ }
543
+ });
546
544
  }
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);
545
+ inlineAllStyles(node, clone, compress);
546
+ try {
547
+ if (isSlotElement(node)) {
548
+ const assigned = node.assignedNodes?.({ flatten: true }) || [];
549
+ const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
550
+ const fragment = document.createDocumentFragment();
551
+ for (const child of nodesToClone) {
552
+ try {
553
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
554
+ if (clonedChild) fragment.appendChild(clonedChild);
555
+ } catch (err) {
556
+ console.warn("[Snapdom] Failed to clone slot content:", child, err);
557
+ }
558
+ }
559
+ return fragment;
560
+ } else {
561
+ const baseChildren = node.shadowRoot ? node.shadowRoot.childNodes : node.childNodes;
562
+ for (const child of baseChildren) {
563
+ try {
564
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
565
+ if (clonedChild) clone.appendChild(clonedChild);
566
+ } catch (err) {
567
+ console.warn("[Snapdom] Failed to clone child node:", child, err);
568
+ }
569
+ }
570
+ if (node.shadowRoot && node.childNodes.length > 0 && !node.shadowRoot.querySelector("slot")) {
571
+ const lightDomContent = document.createDocumentFragment();
572
+ for (const child of node.childNodes) {
573
+ try {
574
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
575
+ if (clonedChild) lightDomContent.appendChild(clonedChild);
576
+ } catch (err) {
577
+ console.warn("[Snapdom] Failed to clone light DOM child:", child, err);
578
+ }
579
+ }
580
+ clone.appendChild(lightDomContent);
581
+ }
552
582
  }
553
- clone.appendChild(lightDomContent);
583
+ } catch (err) {
584
+ console.error("[Snapdom] Error cloning children:", err);
585
+ throw err;
554
586
  }
587
+ return clone;
588
+ } catch (error) {
589
+ console.error("[Snapdom] Error in deepClone:", error);
590
+ throw error;
555
591
  }
556
- return clone;
557
592
  }
558
593
 
559
594
  // src/modules/iconFonts.js
@@ -940,7 +975,8 @@ function inlineExternalDef(root) {
940
975
 
941
976
  // src/core/prepare.js
942
977
  async function prepareClone(element, compress = false, embedFonts = false, options = {}) {
943
- let clone, classCSS;
978
+ let clone;
979
+ let classCSS = "";
944
980
  try {
945
981
  clone = deepClone(element, compress, options, element);
946
982
  } catch (e) {
@@ -1247,8 +1283,10 @@ async function createBackground(url, { dpr = 1, scale = 1 }, backgroundColor) {
1247
1283
  ctx.drawImage(baseCanvas, 0, 0);
1248
1284
  return temp;
1249
1285
  }
1250
- async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor = "#fff", quality }, format = "png") {
1251
- const canvas = await createBackground(url, { dpr, scale }, backgroundColor);
1286
+ async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor, quality }, format = "png") {
1287
+ const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
1288
+ const finalBg = backgroundColor ?? defaultBg;
1289
+ const canvas = await createBackground(url, { dpr, scale }, finalBg);
1252
1290
  const img = new Image();
1253
1291
  img.src = canvas.toDataURL(`image/${format}`, quality);
1254
1292
  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.1",
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>;