@zumer/snapdom 1.9.6-dev.1 → 1.9.6
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.js +275 -412
- package/dist/snapdom.min.js +3 -15
- package/dist/snapdom.min.mjs +3 -15
- package/dist/snapdom.mjs +154 -163
- 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.6
|
|
4
|
+
* v.1.9.6
|
|
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,159 +428,158 @@ 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
|
-
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;
|
|
460
|
+
}
|
|
461
|
+
if (typeof options.filter === "function") {
|
|
513
462
|
try {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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;
|
|
517
468
|
}
|
|
518
|
-
cache.preNodeMap.set(clone, node);
|
|
519
469
|
} catch (err) {
|
|
520
|
-
console.
|
|
521
|
-
throw err;
|
|
470
|
+
console.warn("Error in filter function:", err);
|
|
522
471
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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;
|
|
544
521
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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);
|
|
522
|
+
return clone;
|
|
523
|
+
}
|
|
524
|
+
if (node instanceof HTMLSelectElement) {
|
|
525
|
+
pendingSelectValue = node.value;
|
|
526
|
+
}
|
|
527
|
+
inlineAllStyles(node, clone, compress);
|
|
528
|
+
if (node.shadowRoot) {
|
|
529
|
+
const hasSlot = Array.from(node.shadowRoot.querySelectorAll("slot")).length > 0;
|
|
530
|
+
if (hasSlot) {
|
|
531
|
+
for (const child of node.shadowRoot.childNodes) {
|
|
532
|
+
if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
|
|
533
|
+
const cssText = child.textContent || "";
|
|
534
|
+
if (cssText.trim() && compress) {
|
|
535
|
+
if (!cache.preStyle) cache.preStyle = /* @__PURE__ */ new WeakMap();
|
|
536
|
+
cache.preStyle.set(child, cssText);
|
|
568
537
|
}
|
|
569
538
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
const shadowFrag = document.createDocumentFragment();
|
|
542
|
+
for (const child of node.shadowRoot.childNodes) {
|
|
543
|
+
if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
|
|
544
|
+
const cssText = child.textContent || "";
|
|
545
|
+
if (cssText.trim() && compress) {
|
|
546
|
+
if (!cache.preStyle) cache.preStyle = /* @__PURE__ */ new WeakMap();
|
|
547
|
+
cache.preStyle.set(child, cssText);
|
|
579
548
|
}
|
|
580
|
-
|
|
549
|
+
continue;
|
|
581
550
|
}
|
|
551
|
+
const clonedChild = deepClone(child, compress, options, originalRoot || node);
|
|
552
|
+
if (clonedChild) shadowFrag.appendChild(clonedChild);
|
|
582
553
|
}
|
|
583
|
-
|
|
584
|
-
console.error("[Snapdom] Error cloning children:", err);
|
|
585
|
-
throw err;
|
|
554
|
+
clone.appendChild(shadowFrag);
|
|
586
555
|
}
|
|
587
|
-
return clone;
|
|
588
|
-
} catch (error) {
|
|
589
|
-
console.error("[Snapdom] Error in deepClone:", error);
|
|
590
|
-
throw error;
|
|
591
556
|
}
|
|
557
|
+
if (node.tagName === "SLOT") {
|
|
558
|
+
const assigned = node.assignedNodes?.({ flatten: true }) || [];
|
|
559
|
+
const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
|
|
560
|
+
const fragment = document.createDocumentFragment();
|
|
561
|
+
for (const child of nodesToClone) {
|
|
562
|
+
const clonedChild = deepClone(child, compress, options, originalRoot || node);
|
|
563
|
+
if (clonedChild) fragment.appendChild(clonedChild);
|
|
564
|
+
}
|
|
565
|
+
return fragment;
|
|
566
|
+
}
|
|
567
|
+
for (const child of node.childNodes) {
|
|
568
|
+
if (clonedAssignedNodes.has(child)) continue;
|
|
569
|
+
const clonedChild = deepClone(child, compress, options, originalRoot || node);
|
|
570
|
+
if (clonedChild) clone.appendChild(clonedChild);
|
|
571
|
+
}
|
|
572
|
+
if (pendingSelectValue !== null && clone instanceof HTMLSelectElement) {
|
|
573
|
+
clone.value = pendingSelectValue;
|
|
574
|
+
for (const opt of clone.options) {
|
|
575
|
+
if (opt.value === pendingSelectValue) {
|
|
576
|
+
opt.setAttribute("selected", "");
|
|
577
|
+
} else {
|
|
578
|
+
opt.removeAttribute("selected");
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return clone;
|
|
592
583
|
}
|
|
593
584
|
|
|
594
585
|
// src/modules/iconFonts.js
|
|
@@ -748,6 +739,13 @@ async function embedCustomFonts({ preCached = false } = {}) {
|
|
|
748
739
|
const family = rule.style.getPropertyValue("font-family");
|
|
749
740
|
if (!src || isIconFont(family)) continue;
|
|
750
741
|
const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
|
|
742
|
+
const localRegex = /local\((["']?)[^)]+?\1\)/g;
|
|
743
|
+
const hasURL = urlRegex.test(src);
|
|
744
|
+
const hasLocal = localRegex.test(src);
|
|
745
|
+
if (!hasURL && hasLocal) {
|
|
746
|
+
finalCSS += `@font-face{font-family:${family};src:${src};font-style:${rule.style.getPropertyValue("font-style") || "normal"};font-weight:${rule.style.getPropertyValue("font-weight") || "normal"};}`;
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
751
749
|
let inlinedSrc = src;
|
|
752
750
|
const matches = Array.from(src.matchAll(urlRegex));
|
|
753
751
|
for (const match of matches) {
|
|
@@ -779,13 +777,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
|
|
|
779
777
|
console.warn("[snapdom] Failed to fetch font URL:", url);
|
|
780
778
|
}
|
|
781
779
|
}
|
|
782
|
-
finalCSS += `@font-face {
|
|
783
|
-
font-family: ${family};
|
|
784
|
-
src: ${inlinedSrc};
|
|
785
|
-
font-style: ${rule.style.getPropertyValue("font-style") || "normal"};
|
|
786
|
-
font-weight: ${rule.style.getPropertyValue("font-weight") || "normal"};
|
|
787
|
-
}
|
|
788
|
-
`;
|
|
780
|
+
finalCSS += `@font-face{font-family:${family};src:${inlinedSrc};font-style:${rule.style.getPropertyValue("font-style") || "normal"};font-weight:${rule.style.getPropertyValue("font-weight") || "normal"};}`;
|
|
789
781
|
}
|
|
790
782
|
}
|
|
791
783
|
}
|
|
@@ -818,13 +810,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
|
|
|
818
810
|
}
|
|
819
811
|
}
|
|
820
812
|
}
|
|
821
|
-
finalCSS += `@font-face {
|
|
822
|
-
font-family: '${font.family}';
|
|
823
|
-
src: url(${b64});
|
|
824
|
-
font-style: ${font.style || "normal"};
|
|
825
|
-
font-weight: ${font.weight || "normal"};
|
|
826
|
-
}
|
|
827
|
-
`;
|
|
813
|
+
finalCSS += `@font-face{font-family:'${font.family}';src:url(${b64});font-style:${font.style || "normal"};font-weight:${font.weight || "normal"};}`;
|
|
828
814
|
}
|
|
829
815
|
}
|
|
830
816
|
if (finalCSS) {
|
|
@@ -887,7 +873,12 @@ async function inlinePseudoElements(source, clone, compress, embedFonts = false,
|
|
|
887
873
|
const key = getStyleKey(snapshot, "span", compress);
|
|
888
874
|
cache.preStyleMap.set(pseudoEl, key);
|
|
889
875
|
const isIconFont2 = isIconFont(fontFamily);
|
|
890
|
-
|
|
876
|
+
let cleanContent;
|
|
877
|
+
if (/counter\s*\(|counters\s*\(/.test(content)) {
|
|
878
|
+
cleanContent = "- ";
|
|
879
|
+
} else {
|
|
880
|
+
cleanContent = parseContent(content);
|
|
881
|
+
}
|
|
891
882
|
if (isIconFont2 && cleanContent.length === 1) {
|
|
892
883
|
const imgEl = document.createElement("img");
|
|
893
884
|
imgEl.src = await iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zumer/snapdom",
|
|
3
|
-
"version": "1.9.6
|
|
3
|
+
"version": "1.9.6",
|
|
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"
|