@zumer/snapdom 1.9.3 → 1.9.6-dev.0

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,20 +1,36 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.9.3
4
+ * v.1.9.6-dev.0
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
8
8
 
9
9
 
10
10
  // src/core/cache.js
11
- var imageCache = /* @__PURE__ */ new Map();
12
- var bgCache = /* @__PURE__ */ new Map();
13
- var resourceCache = /* @__PURE__ */ new Map();
14
- var defaultStylesCache = /* @__PURE__ */ new Map();
15
- var baseCSSCache = /* @__PURE__ */ new Map();
16
- var computedStyleCache = /* @__PURE__ */ new WeakMap();
17
- var processedFontURLs = /* @__PURE__ */ new Set();
11
+ var cache = {
12
+ image: /* @__PURE__ */ new Map(),
13
+ background: /* @__PURE__ */ new Map(),
14
+ resource: /* @__PURE__ */ new Map(),
15
+ defaultStyle: /* @__PURE__ */ new Map(),
16
+ baseStyle: /* @__PURE__ */ new Map(),
17
+ computedStyle: /* @__PURE__ */ new WeakMap(),
18
+ font: /* @__PURE__ */ new Set(),
19
+ snapshot: /* @__PURE__ */ new WeakMap(),
20
+ snapshotKey: /* @__PURE__ */ new Map(),
21
+ preStyleMap: /* @__PURE__ */ new Map(),
22
+ preStyle: /* @__PURE__ */ new WeakMap(),
23
+ preNodeMap: /* @__PURE__ */ new Map(),
24
+ reset: resetCache
25
+ };
26
+ function resetCache() {
27
+ cache.computedStyle = /* @__PURE__ */ new WeakMap();
28
+ cache.snapshot = /* @__PURE__ */ new WeakMap();
29
+ cache.snapshotKey.clear();
30
+ cache.preStyleMap.clear();
31
+ cache.preStyle = /* @__PURE__ */ new WeakMap();
32
+ cache.preNodeMap.clear();
33
+ }
18
34
 
19
35
  // src/utils/cssTools.js
20
36
  var commonTags = [
@@ -62,13 +78,13 @@ function precacheCommonTags() {
62
78
  }
63
79
  }
