@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/README.md +5 -3
- package/dist/snapdom.js +353 -471
- package/dist/snapdom.min.js +3 -15
- package/dist/snapdom.min.mjs +3 -15
- package/dist/snapdom.mjs +232 -222
- package/package.json +1 -4
package/dist/snapdom.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.1.9.
|
|
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
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
if (url.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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 (
|
|
450
|
+
if (node.matches?.(selector)) {
|
|
476
451
|
const spacer = document.createElement("div");
|
|
477
452
|
const rect = node.getBoundingClientRect();
|
|
478
|
-
spacer.style.cssText = `display:
|
|
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(
|
|
457
|
+
console.warn(`Invalid selector in exclude option: ${selector}`, err);
|
|
483
458
|
}
|
|
484
459
|
}
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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.
|
|
520
|
-
throw err;
|
|
470
|
+
console.warn("Error in filter function:", err);
|
|
521
471
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
548
|
+
continue;
|
|
580
549
|
}
|
|
550
|
+
const clonedChild = deepClone(child, compress, options, originalRoot || node);
|
|
551
|
+
if (clonedChild) shadowFrag.appendChild(clonedChild);
|
|
581
552
|
}
|
|
582
|
-
|
|
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
|
|
865
|
-
const
|
|
866
|
-
cache.preStyleMap.set(span,
|
|
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
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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
|
|
914
|
-
const
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
pseudoEl.
|
|
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.
|
|
906
|
+
console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
|
|
920
907
|
}
|
|
921
908
|
}
|
|
922
|
-
|
|
923
|
-
|
|
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
|
-
|
|
1228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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"
|