@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.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.9.6-dev.2
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 urlStart = value.indexOf("url(");
227
- if (urlStart === -1) return null;
228
- let url = value.slice(urlStart + 4).trim();
229
- if (url.endsWith(")")) url = url.slice(0, -1).trim();
230
- if (url.startsWith('"') && url.endsWith('"') || url.startsWith("'") && url.endsWith("'")) {
231
- url = url.slice(1, -1);
232
- }
226
+ const match = value.match(/url\((['"]?)(.*?)(\1)\)/);
227
+ if (!match) return null;
228
+ const url = match[2].trim();
229
+ if (url.startsWith("#")) return null;
233
230
  return url;
234
231
  }
235
232
  function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
@@ -266,19 +263,15 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
266
263
  try {
267
264
  return await fetchBlobAsDataURL(proxied);
268
265
  } catch {
269
- console.error(`[SnapDOM - fetchImage] Proxy fallback failed for: ${url}`);
270
- throw new Error("CORS restrictions prevented image capture (even via proxy)");
266
+ throw new Error("[SnapDOM - fetchImage] CORS restrictions prevented image capture (even via proxy)");
271
267
  }
272
268
  } else {
273
- console.error(`[SnapDOM - fetchImage] No valid proxy URL provided for fallback: ${url}`);
274
- throw new Error("Fetch fallback failed and no proxy provided");
269
+ throw new Error("[SnapDOM - fetchImage] Fetch fallback failed and no proxy provided");
275
270
  }
276
271
  }
277
272
  }
278
273
  const crossOriginValue = getCrossOriginMode(src);
279
- console.log(`[SnapDOM - fetchImage] Start loading image: ${src} with crossOrigin=${crossOriginValue}`);
280
274
  if (cache.image.has(src)) {
281
- console.log(`[SnapDOM - fetchImage] Cache hit for: ${src}`);
282
275
  return Promise.resolve(cache.image.get(src));
283
276
  }
284
277
  const isDataURI = src.startsWith("data:image/");
@@ -305,8 +298,7 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
305
298
  }