64
80
  function getDefaultStyleForTag(tagName) {
65
- if (defaultStylesCache.has(tagName)) {
66
- return defaultStylesCache.get(tagName);
81
+ if (cache.defaultStyle.has(tagName)) {
82
+ return cache.defaultStyle.get(tagName);
67
83
  }
68
84
  const skipTags = /* @__PURE__ */ new Set(["script", "style", "meta", "link", "noscript", "template"]);
69
85
  if (skipTags.has(tagName)) {
70
86
  const empty = {};
71
- defaultStylesCache.set(tagName, empty);
87
+ cache.defaultStyle.set(tagName, empty);
72
88
  return empty;
73
89
  }
74
90
  let sandbox = document.getElementById("snapdom-sandbox");
@@ -92,7 +108,7 @@ function getDefaultStyleForTag(tagName) {
92
108
  defaults[prop] = styles.getPropertyValue(prop);
93
109
  }
94
110
  sandbox.removeChild(el);
95
- defaultStylesCache.set(tagName, defaults);
111
+ cache.defaultStyle.set(tagName, defaults);
96
112
  return defaults;
97
113
  }
98
114
  function getStyleKey(snapshot, tagName, compress = false) {
@@ -128,7 +144,7 @@ function collectUsedTagNames(root) {
128
144
  function generateDedupedBaseCSS(usedTagNames) {
129
145
  const groups = /* @__PURE__ */ new Map();
130
146
  for (let tagName of usedTagNames) {
131
- const styles = defaultStylesCache.get(tagName);
147
+ const styles = cache.defaultStyle.get(tagName);
132
148
  if (!styles) continue;
133
149
  const key = Object.entries(styles).map(([k, v]) => `${k}:${v};`).sort().join("");
134
150
  if (!groups.has(key)) {
@@ -143,8 +159,8 @@ function generateDedupedBaseCSS(usedTagNames) {
143
159
  }
144
160
  return css;
145
161
  }
146
- function generateCSSClasses(styleMap) {
147
- const keySet = new Set(styleMap.values());
162
+ function generateCSSClasses() {
163
+ const keySet = new Set(cache.preStyleMap.values());
148
164
  const classMap = /* @__PURE__ */ new Map();
149
165
  let counter = 1;
150
166
  for (const key of keySet) {
@@ -155,17 +171,15 @@ function generateCSSClasses(styleMap) {
155
171
 
156
172
  // src/utils/helpers.js
157
173
  async function inlineSingleBackgroundEntry(entry, options = {}) {
158
- const isUrl = entry.startsWith("url(");
174
+ const rawUrl = extractURL(entry);
159
175
  const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
160
- if (isUrl) {
161
- const rawUrl = extractURL(entry);
162
- if (!rawUrl) return entry;
176
+ if (rawUrl) {
163
177
  const encodedUrl = safeEncodeURI(rawUrl);
164
- if (bgCache.has(encodedUrl)) {
165
- return options.skipInline ? void 0 : `url(${bgCache.get(encodedUrl)})`;
178
+ if (cache.background.has(encodedUrl)) {
179
+ return options.skipInline ? void 0 : `url(${cache.background.get(encodedUrl)})`;
166
180
  } else {
167
181
  const dataUrl = await fetchImage(encodedUrl, { useProxy: options.useProxy });
168
- bgCache.set(encodedUrl, dataUrl);
182
+ cache.background.set(encodedUrl, dataUrl);
169
183
  return options.skipInline ? void 0 : `url("${dataUrl}")`;
170
184
  }
171
185
  }
@@ -186,10 +200,10 @@ function getStyle(el, pseudo = null) {
186
200
  if (!(el instanceof Element)) {
187
201
  return window.getComputedStyle(el, pseudo);
188
202
  }
189
- let map = computedStyleCache.get(el);
203
+ let map = cache.computedStyle.get(el);
190
204
  if (!map) {
191
205
  map = /* @__PURE__ */ new Map();
192
- computedStyleCache.set(el, map);
206
+ cache.computedStyle.set(el, map);
193
207
  }
194
208
  if (!map.has(pseudo)) {
195
209
  const st = window.getComputedStyle(el, pseudo);
@@ -263,13 +277,13 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
263
277
  }
264
278
  const crossOriginValue = getCrossOriginMode(src);
265
279
  console.log(`[SnapDOM - fetchImage] Start loading image: ${src} with crossOrigin=${crossOriginValue}`);
266
- if (imageCache.has(src)) {
280
+ if (cache.image.has(src)) {
267
281
  console.log(`[SnapDOM - fetchImage] Cache hit for: ${src}`);
268
- return Promise.resolve(imageCache.get(src));
282
+ return Promise.resolve(cache.image.get(src));
269
283
  }
270
284
  const isDataURI = src.startsWith("data:image/");
271
285
  if (isDataURI) {
272
- imageCache.set(src, src);
286
+ cache.image.set(src, src);
273
287
  return Promise.resolve(src);
274
288
  }
275
289
  const isSVG = /\.svg(\?.*)?$/i.test(src);
@@ -282,7 +296,7 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
282
296
  });
283
297
  const svgText = await response.text();
284
298
  const encoded = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`;
285
- imageCache.set(src, encoded);
299
+ cache.image.set(src, encoded);
286
300
  return encoded;
287
301
  } catch {
288
302
  return fetchWithFallback(src);
@@ -306,12 +320,12 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
306
320
  const ctx = canvas.getContext("2d");
307
321
  ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
308
322
  const dataURL = canvas.toDataURL("image/png");
309
- imageCache.set(src, dataURL);
323
+ cache.image.set(src, dataURL);
310
324
  resolve(dataURL);
311
325
  } catch {
312
326
  try {
313
327
  const fallbackDataURL = await fetchWithFallback(src);
314
- imageCache.set(src, fallbackDataURL);
328
+ cache.image.set(src, fallbackDataURL);
315
329
  resolve(fallbackDataURL);
316
330
  } catch (e) {
317
331
  reject(e);
@@ -323,7 +337,7 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
323
337
  console.error(`[SnapDOM - fetchImage] Image failed to load: ${src}`);
324
338
  try {
325
339
  const fallbackDataURL = await fetchWithFallback(src);
326
- imageCache.set(src, fallbackDataURL);
340
+ cache.image.set(src, fallbackDataURL);
327
341
  resolve(fallbackDataURL);
328
342
  } catch (e) {
329
343
  reject(e);
@@ -387,8 +401,6 @@ function splitBackgroundImage(bg) {
387
401
  }
388
402
 
389
403
  // src/modules/styles.js
390
- var snapshotCache = /* @__PURE__ */ new WeakMap();
391
- var snapshotKeyCache = /* @__PURE__ */ new Map();
392
404
  function snapshotComputedStyleFull(style) {
393
405
  const result = {};
394
406
  for (let i = 0; i < style.length; i++) {
@@ -401,35 +413,34 @@ function snapshotComputedStyleFull(style) {
401
413
  }
402
414
  return result;
403
415
  }
404
- function inlineAllStyles(source, clone, styleMap, cache, compress) {
416
+ function inlineAllStyles(source, clone, compress) {
405
417
  if (source.tagName === "STYLE") return;
406
- if (!cache.has(source)) {
407
- cache.set(source, getStyle(source));
418
+ if (!cache.preStyle.has(source)) {
419
+ cache.preStyle.set(source, getStyle(source));
408
420
  }
409
- const style = cache.get(source);
410
- if (!snapshotCache.has(source)) {
421
+ const style = cache.preStyle.get(source);
422
+ if (!cache.snapshot.has(source)) {
411
423
  const snapshot2 = snapshotComputedStyleFull(style);
412
- snapshotCache.set(source, snapshot2);
424
+ cache.snapshot.set(source, snapshot2);
413
425
  }
414
- const snapshot = snapshotCache.get(source);
426
+ const snapshot = cache.snapshot.get(source);
415
427
  const hash = Object.entries(snapshot).sort(([a], [b]) => a.localeCompare(b)).map(([prop, val]) => `${prop}:${val}`).join(";");
416
- if (snapshotKeyCache.has(hash)) {
417
- styleMap.set(clone, snapshotKeyCache.get(hash));
428
+ if (cache.snapshotKey.has(hash)) {
429
+ cache.preStyleMap.set(clone, cache.snapshotKey.get(hash));
418
430
  return;
419
431
  }
420
432
  const tagName = source.tagName?.toLowerCase() || "div";
421
433
  const key = getStyleKey(snapshot, tagName, compress);
422
- snapshotKeyCache.set(hash, key);
423
- styleMap.set(clone, key);
434
+ cache.snapshotKey.set(hash, key);
435
+ cache.preStyleMap.set(clone, key);
424
436
  }
425
437
 
426
438
  // src/core/clone.js
427
- function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {}, originalRoot) {
439
+ function isSlotElement(node) {
440
+ return node.nodeType === Node.ELEMENT_NODE && node.tagName === "SLOT";
441
+ }
442
+ function deepClone(node, compress, options = {}, originalRoot) {
428
443
  if (node.nodeType === Node.TEXT_NODE) {
429
- if (node.parentElement?.shadowRoot) {
430
- const tag = node.parentElement.tagName.toLowerCase();
431
- if (customElements.get(tag)) return null;
432
- }
433
444
  return node.cloneNode(true);
434
445
  }
435
446
  if (node.nodeType !== Node.ELEMENT_NODE) return node.cloneNode(true);
@@ -439,10 +450,10 @@ function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {},
439
450
  spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
440
451
  return spacer;
441
452
  }
442
- if (options.exclude && Array.isArray(options.exclude) && options.exclude.length > 0) {
453
+ if (options.exclude && Array.isArray(options.exclude)) {
443
454
  for (const selector of options.exclude) {
444
455
  try {
445
- if (node.matches && node.matches(selector)) {
456
+ if (node.matches?.(selector)) {
446
457
  const spacer = document.createElement("div");
447
458
  const rect = node.getBoundingClientRect();
448
459
  spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
@@ -473,8 +484,8 @@ function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {},
473
484
  }
474
485
  if (node.getAttribute("data-capture") === "placeholder") {
475
486
  const clone2 = node.cloneNode(false);
476
- nodeMap.set(clone2, node);
477
- inlineAllStyles(node, clone2, styleMap, styleCache, compress);
487
+ cache.preNodeMap.set(clone2, node);
488
+ inlineAllStyles(node, clone2, compress);
478
489
  const placeholder = document.createElement("div");
479
490
  placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
480
491
  placeholder.style.cssText = `color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;`;
@@ -493,7 +504,7 @@ function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {},
493
504
  return img;
494
505
  }
495
506
  const clone = node.cloneNode(false);
496
- nodeMap.set(clone, node);
507
+ cache.preNodeMap.set(clone, node);
497
508
  if (node instanceof HTMLInputElement) {
498
509
  clone.value = node.value;
499
510
  clone.setAttribute("value", node.value);
@@ -502,8 +513,10 @@ function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {},
502
513
  if (node.checked) clone.setAttribute("checked", "");
503
514
  }
504
515
  } else if (node instanceof HTMLTextAreaElement) {
505
- clone.value = node.value;
516
+ const rect = node.getBoundingClientRect();
506
517
  clone.textContent = node.value;
518
+ clone.style.width = `${rect.width}px`;
519
+ clone.style.height = `${rect.height}px`;
507
520
  } else if (node instanceof HTMLSelectElement) {
508
521
  clone.value = node.value;
509
522
  Array.from(clone.options).forEach((opt) => {
@@ -514,19 +527,31 @@ function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {},
514
527
  }
515
528
  });
516
529
  }
517
- inlineAllStyles(node, clone, styleMap, styleCache, compress);
518
- const frag = document.createDocumentFragment();
519
- const rootElement = originalRoot || node;
520
- node.childNodes.forEach((child) => {
521
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, rootElement);
522
- if (clonedChild) frag.appendChild(clonedChild);
523
- });
524
- clone.appendChild(frag);
525
- if (node.shadowRoot) {
526
- const shadowContent = Array.from(node.shadowRoot.children).filter((el) => el.tagName !== "STYLE").map((el) => deepClone(el, styleMap, styleCache, nodeMap, compress, options, rootElement)).filter(Boolean);
527
- const shadowFrag = document.createDocumentFragment();
528
- shadowContent.forEach((child) => shadowFrag.appendChild(child));
529
- clone.appendChild(shadowFrag);
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);
546
+ }
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);
552
+ }
553
+ clone.appendChild(lightDomContent);
554
+ }
530
555
  }
531
556
  return clone;
532
557
  }
@@ -608,14 +633,14 @@ function injectLinkIfMissing(href) {
608
633
  });
609
634
  }
610
635
  async function embedCustomFonts({ preCached = false } = {}) {
611
- if (resourceCache.has("fonts-embed-css")) {
636
+ if (cache.resource.has("fonts-embed-css")) {
612
637
  if (preCached) {
613
638
  const style = document.createElement("style");
614
639
  style.setAttribute("data-snapdom", "embedFonts");
615
- style.textContent = resourceCache.get("fonts-embed-css");
640
+ style.textContent = cache.resource.get("fonts-embed-css");
616
641
  document.head.appendChild(style);
617
642
  }
618
- return resourceCache.get("fonts-embed-css");
643
+ return cache.resource.get("fonts-embed-css");
619
644
  }
620
645
  const importRegex = /@import\s+url\(["']?([^"')]+)["']?\)/g;
621
646
  const styleImports = [];
@@ -648,11 +673,11 @@ async function embedCustomFonts({ preCached = false } = {}) {
648
673
  url = new URL(url, link.href).href;
649
674
  }
650
675
  if (isIconFont(url)) return null;
651
- if (resourceCache.has(url)) {
652
- processedFontURLs.add(url);
653
- return { original: match[0], inlined: `url(${resourceCache.get(url)})` };
676
+ if (cache.resource.has(url)) {
677
+ cache.font.add(url);
678
+ return { original: match[0], inlined: `url(${cache.resource.get(url)})` };
654
679
  }
655
- if (processedFontURLs.has(url)) return null;
680
+ if (cache.font.has(url)) return null;
656
681
  try {
657
682
  const fontRes = await fetch(url);
658
683
  const blob = await fontRes.blob();
@@ -661,8 +686,8 @@ async function embedCustomFonts({ preCached = false } = {}) {
661
686
  reader.onload = () => resolve(reader.result);
662
687
  reader.readAsDataURL(blob);
663
688
  });
664
- resourceCache.set(url, b64);
665
- processedFontURLs.add(url);
689
+ cache.resource.set(url, b64);
690
+ cache.font.add(url);
666
691
  return { original: match[0], inlined: `url(${b64})` };
667
692
  } catch (e) {
668
693
  console.warn("[snapdom] Failed to fetch font resource:", url);
@@ -698,12 +723,12 @@ async function embedCustomFonts({ preCached = false } = {}) {
698
723
  url = new URL(url, sheet.href || location.href).href;
699
724
  }
700
725
  if (isIconFont(url)) continue;
701
- if (resourceCache.has(url)) {
702
- processedFontURLs.add(url);
703
- inlinedSrc = inlinedSrc.replace(match[0], `url(${resourceCache.get(url)})`);
726
+ if (cache.resource.has(url)) {
727
+ cache.font.add(url);
728
+ inlinedSrc = inlinedSrc.replace(match[0], `url(${cache.resource.get(url)})`);
704
729
  continue;
705
730
  }
706
- if (processedFontURLs.has(url)) continue;
731
+ if (cache.font.has(url)) continue;
707
732
  try {
708
733
  const res = await fetch(url);
709
734
  const blob = await res.blob();
@@ -712,8 +737,8 @@ async function embedCustomFonts({ preCached = false } = {}) {
712
737
  reader.onload = () => resolve(reader.result);
713
738
  reader.readAsDataURL(blob);
714
739
  });
715
- resourceCache.set(url, b64);
716
- processedFontURLs.add(url);
740
+ cache.resource.set(url, b64);
741
+ cache.font.add(url);
717
742
  inlinedSrc = inlinedSrc.replace(match[0], `url(${b64})`);
718
743
  } catch (e) {
719
744
  console.warn("[snapdom] Failed to fetch font URL:", url);
@@ -738,10 +763,10 @@ async function embedCustomFonts({ preCached = false } = {}) {
738
763
  if (isIconFont(font.family)) continue;
739
764
  let b64 = font._snapdomSrc;
740
765
  if (!b64.startsWith("data:")) {
741
- if (resourceCache.has(font._snapdomSrc)) {
742
- b64 = resourceCache.get(font._snapdomSrc);
743
- processedFontURLs.add(font._snapdomSrc);
744
- } else if (!processedFontURLs.has(font._snapdomSrc)) {
766
+ if (cache.resource.has(font._snapdomSrc)) {
767
+ b64 = cache.resource.get(font._snapdomSrc);
768
+ cache.font.add(font._snapdomSrc);
769
+ } else if (!cache.font.has(font._snapdomSrc)) {
745
770
  try {
746
771
  const res = await fetch(font._snapdomSrc);
747
772
  const blob = await res.blob();
@@ -750,8 +775,8 @@ async function embedCustomFonts({ preCached = false } = {}) {
750
775
  reader.onload = () => resolve(reader.result);
751
776
  reader.readAsDataURL(blob);
752
777
  });
753
- resourceCache.set(font._snapdomSrc, b64);
754
- processedFontURLs.add(font._snapdomSrc);
778
+ cache.resource.set(font._snapdomSrc, b64);
779
+ cache.font.add(font._snapdomSrc);
755
780
  } catch (e) {
756
781
  console.warn("[snapdom] Failed to fetch dynamic font src:", font._snapdomSrc);
757
782
  continue;
@@ -768,7 +793,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
768
793
  }
769
794
  }
770
795
  if (finalCSS) {
771
- resourceCache.set("fonts-embed-css", finalCSS);
796
+ cache.resource.set("fonts-embed-css", finalCSS);
772
797
  if (preCached) {
773
798
  const style = document.createElement("style");
774
799
  style.setAttribute("data-snapdom", "embedFonts");
@@ -780,7 +805,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
780
805
  }
781
806
 
782
807
  // src/modules/pseudo.js
783
- async function inlinePseudoElements(source, clone, styleMap, styleCache, compress, embedFonts = false, useProxy) {
808
+ async function inlinePseudoElements(source, clone, compress, embedFonts = false, useProxy) {
784
809
  if (!(source instanceof Element) || !(clone instanceof Element)) return;
785
810
  for (const pseudo of ["::before", "::after", "::first-letter"]) {
786
811
  try {
@@ -804,7 +829,7 @@ async function inlinePseudoElements(source, clone, styleMap, styleCache, compres
804
829
  span.dataset.snapdomPseudo = "::first-letter";
805
830
  const snapshot = snapshotComputedStyle(style);
806
831
  const key = getStyleKey(snapshot, "span", compress);
807
- styleMap.set(span, key);
832
+ cache.preStyleMap.set(span, key);
808
833
  const restNode = document.createTextNode(rest);
809
834
  clone.replaceChild(restNode, textNode);
810
835
  clone.insertBefore(span, restNode);
@@ -825,7 +850,7 @@ async function inlinePseudoElements(source, clone, styleMap, styleCache, compres
825
850
  pseudoEl.dataset.snapdomPseudo = pseudo;
826
851
  const snapshot = snapshotComputedStyle(style);
827
852
  const key = getStyleKey(snapshot, "span", compress);
828
- styleMap.set(pseudoEl, key);
853
+ cache.preStyleMap.set(pseudoEl, key);
829
854
  const isIconFont2 = isIconFont(fontFamily);
830
855
  const cleanContent = parseContent(content);
831
856
  if (isIconFont2 && cleanContent.length === 1) {
@@ -876,8 +901,6 @@ async function inlinePseudoElements(source, clone, styleMap, styleCache, compres
876
901
  await inlinePseudoElements(
877
902
  sChildren[i],
878
903
  cChildren[i],
879
- styleMap,
880
- styleCache,
881
904
  compress,
882
905
  embedFonts,
883
906
  useProxy
@@ -917,18 +940,15 @@ function inlineExternalDef(root) {
917
940
 
918
941
  // src/core/prepare.js
919
942
  async function prepareClone(element, compress = false, embedFonts = false, options = {}) {
920
- const styleMap = /* @__PURE__ */ new Map();
921
- const styleCache = /* @__PURE__ */ new WeakMap();
922
- const nodeMap = /* @__PURE__ */ new Map();
923
- let clone;
943
+ let clone, classCSS;
924
944
  try {
925
- clone = deepClone(element, styleMap, styleCache, nodeMap, compress, options, element);
945
+ clone = deepClone(element, compress, options, element);
926
946
  } catch (e) {
927
947
  console.warn("deepClone failed:", e);
928
948
  throw e;
929
949
  }
930
950
  try {
931
- await inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts, options.useProxy);
951
+ await inlinePseudoElements(element, clone, compress, embedFonts, options.useProxy);
932
952
  } catch (e) {
933
953
  console.warn("inlinePseudoElements failed:", e);
934
954
  }
@@ -937,11 +957,10 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
937
957
  } catch (e) {
938
958
  console.warn("inlineExternalDef failed:", e);
939
959
  }
940
- let classCSS = "";
941
960
  if (compress) {
942
- const keyToClass = generateCSSClasses(styleMap);
961
+ const keyToClass = generateCSSClasses();
943
962
  classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
944
- for (const [node, key] of styleMap.entries()) {
963
+ for (const [node, key] of cache.preStyleMap.entries()) {
945
964
  if (node.tagName === "STYLE") continue;
946
965
  const className = keyToClass.get(key);
947
966
  if (className) node.classList.add(className);
@@ -950,12 +969,12 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
950
969
  if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
951
970
  }
952
971
  } else {
953
- for (const [node, key] of styleMap.entries()) {
972
+ for (const [node, key] of cache.preStyleMap.entries()) {
954
973
  if (node.tagName === "STYLE") continue;
955
974
  node.setAttribute("style", key.replace(/;/g, "; "));
956
975
  }
957
976
  }
958
- for (const [cloneNode, originalNode] of nodeMap.entries()) {
977
+ for (const [cloneNode, originalNode] of cache.preNodeMap.entries()) {
959
978
  const scrollX = originalNode.scrollLeft;
960
979
  const scrollY = originalNode.scrollTop;
961
980
  const hasScroll = scrollX || scrollY;
@@ -974,9 +993,9 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
974
993
  cloneNode.appendChild(inner);
975
994
  }
976
995
  }
977
- if (element === nodeMap.get(clone)) {
978
- const computed = styleCache.get(element) || window.getComputedStyle(element);
979
- styleCache.set(element, computed);
996
+ if (element === cache.preNodeMap.get(clone)) {
997
+ const computed = cache.preStyle.get(element) || window.getComputedStyle(element);
998
+ cache.preStyle.set(element, computed);
980
999
  const transform = stripTranslate(computed.transform);
981
1000
  clone.style.margin = "0";
982
1001
  clone.style.position = "static";
@@ -989,13 +1008,13 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
989
1008
  clone.style.clear = "none";
990
1009
  clone.style.transform = transform || "";
991
1010
  }
992
- for (const [cloneNode, originalNode] of nodeMap.entries()) {
1011
+ for (const [cloneNode, originalNode] of cache.preNodeMap.entries()) {
993
1012
  if (originalNode.tagName === "PRE") {
994
1013
  cloneNode.style.marginTop = "0";
995
1014
  cloneNode.style.marginBlockStart = "0";
996
1015
  }
997
1016
  }
998
- return { clone, classCSS, styleCache };
1017
+ return { clone, classCSS };
999
1018
  }
1000
1019
 
1001
1020
  // src/modules/images.js
@@ -1022,29 +1041,34 @@ async function inlineImages(clone, options = {}) {
1022
1041
  }
1023
1042
 
1024
1043
  // src/modules/background.js
1025
- async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
1044
+ async function inlineBackgroundImages(source, clone, options = {}) {
1026
1045
  const queue = [[source, clone]];
1046
+ const imageProps = [
1047
+ "background-image",
1048
+ "mask",
1049
+ "mask-image",
1050
+ "-webkit-mask-image",
1051
+ "mask-source",
1052
+ "mask-box-image-source",
1053
+ "mask-border-source",
1054
+ "-webkit-mask-box-image-source"
1055
+ ];
1027
1056
  while (queue.length) {
1028
1057
  const [srcNode, cloneNode] = queue.shift();
1029
- const style = styleCache.get(srcNode) || getStyle(srcNode);
1030
- if (!styleCache.has(srcNode)) styleCache.set(srcNode, style);
1031
- const bg = style.getPropertyValue("background-image");
1032
- const bgColor = style.getPropertyValue("background-color");
1033
- if (!bg || bg === "none") {
1034
- const sChildren2 = Array.from(srcNode.children);
1035
- const cChildren2 = Array.from(cloneNode.children);
1036
- for (let i = 0; i < Math.min(sChildren2.length, cChildren2.length); i++) {
1037
- queue.push([sChildren2[i], cChildren2[i]]);
1058
+ const style = cache.preStyle.get(srcNode) || getStyle(srcNode);
1059
+ if (!cache.preStyle.has(srcNode)) cache.preStyle.set(srcNode, style);
1060
+ for (const prop of imageProps) {
1061
+ const val = style.getPropertyValue(prop);
1062
+ if (!val || val === "none") continue;
1063
+ const splits = splitBackgroundImage(val);
1064
+ const inlined = await Promise.all(
1065
+ splits.map((entry) => inlineSingleBackgroundEntry(entry, options))
1066
+ );
1067
+ if (inlined.some((p) => p && p !== "none" && !/^url\(undefined/.test(p))) {
1068
+ cloneNode.style.setProperty(prop, inlined.join(", "));
1038
1069
  }
1039
- continue;
1040
- }
1041
- const bgSplits = splitBackgroundImage(bg);
1042
- const newBgParts = await Promise.all(
1043
- bgSplits.map((entry) => inlineSingleBackgroundEntry(entry, options))
1044
- );
1045
- if (newBgParts.some((p) => p && p !== "none" && !/^url\(undefined\)/.test(p))) {
1046
- cloneNode.style.backgroundImage = newBgParts.join(", ");
1047
1070
  }
1071
+ const bgColor = style.getPropertyValue("background-color");
1048
1072
  if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") {
1049
1073
  cloneNode.style.backgroundColor = bgColor;
1050
1074
  }
@@ -1059,13 +1083,14 @@ async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
1059
1083
  // src/core/capture.js
1060
1084
  async function captureDOM(element, options = {}) {
1061
1085
  if (!element) throw new Error("Element cannot be null or undefined");
1086
+ cache.reset();
1062
1087
  const { compress = true, embedFonts = false, fast = true, scale = 1, useProxy = "" } = options;
1063
- let clone, classCSS, styleCache;
1088
+ let clone, classCSS;
1064
1089
  let fontsCSS = "";
1065
1090
  let baseCSS = "";
1066
1091
  let dataURL;
1067
1092
  let svgString;
1068
- ({ clone, classCSS, styleCache } = await prepareClone(element, compress, embedFonts, options));
1093
+ ({ clone, classCSS } = await prepareClone(element, compress, embedFonts, options));
1069
1094
  await new Promise((resolve) => {
1070
1095
  idle(async () => {
1071
1096
  await inlineImages(clone, options);
@@ -1074,7 +1099,7 @@ async function captureDOM(element, options = {}) {
1074
1099
  });
1075
1100
  await new Promise((resolve) => {
1076
1101
  idle(async () => {
1077
- await inlineBackgroundImages(element, clone, styleCache, options);
1102
+ await inlineBackgroundImages(element, clone, options);
1078
1103
  resolve();
1079
1104
  }, { fast });
1080
1105
  });
@@ -1089,13 +1114,13 @@ async function captureDOM(element, options = {}) {
1089
1114
  if (compress) {
1090
1115
  const usedTags = collectUsedTagNames(clone).sort();
1091
1116
  const tagKey = usedTags.join(",");
1092
- if (baseCSSCache.has(tagKey)) {
1093
- baseCSS = baseCSSCache.get(tagKey);
1117
+ if (cache.baseStyle.has(tagKey)) {
1118
+ baseCSS = cache.baseStyle.get(tagKey);
1094
1119
  } else {
1095
1120
  await new Promise((resolve) => {
1096
1121
  idle(() => {
1097
1122
  baseCSS = generateDedupedBaseCSS(usedTags);
1098
- baseCSSCache.set(tagKey, baseCSS);
1123
+ cache.baseStyle.set(tagKey, baseCSS);
1099
1124
  resolve();
1100
1125
  }, { fast });
1101
1126
  });
@@ -1304,10 +1329,7 @@ snapdom.download = async (el, options = {}) => {
1304
1329
  async function preCache(root = document, options = {}) {
1305
1330
  const { embedFonts = true, reset = false } = options;
1306
1331
  if (reset) {
1307
- imageCache.clear();
1308
- bgCache.clear();
1309
- resourceCache.clear();
1310
- baseCSSCache.clear();
1332
+ cache.reset();
1311
1333
  return;
1312
1334
  }
1313
1335
  await document.fonts.ready;
@@ -1320,9 +1342,9 @@ async function preCache(root = document, options = {}) {
1320
1342
  const promises = [];
1321
1343
  for (const img of imgEls) {
1322
1344
  const src = img.src;
1323
- if (!imageCache.has(src)) {
1345
+ if (!cache.image.has(src)) {
1324
1346
  promises.push(
1325
- fetchImage(src, { useProxy: options.useProxy }).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
1347
+ fetchImage(src, { useProxy: options.useProxy }).then((dataURL) => cache.image.set(src, dataURL)).catch(() => {
1326
1348
  })
1327
1349
  );
1328
1350
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zumer/snapdom",
3
- "version": "1.9.3",
3
+ "version": "1.9.6-dev.0",
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",