@zumer/snapdom 1.9.6-dev.2 → 1.9.7

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.2
4
+ * v.1.9.7
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -223,13 +223,10 @@ function parseContent(content) {
223
223
  return clean;
224
224
  }
225
225
  function extractURL(value) {
226
- const urlStart = value.indexOf("url(");
227
- if (urlStart === -1) return null;
228
- let url = value.slice(urlStart + 4).trim();
229
- if (url.endsWith(")")) url = url.slice(0, -1).trim();
230
- if (url.startsWith('"') && url.endsWith('"') || url.startsWith("'") && url.endsWith("'")) {
231
- url = url.slice(1, -1);
232
- }
226
+ const match = value.match(/url\((['"]?)(.*?)(\1)\)/);
227
+ if (!match) return null;
228
+ const url = match[2].trim();
229
+ if (url.startsWith("#")) return null;
233
230
  return url;
234
231
  }
235
232
  function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
@@ -266,19 +263,15 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
266
263
  try {
267
264
  return await fetchBlobAsDataURL(proxied);
268
265
  } catch {
269
- console.error(`[SnapDOM - fetchImage] Proxy fallback failed for: ${url}`);
270
- throw new Error("CORS restrictions prevented image capture (even via proxy)");
266
+ throw new Error("[SnapDOM - fetchImage] CORS restrictions prevented image capture (even via proxy)");
271
267
  }
272
268
  } else {
273
- console.error(`[SnapDOM - fetchImage] No valid proxy URL provided for fallback: ${url}`);
274
- throw new Error("Fetch fallback failed and no proxy provided");
269
+ throw new Error("[SnapDOM - fetchImage] Fetch fallback failed and no proxy provided");
275
270
  }
276
271
  }
277
272
  }
278
273
  const crossOriginValue = getCrossOriginMode(src);
279
- console.log(`[SnapDOM - fetchImage] Start loading image: ${src} with crossOrigin=${crossOriginValue}`);
280
274
  if (cache.image.has(src)) {
281
- console.log(`[SnapDOM - fetchImage] Cache hit for: ${src}`);
282
275
  return Promise.resolve(cache.image.get(src));
283
276
  }
284
277
  const isDataURI = src.startsWith("data:image/");
@@ -305,8 +298,7 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
305
298
  }