306
299
  return new Promise((resolve, reject) => {
307
300
  const timeoutId = setTimeout(() => {
308
- console.log(`[SnapDOM - fetchImage] Timeout after ${timeout}ms for image: ${src}`);
309
- reject(new Error("Image load timed out"));
301
+ reject(new Error("[SnapDOM - fetchImage] Image load timed out"));
310
302
  }, timeout);
311
303
  const image = new Image();
312
304
  image.crossOrigin = crossOriginValue;
@@ -436,158 +428,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
- throw new Error("Invalid node: node is null or undefined");
445
- }
446
- try {
447
- if (node.nodeType === Node.TEXT_NODE) {
448
- return node.cloneNode(true);
449
- }
450
- if (node.nodeType !== Node.ELEMENT_NODE) {
451
- return node.cloneNode(true);
452
- }
453
- if (node.getAttribute("data-capture") === "exclude") {
454
- const spacer = document.createElement("div");
455
- const rect = node.getBoundingClientRect();
456
- spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
457
- return spacer;
458
- }
459
- if (options.exclude && Array.isArray(options.exclude)) {
460
- for (const selector of options.exclude) {
461
- try {
462
- if (node.matches?.(selector)) {
463
- const spacer = document.createElement("div");
464
- const rect = node.getBoundingClientRect();
465
- spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
466
- return spacer;
467
- }
468
- } catch (err) {
469
- console.warn(`Invalid selector in exclude option: ${selector}`, err);
470
- }
471
- }
472
- }
473
- if (typeof options.filter === "function") {
432
+ if (!node) throw new Error("Invalid node");
433
+ const clonedAssignedNodes = /* @__PURE__ */ new Set();
434
+ let pendingSelectValue = null;
435
+ if (node.nodeType === Node.TEXT_NODE) {
436
+ return node.cloneNode(true);
437
+ }
438
+ if (node.nodeType !== Node.ELEMENT_NODE) {
439
+ return node.cloneNode(true);
440
+ }
441
+ if (node.getAttribute("data-capture") === "exclude") {
442
+ const spacer = document.createElement("div");
443
+ const rect = node.getBoundingClientRect();
444
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
445
+ return spacer;
446
+ }
447
+ if (options.exclude && Array.isArray(options.exclude)) {
448
+ for (const selector of options.exclude) {
474
449
  try {
475
- if (!options.filter(node, originalRoot || node)) {
450
+ if (node.matches?.(selector)) {
476
451
  const spacer = document.createElement("div");
477
452
  const rect = node.getBoundingClientRect();
478
- spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
453
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
479
454
  return spacer;
480
455
  }
481
456
  } catch (err) {
482
- console.warn("Error in filter function:", err);
457
+ console.warn(`Invalid selector in exclude option: ${selector}`, err);
483
458
  }
484
459
  }
485
- if (node.tagName === "IFRAME") {
486
- const fallback = document.createElement("div");
487
- fallback.textContent = "";
488
- fallback.style.cssText = `width: ${node.offsetWidth}px; height: ${node.offsetHeight}px; background-image: repeating-linear-gradient(45deg, #ddd, #ddd 5px, #f9f9f9 5px, #f9f9f9 10px);display: flex;align-items: center;justify-content: center;font-size: 12px;color: #555; border: 1px solid #aaa;`;
489
- return fallback;
490
- }
491
- if (node.getAttribute("data-capture") === "placeholder") {
492
- const clone2 = node.cloneNode(false);
493
- cache.preNodeMap.set(clone2, node);
494
- inlineAllStyles(node, clone2, compress);
495
- const placeholder = document.createElement("div");
496
- placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
497
- placeholder.style.cssText = `color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;`;
498
- clone2.appendChild(placeholder);
499
- return clone2;
500
- }
501
- if (node.tagName === "CANVAS") {
502
- const dataURL = node.toDataURL();
503
- const img = document.createElement("img");
504
- img.src = dataURL;
505
- img.width = node.width;
506
- img.height = node.height;
507
- cache.preNodeMap.set(img, node);
508
- inlineAllStyles(node, img, compress);
509
- return img;
510
- }
511
- let clone;
460
+ }
461
+ if (typeof options.filter === "function") {
512
462
  try {
513
- clone = node.cloneNode(false);
514
- if (!clone) {
515
- throw new Error("Failed to clone node: cloneNode returned null");
463
+ if (!options.filter(node, originalRoot || node)) {
464
+ const spacer = document.createElement("div");
465
+ const rect = node.getBoundingClientRect();
466
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
467
+ return spacer;
516
468
  }
517
- cache.preNodeMap.set(clone, node);
518
469
  } catch (err) {
519
- console.error("[Snapdom] Failed to clone node:", node, err);
520
- throw err;
470
+ console.warn("Error in filter function:", err);
521
471
  }
522
- if (node instanceof HTMLInputElement) {
523
- clone.value = node.value;
524
- clone.setAttribute("value", node.value);
525
- if (node.checked !== void 0) {
526
- clone.checked = node.checked;
527
- if (node.checked) clone.setAttribute("checked", "");
528
- }
529
- } else if (node instanceof HTMLTextAreaElement) {
530
- const rect = node.getBoundingClientRect();
531
- clone.textContent = node.value;
532
- clone.style.width = `${rect.width}px`;
533
- clone.style.height = `${rect.height}px`;
534
- } else if (node instanceof HTMLSelectElement) {
535
- clone.value = node.value;
536
- Array.from(clone.options).forEach((opt) => {
537
- if (opt.value === node.value) {
538
- opt.setAttribute("selected", "");
539
- } else {
540
- opt.removeAttribute("selected");
541
- }
542
- });
472
+ }
473
+ if (node.tagName === "IFRAME") {
474
+ const fallback = document.createElement("div");
475
+ fallback.style.cssText = `width:${node.offsetWidth}px;height:${node.offsetHeight}px;background-image:repeating-linear-gradient(45deg,#ddd,#ddd 5px,#f9f9f9 5px,#f9f9f9 10px);display:flex;align-items:center;justify-content:center;font-size:12px;color:#555;border:1px solid #aaa;`;
476
+ return fallback;
477
+ }
478
+ if (node.getAttribute("data-capture") === "placeholder") {
479
+ const clone2 = node.cloneNode(false);
480
+ cache.preNodeMap.set(clone2, node);
481
+ inlineAllStyles(node, clone2, compress);
482
+ const placeholder = document.createElement("div");
483
+ placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
484
+ placeholder.style.cssText = `color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;`;
485
+ clone2.appendChild(placeholder);
486
+ return clone2;
487
+ }
488
+ if (node.tagName === "CANVAS") {
489
+ const dataURL = node.toDataURL();
490
+ const img = document.createElement("img");
491
+ img.src = dataURL;
492
+ img.width = node.width;
493
+ img.height = node.height;
494
+ cache.preNodeMap.set(img, node);
495
+ inlineAllStyles(node, img, compress);
496
+ return img;
497
+ }
498
+ let clone;
499
+ try {
500
+ clone = node.cloneNode(false);
501
+ cache.preNodeMap.set(clone, node);
502
+ } catch (err) {
503
+ console.error("[Snapdom] Failed to clone node:", node, err);
504
+ throw err;
505
+ }
506
+ if (node instanceof HTMLTextAreaElement) {
507
+ clone.textContent = node.value;
508
+ clone.value = node.value;
509
+ const rect = node.getBoundingClientRect();
510
+ clone.style.width = `${rect.width}px`;
511
+ clone.style.height = `${rect.height}px`;
512
+ return clone;
513
+ }
514
+ if (node instanceof HTMLInputElement) {
515
+ clone.value = node.value;
516
+ clone.setAttribute("value", node.value);
517
+ if (node.checked !== void 0) {
518
+ clone.checked = node.checked;
519
+ if (node.checked) clone.setAttribute("checked", "");
520
+ if (node.indeterminate) clone.indeterminate = node.indeterminate;
543
521
  }
544
- inlineAllStyles(node, clone, compress);
545
- try {
546
- if (isSlotElement(node)) {
547
- const assigned = node.assignedNodes?.({ flatten: true }) || [];
548
- const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
549
- const fragment = document.createDocumentFragment();
550
- for (const child of nodesToClone) {
551
- try {
552
- const clonedChild = deepClone(child, compress, options, originalRoot || node);
553
- if (clonedChild) fragment.appendChild(clonedChild);
554
- } catch (err) {
555
- console.warn("[Snapdom] Failed to clone slot content:", child, err);
556
- }
557
- }
558
- return fragment;
559
- } else {
560
- const baseChildren = node.shadowRoot ? node.shadowRoot.childNodes : node.childNodes;
561
- for (const child of baseChildren) {
562
- try {
563
- const clonedChild = deepClone(child, compress, options, originalRoot || node);
564
- if (clonedChild) clone.appendChild(clonedChild);
565
- } catch (err) {
566
- console.warn("[Snapdom] Failed to clone child node:", child, err);
522
+ 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
- if (node.shadowRoot && node.childNodes.length > 0 && !node.shadowRoot.querySelector("slot")) {
570
- const lightDomContent = document.createDocumentFragment();
571
- for (const child of node.childNodes) {
572
- try {
573
- const clonedChild = deepClone(child, compress, options, originalRoot || node);
574
- if (clonedChild) lightDomContent.appendChild(clonedChild);
575
- } catch (err) {
576
- console.warn("[Snapdom] Failed to clone light DOM child:", child, err);
577
- }
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
- clone.appendChild(lightDomContent);
549
+ continue;
580
550
  }
551
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
552
+ if (clonedChild) shadowFrag.appendChild(clonedChild);
581
553
  }
582
- } catch (err) {
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
- const cleanContent = parseContent(content);
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-dev.2",
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"