@zumer/snapdom 1.9.6-dev.2 → 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 -411
- package/dist/snapdom.min.js +3 -15
- package/dist/snapdom.min.mjs +3 -15
- package/dist/snapdom.mjs +154 -162
- 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,158 +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
|
-
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
|
-
|
|
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
|
+
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);
|
|
567
537
|
}
|
|
568
538
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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);
|
|
578
548
|
}
|
|
579
|
-
|
|
549
|
+
continue;
|
|
580
550
|
}
|
|
551
|
+
const clonedChild = deepClone(child, compress, options, originalRoot || node);
|
|
552
|
+
if (clonedChild) shadowFrag.appendChild(clonedChild);
|
|
581
553
|
}
|
|
582
|
-
|
|
583
|
-
console.error("[Snapdom] Error cloning children:", err);
|
|
584
|
-
throw err;
|
|
554
|
+
clone.appendChild(shadowFrag);
|
|
585
555
|
}
|
|
586
|
-
return clone;
|
|
587
|
-
} catch (error) {
|
|
588
|
-
console.error("[Snapdom] Error in deepClone:", error);
|
|
589
|
-
throw error;
|
|
590
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;
|
|
591
583
|
}
|
|
592
584
|
|
|
593
585
|
// src/modules/iconFonts.js
|
|
@@ -747,6 +739,13 @@ async function embedCustomFonts({ preCached = false } = {}) {
|
|
|
747
739
|
const family = rule.style.getPropertyValue("font-family");
|
|
748
740
|
if (!src || isIconFont(family)) continue;
|
|
749
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
|
+
}
|
|
750
749
|
let inlinedSrc = src;
|
|
751
750
|
const matches = Array.from(src.matchAll(urlRegex));
|
|
752
751
|
for (const match of matches) {
|
|
@@ -778,13 +777,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
|
|
|
778
777
|
console.warn("[snapdom] Failed to fetch font URL:", url);
|
|
779
778
|
}
|
|
780
779
|
}
|
|
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
|
-
`;
|
|
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"};}`;
|
|
788
781
|
}
|
|
789
782
|
}
|
|
790
783
|
}
|
|
@@ -817,13 +810,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
|
|
|
817
810
|
}
|
|
818
811
|
}
|
|
819
812
|
}
|
|
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
|
-
`;
|
|
813
|
+
finalCSS += `@font-face{font-family:'${font.family}';src:url(${b64});font-style:${font.style || "normal"};font-weight:${font.weight || "normal"};}`;
|
|
827
814
|
}
|
|
828
815
|
}
|
|
829
816
|
if (finalCSS) {
|
|
@@ -886,7 +873,12 @@ async function inlinePseudoElements(source, clone, compress, embedFonts = false,
|
|
|
886
873
|
const key = getStyleKey(snapshot, "span", compress);
|
|
887
874
|
cache.preStyleMap.set(pseudoEl, key);
|
|
888
875
|
const isIconFont2 = isIconFont(fontFamily);
|
|
889
|
-
|
|
876
|
+
let cleanContent;
|
|
877
|
+
if (/counter\s*\(|counters\s*\(/.test(content)) {
|
|
878
|
+
cleanContent = "- ";
|
|
879
|
+
} else {
|
|
880
|
+
cleanContent = parseContent(content);
|
|
881
|
+
}
|
|
890
882
|
if (isIconFont2 && cleanContent.length === 1) {
|
|
891
883
|
const imgEl = document.createElement("img");
|
|
892
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"
|