306
299
  return new Promise((resolve, reject) => {
307
300
  const timeoutId = setTimeout(() => {
308
- console.log(`[SnapDOM - fetchImage] Timeout after ${timeout}ms for image: ${src}`);
309
- reject(new Error("Image load timed out"));
301
+ reject(new Error("[SnapDOM - fetchImage] Image load timed out"));
310
302
  }, timeout);
311
303
  const image = new Image();
312
304
  image.crossOrigin = crossOriginValue;
@@ -436,158 +428,157 @@ function inlineAllStyles(source, clone, compress) {
436
428
  }
437
429
 
438
430
  // src/core/clone.js
439
- function isSlotElement(node) {
440
- return node.nodeType === Node.ELEMENT_NODE && node.tagName === "SLOT";
441
- }
442
431
  function deepClone(node, compress, options = {}, originalRoot) {
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") {
432
+ if (!node) throw new Error("Invalid node");
433
+ const clonedAssignedNodes = /* @__PURE__ */ new Set();
434
+ let pendingSelectValue = null;
435
+ if (node.nodeType === Node.TEXT_NODE) {
436
+ return node.cloneNode(true);
437
+ }
438
+ if (node.nodeType !== Node.ELEMENT_NODE) {
439
+ return node.cloneNode(true);
440
+ }
441
+ if (node.getAttribute("data-capture") === "exclude") {
442
+ const spacer = document.createElement("div");
443
+ const rect = node.getBoundingClientRect();
444
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
445
+ return spacer;
446
+ }
447
+ if (options.exclude && Array.isArray(options.exclude)) {
448
+ for (const selector of options.exclude) {
474
449
  try {
475
- if (!options.filter(node, originalRoot || node)) {
450
+ if (node.matches?.(selector)) {
476
451
  const spacer = document.createElement("div");
477
452
  const rect = node.getBoundingClientRect();
478
- spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
453
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
479
454
  return spacer;
480
455
  }
481
456
  } catch (err) {
482
- console.warn("Error in filter function:", err);
457
+ console.warn(`Invalid selector in exclude option: ${selector}`, err);
483
458
  }
484
459
  }
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;
460
+ }
461
+ if (typeof options.filter === "function") {
512
462
  try {
513
- clone = node.cloneNode(false);
514
- if (!clone) {
515
- throw new Error("Failed to clone node: cloneNode returned null");
463
+ if (!options.filter(node, originalRoot || node)) {
464
+ const spacer = document.createElement("div");
465
+ const rect = node.getBoundingClientRect();
466
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
467
+ return spacer;
516
468
  }
517
- cache.preNodeMap.set(clone, node);
518
469
  } catch (err) {
519
- console.error("[Snapdom] Failed to clone node:", node, err);
520
- throw err;
470
+ console.warn("Error in filter function:", err);
521
471
  }
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", "");
528
- }
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
- });
472
+ }
473
+ if (node.tagName === "IFRAME") {
474
+ const fallback = document.createElement("div");
475
+ 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;`;
476
+ return fallback;
477
+ }
478
+ if (node.getAttribute("data-capture") === "placeholder") {
479
+ const clone2 = node.cloneNode(false);
480
+ cache.preNodeMap.set(clone2, node);
481
+ inlineAllStyles(node, clone2, compress);
482
+ const placeholder = document.createElement("div");
483
+ placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
484
+ placeholder.style.cssText = `color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;`;
485
+ clone2.appendChild(placeholder);
486
+ return clone2;
487
+ }
488
+ if (node.tagName === "CANVAS") {
489
+ const dataURL = node.toDataURL();
490
+ const img = document.createElement("img");
491
+ img.src = dataURL;
492
+ img.width = node.width;
493
+ img.height = node.height;
494
+ cache.preNodeMap.set(img, node);
495
+ inlineAllStyles(node, img, compress);
496
+ return img;
497
+ }
498
+ let clone;
499
+ try {
500
+ clone = node.cloneNode(false);
501
+ cache.preNodeMap.set(clone, node);
502
+ } catch (err) {
503
+ console.error("[Snapdom] Failed to clone node:", node, err);
504
+ throw err;
505
+ }
506
+ if (node instanceof HTMLTextAreaElement) {
507
+ clone.textContent = node.value;
508
+ clone.value = node.value;
509
+ const rect = node.getBoundingClientRect();
510
+ clone.style.width = `${rect.width}px`;
511
+ clone.style.height = `${rect.height}px`;
512
+ return clone;
513
+ }
514
+ if (node instanceof HTMLInputElement) {
515
+ clone.value = node.value;
516
+ clone.setAttribute("value", node.value);
517
+ if (node.checked !== void 0) {
518
+ clone.checked = node.checked;
519
+ if (node.checked) clone.setAttribute("checked", "");
520
+ if (node.indeterminate) clone.indeterminate = node.indeterminate;
543
521
  }
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);
522
+ }
523
+ if (node instanceof HTMLSelectElement) {
524
+ pendingSelectValue = node.value;
525
+ }
526
+ inlineAllStyles(node, clone, compress);
527
+ if (node.shadowRoot) {
528
+ const hasSlot = Array.from(node.shadowRoot.querySelectorAll("slot")).length > 0;
529
+ if (hasSlot) {
530
+ for (const child of node.shadowRoot.childNodes) {
531
+ if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
532
+ const cssText = child.textContent || "";
533
+ if (cssText.trim() && compress) {
534
+ if (!cache.preStyle) cache.preStyle = /* @__PURE__ */ new WeakMap();
535
+ cache.preStyle.set(child, cssText);
567
536
  }
568
537
  }
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
- }
538
+ }
539
+ } else {
540
+ const shadowFrag = document.createDocumentFragment();
541
+ for (const child of node.shadowRoot.childNodes) {
542
+ if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
543
+ const cssText = child.textContent || "";
544
+ if (cssText.trim() && compress) {
545
+ if (!cache.preStyle) cache.preStyle = /* @__PURE__ */ new WeakMap();
546
+ cache.preStyle.set(child, cssText);
578
547
  }
579
- clone.appendChild(lightDomContent);
548
+ continue;
580
549
  }
550
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
551
+ if (clonedChild) shadowFrag.appendChild(clonedChild);
581
552
  }
582
- } catch (err) {
583
- console.error("[Snapdom] Error cloning children:", err);
584
- throw err;
553
+ clone.appendChild(shadowFrag);
585
554
  }
586
- return clone;
587
- } catch (error) {
588
- console.error("[Snapdom] Error in deepClone:", error);
589
- throw error;
590
555
  }
556
+ if (node.tagName === "SLOT") {
557
+ const assigned = node.assignedNodes?.({ flatten: true }) || [];
558
+ const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
559
+ const fragment = document.createDocumentFragment();
560
+ for (const child of nodesToClone) {
561
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
562
+ if (clonedChild) fragment.appendChild(clonedChild);
563
+ }
564
+ return fragment;
565
+ }
566
+ for (const child of node.childNodes) {
567
+ if (clonedAssignedNodes.has(child)) continue;
568
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
569
+ if (clonedChild) clone.appendChild(clonedChild);
570
+ }
571
+ if (pendingSelectValue !== null && clone instanceof HTMLSelectElement) {
572
+ clone.value = pendingSelectValue;
573
+ for (const opt of clone.options) {
574
+ if (opt.value === pendingSelectValue) {
575
+ opt.setAttribute("selected", "");
576
+ } else {
577
+ opt.removeAttribute("selected");
578
+ }
579
+ }
580
+ }
581
+ return clone;
591
582
  }
592
583
 
593
584
  // src/modules/iconFonts.js
@@ -747,6 +738,13 @@ async function embedCustomFonts({ preCached = false } = {}) {
747
738
  const family = rule.style.getPropertyValue("font-family");
748
739
  if (!src || isIconFont(family)) continue;
749
740
  const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
741
+ const localRegex = /local\((["']?)[^)]+?\1\)/g;
742
+ const hasURL = !!src.match(urlRegex);
743
+ const hasLocal = !!src.match(localRegex);
744
+ if (!hasURL && hasLocal) {
745
+ finalCSS += `@font-face{font-family:${family};src:${src};font-style:${rule.style.getPropertyValue("font-style") || "normal"};font-weight:${rule.style.getPropertyValue("font-weight") || "normal"};}`;
746
+ continue;
747
+ }
750
748
  let inlinedSrc = src;
751
749
  const matches = Array.from(src.matchAll(urlRegex));
752
750
  for (const match of matches) {
@@ -778,13 +776,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
778
776
  console.warn("[snapdom] Failed to fetch font URL:", url);
779
777
  }
780
778
  }
781
- finalCSS += `@font-face {
782
- font-family: ${family};
783
- src: ${inlinedSrc};
784
- font-style: ${rule.style.getPropertyValue("font-style") || "normal"};
785
- font-weight: ${rule.style.getPropertyValue("font-weight") || "normal"};
786
- }
787
- `;
779
+ finalCSS += `@font-face{font-family:${family};src:${inlinedSrc};font-style:${rule.style.getPropertyValue("font-style") || "normal"};font-weight:${rule.style.getPropertyValue("font-weight") || "normal"};}`;
788
780
  }
789
781
  }
790
782
  }
@@ -817,13 +809,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
817
809
  }
818
810
  }
819
811
  }
820
- finalCSS += `@font-face {
821
- font-family: '${font.family}';
822
- src: url(${b64});
823
- font-style: ${font.style || "normal"};
824
- font-weight: ${font.weight || "normal"};
825
- }
826
- `;
812
+ finalCSS += `@font-face{font-family:'${font.family}';src:url(${b64});font-style:${font.style || "normal"};font-weight:${font.weight || "normal"};}`;
827
813
  }
828
814
  }
829
815
  if (finalCSS) {
@@ -861,9 +847,9 @@ async function inlinePseudoElements(source, clone, compress, embedFonts = false,
861
847
  const span = document.createElement("span");
862
848
  span.textContent = first;
863
849
  span.dataset.snapdomPseudo = "::first-letter";
864
- const snapshot = snapshotComputedStyle(style);
865
- const key = getStyleKey(snapshot, "span", compress);
866
- cache.preStyleMap.set(span, key);
850
+ const snapshot2 = snapshotComputedStyle(style);
851
+ const key2 = getStyleKey(snapshot2, "span", compress);
852
+ cache.preStyleMap.set(span, key2);
867
853
  const restNode = document.createTextNode(rest);
868
854
  clone.replaceChild(restNode, textNode);
869
855
  clone.insertBefore(span, restNode);
@@ -872,59 +858,73 @@ async function inlinePseudoElements(source, clone, compress, embedFonts = false,
872
858
  const content = style.getPropertyValue("content");
873
859
  const bg = style.getPropertyValue("background-image");
874
860
  const bgColor = style.getPropertyValue("background-color");
861
+ const fontFamily = style.getPropertyValue("font-family");
862
+ const fontSize = parseInt(style.getPropertyValue("font-size")) || 32;
863
+ const fontWeight = parseInt(style.getPropertyValue("font-weight")) || false;
864
+ const color = style.getPropertyValue("color") || "#000";
865
+ const display = style.getPropertyValue("display");
866
+ const width = parseFloat(style.getPropertyValue("width"));
867
+ const height = parseFloat(style.getPropertyValue("height"));
868
+ const borderStyle = style.getPropertyValue("border-style");
869
+ const transform = style.getPropertyValue("transform");
870
+ const isIconFont2 = isIconFont(fontFamily);
871
+ let cleanContent;
872
+ if (/counter\s*\(|counters\s*\(/.test(content)) {
873
+ cleanContent = "- ";
874
+ } else {
875
+ cleanContent = parseContent(content);
876
+ }
875
877
  const hasContent = content !== "none";
878
+ const hasExplicitContent = hasContent && cleanContent !== "";
876
879
  const hasBg = bg && bg !== "none";
877
880
  const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
878
- if (hasContent || hasBg || hasBgColor) {
879
- const fontFamily = style.getPropertyValue("font-family");
880
- const fontSize = parseInt(style.getPropertyValue("font-size")) || 32;
881
- const fontWeight = parseInt(style.getPropertyValue("font-weight")) || false;
882
- const color = style.getPropertyValue("color") || "#000";
883
- const pseudoEl = document.createElement("span");
884
- pseudoEl.dataset.snapdomPseudo = pseudo;
885
- const snapshot = snapshotComputedStyle(style);
886
- const key = getStyleKey(snapshot, "span", compress);
887
- cache.preStyleMap.set(pseudoEl, key);
888
- const isIconFont2 = isIconFont(fontFamily);
889
- const cleanContent = parseContent(content);
890
- if (isIconFont2 && cleanContent.length === 1) {
891
- const imgEl = document.createElement("img");
892
- imgEl.src = await iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
893
- imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
894
- pseudoEl.appendChild(imgEl);
895
- } else if (cleanContent.startsWith("url(")) {
896
- const rawUrl = extractURL(cleanContent);
897
- if (rawUrl && rawUrl.trim() !== "") {
898
- try {
899
- const imgEl = document.createElement("img");
900
- const dataUrl = await fetchImage(safeEncodeURI(rawUrl, { useProxy }));
901
- imgEl.src = dataUrl;
902
- imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
903
- pseudoEl.appendChild(imgEl);
904
- } catch (e) {
905
- console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
906
- }
907
- }
908
- } else if (!isIconFont2 && cleanContent && cleanContent !== "none") {
909
- pseudoEl.textContent = cleanContent;
910
- }
911
- if (hasBg) {
881
+ const hasBox = display !== "inline" && (width > 0 || height > 0);
882
+ const hasBorder = borderStyle && borderStyle !== "none";
883
+ const hasTransform = transform && transform !== "none";
884
+ const shouldRender = hasExplicitContent || hasBg || hasBgColor || hasBox || hasBorder || hasTransform;
885
+ if (!shouldRender) continue;
886
+ const pseudoEl = document.createElement("span");
887
+ pseudoEl.dataset.snapdomPseudo = pseudo;
888
+ const snapshot = snapshotComputedStyle(style);
889
+ const key = getStyleKey(snapshot, "span", compress);
890
+ cache.preStyleMap.set(pseudoEl, key);
891
+ if (isIconFont2 && cleanContent.length === 1) {
892
+ const imgEl = document.createElement("img");
893
+ imgEl.src = await iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
894
+ imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
895
+ pseudoEl.appendChild(imgEl);
896
+ } else if (cleanContent.startsWith("url(")) {
897
+ const rawUrl = extractURL(cleanContent);
898
+ if (rawUrl && rawUrl.trim() !== "") {
912
899
  try {
913
- const bgSplits = splitBackgroundImage(bg);
914
- const newBgParts = await Promise.all(
915
- bgSplits.map((entry) => inlineSingleBackgroundEntry(entry))
916
- );
917
- pseudoEl.style.backgroundImage = newBgParts.join(", ");
900
+ const imgEl = document.createElement("img");
901
+ const dataUrl = await fetchImage(safeEncodeURI(rawUrl, { useProxy }));
902
+ imgEl.src = dataUrl;
903
+ imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
904
+ pseudoEl.appendChild(imgEl);
918
905
  } catch (e) {
919
- console.warn(`[snapdom] Failed to inline background-image for ${pseudo}`, e);
906
+ console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
920
907
  }
921
908
  }
922
- if (hasBgColor) pseudoEl.style.backgroundColor = bgColor;
923
- const hasContent2 = pseudoEl.childNodes.length > 0 || pseudoEl.textContent && pseudoEl.textContent.trim() !== "";
924
- const hasVisibleBox = hasContent2 || hasBg || hasBgColor;
925
- if (!hasVisibleBox) continue;
926
- pseudo === "::before" ? clone.insertBefore(pseudoEl, clone.firstChild) : clone.appendChild(pseudoEl);
909
+ } else if (!isIconFont2 && hasExplicitContent) {
910
+ pseudoEl.textContent = cleanContent;
927
911
  }
912
+ if (hasBg) {
913
+ try {
914
+ const bgSplits = splitBackgroundImage(bg);
915
+ const newBgParts = await Promise.all(
916
+ bgSplits.map((entry) => inlineSingleBackgroundEntry(entry))
917
+ );
918
+ pseudoEl.style.backgroundImage = newBgParts.join(", ");
919
+ } catch (e) {
920
+ console.warn(`[snapdom] Failed to inline background-image for ${pseudo}`, e);
921
+ }
922
+ }
923
+ if (hasBgColor) pseudoEl.style.backgroundColor = bgColor;
924
+ const hasContent2 = pseudoEl.childNodes.length > 0 || pseudoEl.textContent && pseudoEl.textContent.trim() !== "";
925
+ const hasVisibleBox = hasContent2 || hasBg || hasBgColor || hasBox || hasBorder || hasTransform;
926
+ if (!hasVisibleBox) continue;
927
+ pseudo === "::before" ? clone.insertBefore(pseudoEl, clone.firstChild) : clone.appendChild(pseudoEl);
928
928
  } catch (e) {
929
929
  console.warn(`[snapdom] Failed to capture ${pseudo} for`, source, e);
930
930
  }
@@ -1194,8 +1194,6 @@ async function captureDOM(element, options = {}) {
1194
1194
  const existingTransform = clone.style.transform || "";
1195
1195
  const scaleTransform = `scale(${scaleX}, ${scaleY})`;
1196
1196
  clone.style.transform = `${scaleTransform} ${existingTransform}`.trim();
1197
- } else if (hasScale && isSafari()) {
1198
- clone.style.scale = `${scale}`;
1199
1197
  }
1200
1198
  const svgNS = "http://www.w3.org/2000/svg";
1201
1199
  const fo = document.createElementNS(svgNS, "foreignObject");
@@ -1224,29 +1222,41 @@ async function toImg(url, { dpr = 1, scale = 1 }) {
1224
1222
  const img = new Image();
1225
1223
  img.src = url;
1226
1224
  await img.decode();
1227
- if (isSafari) {
1228
- img.width = img.width * scale;
1229
- img.height = img.height * scale;
1230
- } else {
1231
- img.width = img.width / scale;
1232
- img.height = img.height / scale;
1233
- }
1225
+ img.width = img.width * scale;
1226
+ img.height = img.height * scale;
1234
1227
  return img;
1235
1228
  }
1236
1229
  async function toCanvas(url, { dpr = 1, scale = 1 } = {}) {
1237
1230
  const img = new Image();
1238
1231
  img.src = url;
1232
+ img.crossOrigin = "anonymous";
1233
+ img.loading = "eager";
1234
+ img.decoding = "sync";
1235
+ const isSafariBrowser = isSafari();
1236
+ let appended = false;
1237
+ if (isSafariBrowser) {
1238
+ document.body.appendChild(img);
1239
+ appended = true;
1240
+ }
1239
1241
  await img.decode();
1240
- const canvas = document.createElement("canvas");
1242
+ if (isSafariBrowser) {
1243
+ await new Promise((resolve) => setTimeout(resolve, 100));
1244
+ }
1245
+ if (img.width === 0 || img.height === 0) {
1246
+ if (appended) img.remove();
1247
+ throw new Error("Image failed to load or has no dimensions");
1248
+ }
1241
1249
  const width = img.width * scale;
1242
1250
  const height = img.height * scale;
1251
+ const canvas = document.createElement("canvas");
1243
1252
  canvas.width = Math.ceil(width * dpr);
1244
1253
  canvas.height = Math.ceil(height * dpr);
1254
+ canvas.style.width = `${width}px`;
1255
+ canvas.style.height = `${height}px`;
1245
1256
  const ctx = canvas.getContext("2d");
1246
1257
  ctx.scale(dpr, dpr);
1247
1258
  ctx.drawImage(img, 0, 0, width, height);
1248
- canvas.style.width = `${width}px`;
1249
- canvas.style.height = `${height}px`;
1259
+ if (appended) img.remove();
1250
1260
  return canvas;
1251
1261
  }
1252
1262
  async function toBlob(url, {
@@ -1293,7 +1303,7 @@ async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor, quality }
1293
1303
  img.style.height = `${canvas.height / dpr}px`;
1294
1304
  return img;
1295
1305
  }
1296
- async function download(url, { dpr = 1, scale = 1, backgroundColor = "#fff", format = "png", filename = "capture" } = {}) {
1306
+ async function download(url, { dpr = 1, scale = 1, backgroundColor, format = "png", filename = "snapDOM" } = {}) {
1297
1307
  if (format === "svg") {
1298
1308
  const blob = await toBlob(url);
1299
1309
  const objectURL = URL.createObjectURL(blob);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zumer/snapdom",
3
- "version": "1.9.6-dev.2",
3
+ "version": "1.9.7",
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,9 +15,6 @@
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
- },
21
18
  "repository": {
22
19
  "type": "git",
23
20
  "url": "git+https://github.com/zumerlab/snapdom.git"