@zumer/snapdom 1.9.13 → 2.0.0-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.js CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.9.12-dev.4
4
+ * v.2.0.0-dev.0
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -13,6 +13,7 @@
13
13
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
14
14
  var __hasOwnProp = Object.prototype.hasOwnProperty;
15
15
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
16
+ var __pow = Math.pow;
16
17
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
17
18
  var __spreadValues = (a, b) => {
18
19
  for (var prop in b || (b = {}))
@@ -63,6 +64,7 @@
63
64
  }
64
65
  };
65
66
  function applyCachePolicy(policy = "soft") {
67
+ cache.session.__counterEpoch = (cache.session.__counterEpoch || 0) + 1;
66
68
  switch (policy) {
67
69
  case "auto": {
68
70
  cache.session.styleMap = /* @__PURE__ */ new Map();
@@ -859,6 +861,27 @@
859
861
  }
860
862
 
861
863
  // src/modules/CSSVar.js
864
+ var KEY_PROPS = ["fill", "stroke", "color", "background-color", "stop-color"];
865
+ var __BASELINE_CACHE = /* @__PURE__ */ new Map();
866
+ function getBaselineComputed(tagName, ns) {
867
+ const key = ns + "::" + tagName.toLowerCase();
868
+ let entry = __BASELINE_CACHE.get(key);
869
+ if (entry) return entry;
870
+ const doc = document;
871
+ const el = ns === "http://www.w3.org/2000/svg" ? doc.createElementNS(ns, tagName) : doc.createElement(tagName);
872
+ const holder = doc.createElement("div");
873
+ holder.style.cssText = "position:absolute;left:-99999px;top:-99999px;contain:strict;display:block;";
874
+ holder.appendChild(el);
875
+ doc.documentElement.appendChild(holder);
876
+ const cs = getComputedStyle(el);
877
+ const base = {};
878
+ for (const p of KEY_PROPS) {
879
+ base[p] = cs.getPropertyValue(p) || "";
880
+ }
881
+ holder.remove();
882
+ __BASELINE_CACHE.set(key, base);
883
+ return base;
884
+ }
862
885
  function resolveCSSVars(sourceEl, cloneEl) {
863
886
  var _a, _b, _c;
864
887
  if (!(sourceEl instanceof Element) || !(cloneEl instanceof Element)) return;
@@ -874,42 +897,62 @@
874
897
  }
875
898
  }
876
899
  }
877
- if (!hasVar) return;
878
- let cs;
879
- try {
880
- cs = getComputedStyle(sourceEl);
881
- } catch (e) {
882
- return;
900
+ let cs = null;
901
+ if (hasVar) {
902
+ try {
903
+ cs = getComputedStyle(sourceEl);
904
+ } catch (e) {
905
+ }
883
906
  }
884
- const author = sourceEl.style;
885
- if (author && author.length) {
886
- for (let i = 0; i < author.length; i++) {
887
- const prop = author[i];
888
- const val = author.getPropertyValue(prop);
889
- if (!val || !val.includes("var(")) continue;
890
- const resolved = cs.getPropertyValue(prop);
891
- if (resolved) {
892
- try {
893
- cloneEl.style.setProperty(prop, resolved.trim(), author.getPropertyPriority(prop));
894
- } catch (e) {
907
+ if (hasVar) {
908
+ const author = sourceEl.style;
909
+ if (author && author.length) {
910
+ for (let i = 0; i < author.length; i++) {
911
+ const prop = author[i];
912
+ const val = author.getPropertyValue(prop);
913
+ if (!val || !val.includes("var(")) continue;
914
+ const resolved = cs && cs.getPropertyValue(prop);
915
+ if (resolved) {
916
+ try {
917
+ cloneEl.style.setProperty(prop, resolved.trim(), author.getPropertyPriority(prop));
918
+ } catch (e) {
919
+ }
895
920
  }
896
921
  }
897
922
  }
898
923
  }
899
- if ((_c = sourceEl.attributes) == null ? void 0 : _c.length) {
924
+ if (hasVar && ((_c = sourceEl.attributes) == null ? void 0 : _c.length)) {
900
925
  const attrs = sourceEl.attributes;
901
926
  for (let i = 0; i < attrs.length; i++) {
902
927
  const a = attrs[i];
903
928
  if (!a || typeof a.value !== "string" || !a.value.includes("var(")) continue;
904
929
  const propName = a.name;
905
- let resolved = "";
930
+ const resolved = cs && cs.getPropertyValue(propName);
931
+ if (resolved) {
932
+ try {
933
+ cloneEl.style.setProperty(propName, resolved.trim());
934
+ } catch (e) {
935
+ }
936
+ }
937
+ }
938
+ }
939
+ if (!hasVar) {
940
+ if (!cs) {
906
941
  try {
907
- resolved = cs.getPropertyValue(propName);
942
+ cs = getComputedStyle(sourceEl);
908
943
  } catch (e) {
944
+ cs = null;
909
945
  }
910
- if (resolved) {
946
+ }
947
+ if (!cs) return;
948
+ const ns = sourceEl.namespaceURI || "html";
949
+ const base = getBaselineComputed(sourceEl.tagName, ns);
950
+ for (const prop of KEY_PROPS) {
951
+ const v = cs.getPropertyValue(prop) || "";
952
+ const b = base[prop] || "";
953
+ if (v && v !== b) {
911
954
  try {
912
- cloneEl.style.setProperty(propName, resolved.trim());
955
+ cloneEl.style.setProperty(prop, v.trim());
913
956
  } catch (e) {
914
957
  }
915
958
  }
@@ -1239,11 +1282,50 @@
1239
1282
  return clone2;
1240
1283
  }
1241
1284
  if (node.tagName === "CANVAS") {
1242
- const dataURL = node.toDataURL();
1285
+ let url = "";
1286
+ try {
1287
+ const ctx = node.getContext("2d", { willReadFrequently: true });
1288
+ try {
1289
+ ctx && ctx.getImageData(0, 0, 1, 1);
1290
+ } catch (e) {
1291
+ }
1292
+ yield new Promise((r) => requestAnimationFrame(r));
1293
+ url = node.toDataURL("image/png");
1294
+ if (!url || url === "data:,") {
1295
+ try {
1296
+ ctx && ctx.getImageData(0, 0, 1, 1);
1297
+ } catch (e) {
1298
+ }
1299
+ yield new Promise((r) => requestAnimationFrame(r));
1300
+ url = node.toDataURL("image/png");
1301
+ if (!url || url === "data:,") {
1302
+ const scratch = document.createElement("canvas");
1303
+ scratch.width = node.width;
1304
+ scratch.height = node.height;
1305
+ const sctx = scratch.getContext("2d");
1306
+ if (sctx) {
1307
+ sctx.drawImage(node, 0, 0);
1308
+ url = scratch.toDataURL("image/png");
1309
+ }
1310
+ }
1311
+ }
1312
+ } catch (e) {
1313
+ }
1243
1314
  const img = document.createElement("img");
1244
- img.src = dataURL;
1315
+ try {
1316
+ img.decoding = "sync";
1317
+ img.loading = "eager";
1318
+ } catch (e) {
1319
+ }
1320
+ if (url) img.src = url;
1245
1321
  img.width = node.width;
1246
1322
  img.height = node.height;
1323
+ try {
1324
+ const cs = getComputedStyle(node);
1325
+ if (cs.width) img.style.width = cs.width;
1326
+ if (cs.height) img.style.height = cs.height;
1327
+ } catch (e) {
1328
+ }
1247
1329
  sessionCache.nodeMap.set(img, node);
1248
1330
  inlineAllStyles(node, img, sessionCache, options);
1249
1331
  return img;
@@ -1614,7 +1696,7 @@
1614
1696
  const rawUrl = (m[2] || m[4] || "").trim();
1615
1697
  const absUrl = normalizeUrl(rawUrl, baseHref);
1616
1698
  if (visited.has(absUrl)) {
1617
- console.warn("[snapDOM] Skipping circular import");
1699
+ console.warn(`[snapDOM] Skipping circular @import: ${absUrl}`);
1618
1700
  continue;
1619
1701
  }
1620
1702
  visited.add(absUrl);
@@ -2254,6 +2336,11 @@ ${imported}
2254
2336
  }
2255
2337
  }
2256
2338
  function buildCounterContext(root) {
2339
+ const getEpoch = () => {
2340
+ var _a, _b, _c;
2341
+ return (_c = (_b = (_a = cache) == null ? void 0 : _a.session) == null ? void 0 : _b.__counterEpoch) != null ? _c : 0;
2342
+ };
2343
+ let run = getEpoch();
2257
2344
  const nodeCounters = /* @__PURE__ */ new WeakMap();
2258
2345
  const rootEl = root instanceof Document ? root.documentElement : root;
2259
2346
  const isLi = (el) => el && el.tagName === "LI";
@@ -2347,6 +2434,14 @@ ${imported}
2347
2434
  };
2348
2435
  const empty = /* @__PURE__ */ new Map();
2349
2436
  build(rootEl, empty, empty);
2437
+ function ensureFresh() {
2438
+ const now = getEpoch();
2439
+ if (now !== run) {
2440
+ run = now;
2441
+ const empty2 = /* @__PURE__ */ new Map();
2442
+ build(rootEl, empty2, empty2);
2443
+ }
2444
+ }
2350
2445
  return {
2351
2446
  /**
2352
2447
  * Get top value for counter name at given node.
@@ -2355,6 +2450,7 @@ ${imported}
2355
2450
  */
2356
2451
  get(node, name) {
2357
2452
  var _a;
2453
+ ensureFresh();
2358
2454
  const s = (_a = nodeCounters.get(node)) == null ? void 0 : _a.get(name);
2359
2455
  return s && s.length ? s[s.length - 1] : 0;
2360
2456
  },
@@ -2365,6 +2461,7 @@ ${imported}
2365
2461
  */
2366
2462
  getStack(node, name) {
2367
2463
  var _a;
2464
+ ensureFresh();
2368
2465
  const s = (_a = nodeCounters.get(node)) == null ? void 0 : _a.get(name);
2369
2466
  return s ? s.slice() : [];
2370
2467
  }
@@ -2399,8 +2496,8 @@ ${imported}
2399
2496
  }
2400
2497
 
2401
2498
  // src/modules/pseudo.js
2402
- var counterCtx = null;
2403
2499
  var __siblingCounters = /* @__PURE__ */ new WeakMap();
2500
+ var __pseudoEpoch = -1;
2404
2501
  function unquoteDoubleStrings2(s) {
2405
2502
  return (s || "").replace(/"([^"]*)"/g, "$1");
2406
2503
  }
@@ -2497,14 +2594,21 @@ ${imported}
2497
2594
  }
2498
2595
  function inlinePseudoElements(source, clone, sessionCache, options) {
2499
2596
  return __async(this, null, function* () {
2500
- var _a;
2597
+ var _a, _b, _c, _d;
2501
2598
  if (!(source instanceof Element) || !(clone instanceof Element)) return;
2502
- if (!counterCtx) {
2599
+ const epoch = (_c = (_b = (_a = cache) == null ? void 0 : _a.session) == null ? void 0 : _b.__counterEpoch) != null ? _c : 0;
2600
+ if (__pseudoEpoch !== epoch) {
2601
+ __siblingCounters = /* @__PURE__ */ new WeakMap();
2602
+ if (sessionCache) sessionCache.__counterCtx = null;
2603
+ __pseudoEpoch = epoch;
2604
+ }
2605
+ if (!sessionCache.__counterCtx) {
2503
2606
  try {
2504
- counterCtx = buildCounterContext(source.ownerDocument || document);
2607
+ sessionCache.__counterCtx = buildCounterContext(source.ownerDocument || document);
2505
2608
  } catch (e) {
2506
2609
  }
2507
2610
  }
2611
+ const counterCtx = sessionCache.__counterCtx;
2508
2612
  for (const pseudo of ["::before", "::after", "::first-letter"]) {
2509
2613
  try {
2510
2614
  const style = getStyle(source, pseudo);
@@ -2572,7 +2676,8 @@ ${imported}
2572
2676
  }
2573
2677
  const pseudoEl = document.createElement("span");
2574
2678
  pseudoEl.dataset.snapdomPseudo = pseudo;
2575
- pseudoEl.style.verticalAlign = "middle";
2679
+ pseudoEl.style.display = "inline";
2680
+ pseudoEl.style.verticalAlign = "baseline";
2576
2681
  pseudoEl.style.pointerEvents = "none";
2577
2682
  const snapshot = snapshotComputedStyle(style);
2578
2683
  const key = getStyleKey(snapshot, "span");
@@ -2600,9 +2705,23 @@ ${imported}
2600
2705
  } else if (!isIconFont22 && hasExplicitContent) {
2601
2706
  pseudoEl.textContent = cleanContent;
2602
2707
  }
2603
- pseudoEl.style.background = "none";
2604
- if ("mask" in pseudoEl.style) {
2605
- pseudoEl.style.mask = "none";
2708
+ pseudoEl.style.backgroundImage = "none";
2709
+ if ("maskImage" in pseudoEl.style) pseudoEl.style.maskImage = "none";
2710
+ if ("webkitMaskImage" in pseudoEl.style) pseudoEl.style.webkitMaskImage = "none";
2711
+ try {
2712
+ pseudoEl.style.backgroundRepeat = style.backgroundRepeat;
2713
+ pseudoEl.style.backgroundSize = style.backgroundSize;
2714
+ if (style.backgroundPositionX && style.backgroundPositionY) {
2715
+ pseudoEl.style.backgroundPositionX = style.backgroundPositionX;
2716
+ pseudoEl.style.backgroundPositionY = style.backgroundPositionY;
2717
+ } else {
2718
+ pseudoEl.style.backgroundPosition = style.backgroundPosition;
2719
+ }
2720
+ pseudoEl.style.backgroundOrigin = style.backgroundOrigin;
2721
+ pseudoEl.style.backgroundClip = style.backgroundClip;
2722
+ pseudoEl.style.backgroundAttachment = style.backgroundAttachment;
2723
+ pseudoEl.style.backgroundBlendMode = style.backgroundBlendMode;
2724
+ } catch (e) {
2606
2725
  }
2607
2726
  if (hasBg) {
2608
2727
  try {
@@ -2614,7 +2733,7 @@ ${imported}
2614
2733
  }
2615
2734
  }
2616
2735
  if (hasBgColor) pseudoEl.style.backgroundColor = bgColor;
2617
- const hasContent2 = pseudoEl.childNodes.length > 0 || ((_a = pseudoEl.textContent) == null ? void 0 : _a.trim()) !== "";
2736
+ const hasContent2 = pseudoEl.childNodes.length > 0 || ((_d = pseudoEl.textContent) == null ? void 0 : _d.trim()) !== "";
2618
2737
  const hasVisibleBox = hasContent2 || hasBg || hasBgColor || hasBorder || hasTransform;
2619
2738
  if (incs && incs.length && source.parentElement) {
2620
2739
  const map = __siblingCounters.get(source.parentElement) || /* @__PURE__ */ new Map();
@@ -2646,53 +2765,114 @@ ${imported}
2646
2765
  }
2647
2766
 
2648
2767
  // src/modules/svgDefs.js
2649
- function inlineExternalDefsAndSymbols(rootElement) {
2650
- if (!rootElement) return;
2651
- const usedIds = /* @__PURE__ */ new Set();
2652
- rootElement.querySelectorAll("use").forEach((use) => {
2653
- const href = use.getAttribute("xlink:href") || use.getAttribute("href");
2654
- if (href && href.startsWith("#")) {
2655
- usedIds.add(href.slice(1));
2768
+ function inlineExternalDefsAndSymbols(element, lookupRoot) {
2769
+ var _a, _b, _c, _d;
2770
+ if (!element || !(element instanceof Element)) return;
2771
+ const doc = element.ownerDocument || document;
2772
+ const searchRoot = lookupRoot || doc;
2773
+ const svgRoots = element instanceof SVGSVGElement ? [element] : Array.from(element.querySelectorAll("svg"));
2774
+ if (svgRoots.length === 0) return;
2775
+ const URL_ID_RE = /url\(\s*#([^)]+)\)/g;
2776
+ const URL_ATTRS = [
2777
+ "fill",
2778
+ "stroke",
2779
+ "filter",
2780
+ "clip-path",
2781
+ "mask",
2782
+ "marker",
2783
+ "marker-start",
2784
+ "marker-mid",
2785
+ "marker-end"
2786
+ ];
2787
+ const cssEscape = (s) => window.CSS && CSS.escape ? CSS.escape(s) : s.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
2788
+ const globalExistingIds = new Set(
2789
+ Array.from(element.querySelectorAll("[id]")).map((n) => n.id)
2790
+ );
2791
+ const neededIds = /* @__PURE__ */ new Set();
2792
+ let sawAnyReference = false;
2793
+ const addUrlIdsFromValue = (val) => {
2794
+ if (!val) return;
2795
+ URL_ID_RE.lastIndex = 0;
2796
+ let m;
2797
+ while (m = URL_ID_RE.exec(val)) {
2798
+ sawAnyReference = true;
2799
+ const id = (m[1] || "").trim();
2800
+ if (id && !globalExistingIds.has(id)) neededIds.add(id);
2656
2801
  }
2657
- });
2658
- if (!usedIds.size) return;
2659
- const allGlobal = Array.from(document.querySelectorAll("svg > symbol, svg > defs"));
2660
- const globalSymbols = allGlobal.filter((el) => el.tagName.toLowerCase() === "symbol");
2661
- const globalDefs = allGlobal.filter((el) => el.tagName.toLowerCase() === "defs");
2662
- let container = rootElement.querySelector("svg.inline-defs-container");
2663
- if (!container) {
2664
- container = document.createElementNS("http://www.w3.org/2000/svg", "svg");
2665
- container.setAttribute("aria-hidden", "true");
2666
- container.setAttribute("style", "position: absolute; width: 0; height: 0; overflow: hidden;");
2667
- container.classList.add("inline-defs-container");
2668
- rootElement.insertBefore(container, rootElement.firstChild);
2669
- }
2670
- const existingIds = /* @__PURE__ */ new Set();
2671
- rootElement.querySelectorAll("symbol[id], defs > *[id]").forEach((el) => {
2672
- existingIds.add(el.id);
2673
- });
2674
- usedIds.forEach((id) => {
2675
- if (existingIds.has(id)) return;
2676
- const symbol = globalSymbols.find((sym) => sym.id === id);
2677
- if (symbol) {
2678
- container.appendChild(symbol.cloneNode(true));
2679
- existingIds.add(id);
2680
- return;
2802
+ };
2803
+ const collectReferencesInSvg = (rootSvg) => {
2804
+ const uses = rootSvg.querySelectorAll('use[href^="#"], use[xlink\\:href^="#"]');
2805
+ for (const u of uses) {
2806
+ const href = u.getAttribute("href") || u.getAttribute("xlink:href");
2807
+ if (!href || !href.startsWith("#")) continue;
2808
+ sawAnyReference = true;
2809
+ const id = href.slice(1).trim();
2810
+ if (id && !globalExistingIds.has(id)) neededIds.add(id);
2811
+ }
2812
+ let query = '*[style*="url("],*[fill^="url("], *[stroke^="url("],*[filter^="url("], *[clip-path^="url("],*[mask^="url("],*[marker^="url("], *[marker-start^="url("],*[marker-mid^="url("], *[marker-end^="url("]';
2813
+ const candidates = rootSvg.querySelectorAll(query);
2814
+ for (const el of candidates) {
2815
+ addUrlIdsFromValue(el.getAttribute("style") || "");
2816
+ for (const a of URL_ATTRS) addUrlIdsFromValue(el.getAttribute(a));
2681
2817
  }
2682
- for (const defs of globalDefs) {
2683
- const defEl = defs.querySelector(`#${CSS.escape(id)}`);
2684
- if (defEl) {
2685
- let defsContainer = container.querySelector("defs");
2686
- if (!defsContainer) {
2687
- defsContainer = document.createElementNS("http://www.w3.org/2000/svg", "defs");
2688
- container.appendChild(defsContainer);
2689
- }
2690
- defsContainer.appendChild(defEl.cloneNode(true));
2691
- existingIds.add(id);
2692
- break;
2818
+ };
2819
+ for (const svg of svgRoots) collectReferencesInSvg(svg);
2820
+ if (!sawAnyReference) return;
2821
+ let defsHost = element.querySelector("svg.inline-defs-container");
2822
+ if (!defsHost) {
2823
+ defsHost = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
2824
+ defsHost.classList.add("inline-defs-container");
2825
+ defsHost.setAttribute("aria-hidden", "true");
2826
+ defsHost.setAttribute("style", "position:absolute;width:0;height:0;overflow:hidden");
2827
+ element.insertBefore(defsHost, element.firstChild || null);
2828
+ }
2829
+ let localDefs = defsHost.querySelector("defs") || null;
2830
+ const findGlobalById = (id) => {
2831
+ if (!id) return null;
2832
+ if (globalExistingIds.has(id)) return null;
2833
+ const esc = cssEscape(id);
2834
+ const tryFind = (sel) => {
2835
+ const el = searchRoot.querySelector(sel);
2836
+ return el && !element.contains(el) ? el : null;
2837
+ };
2838
+ return tryFind(`svg defs > *#${esc}`) || tryFind(`svg > symbol#${esc}`) || tryFind(`*#${esc}`);
2839
+ };
2840
+ if (!neededIds.size) return;
2841
+ const queued = new Set(neededIds);
2842
+ const inlined = /* @__PURE__ */ new Set();
2843
+ while (queued.size) {
2844
+ const id = queued.values().next().value;
2845
+ queued.delete(id);
2846
+ if (!id || globalExistingIds.has(id) || inlined.has(id)) continue;
2847
+ const source = findGlobalById(id);
2848
+ if (!source) {
2849
+ inlined.add(id);
2850
+ continue;
2851
+ }
2852
+ if (!localDefs) {
2853
+ localDefs = doc.createElementNS("http://www.w3.org/2000/svg", "defs");
2854
+ defsHost.appendChild(localDefs);
2855
+ }
2856
+ const clone = source.cloneNode(true);
2857
+ if (!clone.id) clone.setAttribute("id", id);
2858
+ localDefs.appendChild(clone);
2859
+ inlined.add(id);
2860
+ globalExistingIds.add(id);
2861
+ const walk = [clone, ...clone.querySelectorAll("*")];
2862
+ for (const node of walk) {
2863
+ const h = ((_a = node.getAttribute) == null ? void 0 : _a.call(node, "href")) || ((_b = node.getAttribute) == null ? void 0 : _b.call(node, "xlink:href"));
2864
+ if (h && h.startsWith("#")) {
2865
+ const ref = h.slice(1).trim();
2866
+ if (ref && !globalExistingIds.has(ref) && !inlined.has(ref)) queued.add(ref);
2867
+ }
2868
+ const style = ((_c = node.getAttribute) == null ? void 0 : _c.call(node, "style")) || "";
2869
+ if (style) addUrlIdsFromValue(style);
2870
+ for (const a of URL_ATTRS) {
2871
+ const v = (_d = node.getAttribute) == null ? void 0 : _d.call(node, a);
2872
+ if (v) addUrlIdsFromValue(v);
2693
2873
  }
2694
2874
  }
2695
- });
2875
+ }
2696
2876
  }
2697
2877
 
2698
2878
  // src/modules/changeCSS.js
@@ -3155,6 +3335,17 @@ ${imported}
3155
3335
  "-webkit-mask-position-x",
3156
3336
  "-webkit-mask-position-y"
3157
3337
  ];
3338
+ const BG_LAYOUT_PROPS = [
3339
+ "background-position",
3340
+ "background-position-x",
3341
+ "background-position-y",
3342
+ "background-size",
3343
+ "background-repeat",
3344
+ "background-origin",
3345
+ "background-clip",
3346
+ "background-attachment",
3347
+ "background-blend-mode"
3348
+ ];
3158
3349
  const BORDER_AUX_PROPS = [
3159
3350
  "border-image-slice",
3160
3351
  "border-image-width",
@@ -3170,6 +3361,11 @@ ${imported}
3170
3361
  const bis = style.getPropertyValue("border-image-source");
3171
3362
  return bi && bi !== "none" || bis && bis !== "none";
3172
3363
  })();
3364
+ for (const prop of BG_LAYOUT_PROPS) {
3365
+ const v = style.getPropertyValue(prop);
3366
+ if (!v) continue;
3367
+ cloneNode.style.setProperty(prop, v);
3368
+ }
3173
3369
  for (const prop of URL_PROPS) {
3174
3370
  const val = style.getPropertyValue(prop);
3175
3371
  if (!val || val === "none") continue;
@@ -3194,7 +3390,7 @@ ${imported}
3194
3390
  }
3195
3391
  }
3196
3392
  const sChildren = Array.from(srcNode.children);
3197
- const cChildren = Array.from(cloneNode.children);
3393
+ const cChildren = Array.from(cloneNode.children).filter((el) => !(el.dataset && el.dataset.snapdomPseudo));
3198
3394
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
3199
3395
  queue.push([sChildren[i], cChildren[i]]);
3200
3396
  }
@@ -3260,6 +3456,85 @@ ${imported}
3260
3456
  return Array.from(el.childNodes).some((n) => n.nodeType === Node.TEXT_NODE);
3261
3457
  }
3262
3458
 
3459
+ // src/core/plugins.js
3460
+ var __plugins = [];
3461
+ function normalizePlugin(spec) {
3462
+ if (!spec) return null;
3463
+ if (Array.isArray(spec)) {
3464
+ const [factory, options] = spec;
3465
+ return typeof factory === "function" ? factory(options) : factory;
3466
+ }
3467
+ if (typeof spec === "object" && "plugin" in spec) {
3468
+ const { plugin, options } = spec;
3469
+ return typeof plugin === "function" ? plugin(options) : plugin;
3470
+ }
3471
+ if (typeof spec === "function") return spec();
3472
+ return spec;
3473
+ }
3474
+ function registerPlugins(...defs) {
3475
+ const flat = defs.flat();
3476
+ for (const d of flat) {
3477
+ const inst = normalizePlugin(d);
3478
+ if (!inst) continue;
3479
+ if (!__plugins.some((p) => p && p.name && inst.name && p.name === inst.name)) {
3480
+ __plugins.push(inst);
3481
+ }
3482
+ }
3483
+ }
3484
+ function getContextPlugins(context) {
3485
+ const arr = context && Array.isArray(context.plugins) ? context.plugins : __plugins;
3486
+ return arr || __plugins;
3487
+ }
3488
+ function runHook(name, context, payload) {
3489
+ return __async(this, null, function* () {
3490
+ let acc = payload;
3491
+ const list = getContextPlugins(context);
3492
+ for (const p of list) {
3493
+ const fn = p && typeof p[name] === "function" ? p[name] : null;
3494
+ if (!fn) continue;
3495
+ const out = yield fn(context, acc);
3496
+ if (typeof out !== "undefined") acc = out;
3497
+ }
3498
+ return acc;
3499
+ });
3500
+ }
3501
+ function runAll(name, context, payload) {
3502
+ return __async(this, null, function* () {
3503
+ const outs = [];
3504
+ const list = getContextPlugins(context);
3505
+ for (const p of list) {
3506
+ const fn = p && typeof p[name] === "function" ? p[name] : null;
3507
+ if (!fn) continue;
3508
+ const out = yield fn(context, payload);
3509
+ if (typeof out !== "undefined") outs.push(out);
3510
+ }
3511
+ return outs;
3512
+ });
3513
+ }
3514
+ function mergePlugins(localDefs) {
3515
+ const out = [];
3516
+ if (Array.isArray(localDefs)) {
3517
+ for (const d of localDefs) {
3518
+ const inst = normalizePlugin(d);
3519
+ if (!inst || !inst.name) continue;
3520
+ const i = out.findIndex((x) => x && x.name === inst.name);
3521
+ if (i >= 0) out.splice(i, 1);
3522
+ out.push(inst);
3523
+ }
3524
+ }
3525
+ for (const g of __plugins) {
3526
+ if (g && g.name && !out.some((x) => x.name === g.name)) {
3527
+ out.push(g);
3528
+ }
3529
+ }
3530
+ return Object.freeze(out);
3531
+ }
3532
+ function attachSessionPlugins(context, localDefs, force = false) {
3533
+ if (!context || context.plugins && !force) return context;
3534
+ context.plugins = mergePlugins(localDefs);
3535
+ return context;
3536
+ }
3537
+
3263
3538
  // src/core/capture.js
3264
3539
  function stripRootShadows(originalEl, cloneRoot) {
3265
3540
  if (!originalEl || !cloneRoot || !cloneRoot.style) return;
@@ -3290,41 +3565,46 @@ ${imported}
3290
3565
  const fast = options.fast;
3291
3566
  const straighten = !!options.straighten;
3292
3567
  const noShadows = !!options.noShadows;
3568
+ let state = { element, options, plugins: options.plugins };
3293
3569
  let clone, classCSS, styleCache;
3294
3570
  let fontsCSS = "";
3295
3571
  let baseCSS = "";
3296
3572
  let dataURL;
3297
3573
  let svgString;
3298
3574
  let rootTransform2D = null;
3299
- const undoClamp = lineClamp(element);
3575
+ yield runHook("beforeSnap", state);
3576
+ yield runHook("beforeClone", state);
3577
+ const undoClamp = lineClamp(state.element);
3300
3578
  try {
3301
- ({ clone, classCSS, styleCache } = yield prepareClone(element, options));
3579
+ ({ clone, classCSS, styleCache } = yield prepareClone(state.element, state.options));
3302
3580
  if (straighten && clone) {
3303
- rootTransform2D = normalizeRootTransforms(element, clone);
3581
+ rootTransform2D = normalizeRootTransforms(state.element, state.clone);
3304
3582
  }
3305
3583
  if (noShadows && clone) {
3306
- stripRootShadows(element, clone);
3584
+ stripRootShadows(state.element, state.clone);
3307
3585
  }
3308
3586
  } finally {
3309
3587
  undoClamp();
3310
3588
  }
3589
+ state = __spreadValues({ clone, classCSS, styleCache }, state);
3590
+ yield runHook("afterClone", state);
3311
3591
  yield new Promise((resolve) => {
3312
3592
  idle(() => __async(null, null, function* () {
3313
- yield inlineImages(clone, options);
3593
+ yield inlineImages(state.clone, state.options);
3314
3594
  resolve();
3315
3595
  }), { fast });
3316
3596
  });
3317
3597
  yield new Promise((resolve) => {
3318
3598
  idle(() => __async(null, null, function* () {
3319
- yield inlineBackgroundImages(element, clone, styleCache, options);
3599
+ yield inlineBackgroundImages(state.element, state.clone, state.styleCache, state.options);
3320
3600
  resolve();
3321
3601
  }), { fast });
3322
3602
  });
3323
3603
  if (options.embedFonts) {
3324
3604
  yield new Promise((resolve) => {
3325
3605
  idle(() => __async(null, null, function* () {
3326
- const required = collectUsedFontVariants(element);
3327
- const usedCodepoints = collectUsedCodepoints(element);
3606
+ const required = collectUsedFontVariants(state.element);
3607
+ const usedCodepoints = collectUsedCodepoints(state.element);
3328
3608
  if (isSafari()) {
3329
3609
  const families = new Set(
3330
3610
  Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
@@ -3335,14 +3615,14 @@ ${imported}
3335
3615
  required,
3336
3616
  usedCodepoints,
3337
3617
  preCached: false,
3338
- exclude: options.excludeFonts,
3339
- useProxy: options.useProxy
3618
+ exclude: state.options.excludeFonts,
3619
+ useProxy: state.options.useProxy
3340
3620
  });
3341
3621
  resolve();
3342
3622
  }), { fast });
3343
3623
  });
3344
3624
  }
3345
- const usedTags = collectUsedTagNames(clone).sort();
3625
+ const usedTags = collectUsedTagNames(state.clone).sort();
3346
3626
  const tagKey = usedTags.join(",");
3347
3627
  if (cache.baseStyle.has(tagKey)) {
3348
3628
  baseCSS = cache.baseStyle.get(tagKey);
@@ -3355,70 +3635,57 @@ ${imported}
3355
3635
  }, { fast });
3356
3636
  });
3357
3637
  }
3638
+ state = __spreadValues({ fontsCSS, baseCSS }, state);
3639
+ yield runHook("beforeRender", state);
3358
3640
  yield new Promise((resolve) => {
3359
3641
  idle(() => {
3360
- const csEl = getComputedStyle(element);
3361
- function parseFilterDropShadows(cs) {
3362
- var _a;
3363
- const raw = `${cs.filter || ""} ${cs.webkitFilter || ""}`.trim();
3364
- if (!raw || raw === "none") {
3365
- return { bleed: { top: 0, right: 0, bottom: 0, left: 0 }, has: false };
3366
- }
3367
- const tokens = raw.match(/drop-shadow\((?:[^()]|\([^()]*\))*\)/gi) || [];
3368
- let t = 0, r = 0, b = 0, l = 0;
3369
- let found = false;
3370
- for (const tok of tokens) {
3371
- found = true;
3372
- const nums = ((_a = tok.match(/-?\d+(?:\.\d+)?px/gi)) == null ? void 0 : _a.map((v) => parseFloat(v))) || [];
3373
- const [ox = 0, oy = 0, blur = 0] = nums;
3374
- const extX = Math.abs(ox) + blur;
3375
- const extY = Math.abs(oy) + blur;
3376
- r = Math.max(r, extX + Math.max(ox, 0));
3377
- l = Math.max(l, extX + Math.max(-ox, 0));
3378
- b = Math.max(b, extY + Math.max(oy, 0));
3379
- t = Math.max(t, extY + Math.max(-oy, 0));
3380
- }
3381
- return { bleed: { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b), left: Math.ceil(l) }, has: found };
3382
- }
3383
- const rect = element.getBoundingClientRect();
3384
- const w0 = Math.max(1, Math.ceil(element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
3385
- const h0 = Math.max(1, Math.ceil(element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
3642
+ const csEl = getComputedStyle(state.element);
3643
+ const rect = state.element.getBoundingClientRect();
3644
+ const w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
3645
+ const h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
3386
3646
  const coerceNum = (v, def = NaN) => {
3387
3647
  const n = typeof v === "string" ? parseFloat(v) : v;
3388
3648
  return Number.isFinite(n) ? n : def;
3389
3649
  };
3390
- const optW = coerceNum(options.width);
3391
- const optH = coerceNum(options.height);
3650
+ const optW = coerceNum(state.options.width);
3651
+ const optH = coerceNum(state.options.height);
3392
3652
  let w = w0, h = h0;
3393
3653
  const hasW = Number.isFinite(optW);
3394
3654
  const hasH = Number.isFinite(optH);
3395
3655
  const aspect0 = h0 > 0 ? w0 / h0 : 1;
3396
3656
  if (hasW && hasH) {
3397
- w = Math.max(1, Math.ceil(optW));
3398
- h = Math.max(1, Math.ceil(optH));
3657
+ w = Math.max(1, limitDecimals(optW));
3658
+ h = Math.max(1, limitDecimals(optH));
3399
3659
  } else if (hasW) {
3400
- w = Math.max(1, Math.ceil(optW));
3401
- h = Math.max(1, Math.ceil(w / (aspect0 || 1)));
3660
+ w = Math.max(1, limitDecimals(optW));
3661
+ h = Math.max(1, limitDecimals(w / (aspect0 || 1)));
3402
3662
  } else if (hasH) {
3403
- h = Math.max(1, Math.ceil(optH));
3404
- w = Math.max(1, Math.ceil(h * (aspect0 || 1)));
3663
+ h = Math.max(1, limitDecimals(optH));
3664
+ w = Math.max(1, limitDecimals(h * (aspect0 || 1)));
3405
3665
  } else {
3406
3666
  w = w0;
3407
3667
  h = h0;
3408
3668
  }
3409
3669
  let minX = 0, minY = 0, maxX = w0, maxY = h0;
3410
3670
  if (straighten && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
3411
- const M2 = { a: rootTransform2D.a, b: rootTransform2D.b || 0, c: rootTransform2D.c || 0, d: rootTransform2D.d || 1, e: 0, f: 0 };
3671
+ const M2 = {
3672
+ a: rootTransform2D.a,
3673
+ b: rootTransform2D.b || 0,
3674
+ c: rootTransform2D.c || 0,
3675
+ d: rootTransform2D.d || 1,
3676
+ e: 0,
3677
+ f: 0
3678
+ };
3412
3679
  const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
3413
- minX = bb2.minX;
3414
- minY = bb2.minY;
3415
- maxX = bb2.maxX;
3416
- maxY = bb2.maxY;
3680
+ minX = limitDecimals(bb2.minX);
3681
+ minY = limitDecimals(bb2.minY);
3682
+ maxX = limitDecimals(bb2.maxX);
3683
+ maxY = limitDecimals(bb2.maxY);
3417
3684
  } else {
3418
- const useTFBBox = !straighten && hasTFBBox(element);
3685
+ const useTFBBox = !straighten && hasTFBBox(state.element);
3419
3686
  if (useTFBBox) {
3420
3687
  const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
3421
- const ind2 = readIndividualTransforms(element);
3688
+ const ind2 = readIndividualTransforms(state.element);
3422
3689
  const TOTAL = readTotalTransformMatrix({
3423
3690
  baseTransform: baseTransform2,
3424
3691
  rotate: ind2.rotate || "0deg",
@@ -3428,10 +3695,10 @@ ${imported}
3428
3695
  const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
3429
3696
  const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
3430
3697
  const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
3431
- minX = bb.minX;
3432
- minY = bb.minY;
3433
- maxX = bb.maxX;
3434
- maxY = bb.maxY;
3698
+ minX = limitDecimals(bb.minX);
3699
+ minY = limitDecimals(bb.minY);
3700
+ maxX = limitDecimals(bb.maxX);
3701
+ maxY = limitDecimals(bb.maxY);
3435
3702
  }
3436
3703
  }
3437
3704
  const bleedShadow = parseBoxShadow(csEl);
@@ -3439,62 +3706,97 @@ ${imported}
3439
3706
  const bleedOutline = parseOutline(csEl);
3440
3707
  const drop = parseFilterDropShadows(csEl);
3441
3708
  const bleed = noShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
3442
- top: bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top,
3443
- right: bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right,
3444
- bottom: bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom,
3445
- left: bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left
3709
+ top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
3710
+ right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
3711
+ bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
3712
+ left: limitDecimals(bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left)
3446
3713
  };
3447
- minX -= bleed.left;
3448
- minY -= bleed.top;
3449
- maxX += bleed.right;
3450
- maxY += bleed.bottom;
3451
- const vbW0 = Math.max(1, Math.ceil(maxX - minX));
3452
- const vbH0 = Math.max(1, Math.ceil(maxY - minY));
3453
- const outW = Math.max(1, Math.round(vbW0 * (hasW || hasH ? w / w0 : 1)));
3454
- const outH = Math.max(1, Math.round(vbH0 * (hasH || hasW ? h / h0 : 1)));
3714
+ minX = limitDecimals(minX - bleed.left);
3715
+ minY = limitDecimals(minY - bleed.top);
3716
+ maxX = limitDecimals(maxX + bleed.right);
3717
+ maxY = limitDecimals(maxY + bleed.bottom);
3718
+ const vbW0 = Math.max(1, limitDecimals(maxX - minX));
3719
+ const vbH0 = Math.max(1, limitDecimals(maxY - minY));
3720
+ const scaleW = hasW || hasH ? limitDecimals(w / w0) : 1;
3721
+ const scaleH = hasH || hasW ? limitDecimals(h / h0) : 1;
3722
+ const outW = Math.max(1, limitDecimals(vbW0 * scaleW));
3723
+ const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
3455
3724
  const svgNS = "http://www.w3.org/2000/svg";
3456
3725
  const basePad = isSafari() ? 1 : 0;
3457
3726
  const extraPad = straighten ? 1 : 0;
3458
- const pad = basePad + extraPad;
3727
+ const pad = limitDecimals(basePad + extraPad);
3459
3728
  const fo = document.createElementNS(svgNS, "foreignObject");
3460
- const vbMinX = Math.floor(minX);
3461
- const vbMinY = Math.floor(minY);
3462
- fo.setAttribute("x", String(-(vbMinX - pad)));
3463
- fo.setAttribute("y", String(-(vbMinY - pad)));
3464
- fo.setAttribute("width", String(Math.ceil(w0 + pad * 2)));
3465
- fo.setAttribute("height", String(Math.ceil(h0 + pad * 2)));
3729
+ const vbMinX = limitDecimals(minX);
3730
+ const vbMinY = limitDecimals(minY);
3731
+ fo.setAttribute("x", String(limitDecimals(-(vbMinX - pad))));
3732
+ fo.setAttribute("y", String(limitDecimals(-(vbMinY - pad))));
3733
+ fo.setAttribute("width", String(limitDecimals(w0 + pad * 2)));
3734
+ fo.setAttribute("height", String(limitDecimals(h0 + pad * 2)));
3466
3735
  fo.style.overflow = "visible";
3467
3736
  const styleTag = document.createElement("style");
3468
- styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + classCSS;
3737
+ styleTag.textContent = state.baseCSS + state.fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + state.classCSS;
3469
3738
  fo.appendChild(styleTag);
3470
3739
  const container = document.createElement("div");
3471
3740
  container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
3472
- container.style.width = `${w0}px`;
3473
- container.style.height = `${h0}px`;
3741
+ container.style.width = `${limitDecimals(w0)}px`;
3742
+ container.style.height = `${limitDecimals(h0)}px`;
3474
3743
  container.style.overflow = "visible";
3475
- clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
3476
- container.appendChild(clone);
3744
+ state.clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
3745
+ container.appendChild(state.clone);
3477
3746
  fo.appendChild(container);
3478
3747
  const serializer = new XMLSerializer();
3479
3748
  const foString = serializer.serializeToString(fo);
3480
- const vbW = vbW0 + pad * 2;
3481
- const vbH = vbH0 + pad * 2;
3749
+ const vbW = limitDecimals(vbW0 + pad * 2);
3750
+ const vbH = limitDecimals(vbH0 + pad * 2);
3482
3751
  const wantsSize = hasW || hasH;
3483
3752
  options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
3484
- const svgOutW = isSafari() && wantsSize ? vbW : outW + pad * 2;
3485
- const svgOutH = isSafari() && wantsSize ? vbH : outH + pad * 2;
3753
+ const svgOutW = isSafari() && wantsSize ? vbW : limitDecimals(outW + pad * 2);
3754
+ const svgOutH = isSafari() && wantsSize ? vbH : limitDecimals(outH + pad * 2);
3486
3755
  const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
3487
3756
  const svgFooter = "</svg>";
3488
3757
  svgString = svgHeader + foString + svgFooter;
3489
3758
  dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
3759
+ state = __spreadValues({ svgString, dataURL }, state);
3490
3760
  resolve();
3491
3761
  }, { fast });
3492
3762
  });
3763
+ yield runHook("afterRender", state);
3493
3764
  const sandbox = document.getElementById("snapdom-sandbox");
3494
3765
  if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
3495
- return dataURL;
3766
+ return state.dataURL;
3496
3767
  });
3497
3768
  }
3769
+ var limitDecimals = (v, n = 3) => Number.isFinite(v) ? Math.round(v * __pow(10, n)) / __pow(10, n) : v;
3770
+ function parseFilterDropShadows(cs) {
3771
+ var _a;
3772
+ const raw = `${cs.filter || ""} ${cs.webkitFilter || ""}`.trim();
3773
+ if (!raw || raw === "none") {
3774
+ return { bleed: { top: 0, right: 0, bottom: 0, left: 0 }, has: false };
3775
+ }
3776
+ const tokens = raw.match(/drop-shadow\((?:[^()]|\([^()]*\))*\)/gi) || [];
3777
+ let t = 0, r = 0, b = 0, l = 0;
3778
+ let found = false;
3779
+ for (const tok of tokens) {
3780
+ found = true;
3781
+ const nums = ((_a = tok.match(/-?\d+(?:\.\d+)?px/gi)) == null ? void 0 : _a.map((v) => parseFloat(v))) || [];
3782
+ const [ox = 0, oy = 0, blur = 0] = nums;
3783
+ const extX = Math.abs(ox) + blur;
3784
+ const extY = Math.abs(oy) + blur;
3785
+ r = Math.max(r, extX + Math.max(ox, 0));
3786
+ l = Math.max(l, extX + Math.max(-ox, 0));
3787
+ b = Math.max(b, extY + Math.max(oy, 0));
3788
+ t = Math.max(t, extY + Math.max(-oy, 0));
3789
+ }
3790
+ return {
3791
+ bleed: {
3792
+ top: limitDecimals(t),
3793
+ right: limitDecimals(r),
3794
+ bottom: limitDecimals(b),
3795
+ left: limitDecimals(l)
3796
+ },
3797
+ has: found
3798
+ };
3799
+ }
3498
3800
  function normalizeRootTransforms(originalEl, cloneRoot) {
3499
3801
  if (!originalEl || !cloneRoot || !cloneRoot.style) return null;
3500
3802
  const cs = getComputedStyle(originalEl);
@@ -3747,7 +4049,8 @@ ${imported}
3747
4049
  }
3748
4050
  function createContext(options = {}) {
3749
4051
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
3750
- const resolvedFormat = (_a = options.format) != null ? _a : "png";
4052
+ let resolvedFormat = (_a = options.format) != null ? _a : "png";
4053
+ if (resolvedFormat === "jpg") resolvedFormat = "jpeg";
3751
4054
  const cachePolicy = normalizeCachePolicy(options.cache);
3752
4055
  return {
3753
4056
  // Debug & perf
@@ -3779,7 +4082,7 @@ ${imported}
3779
4082
  type: (_n = options.type) != null ? _n : "svg",
3780
4083
  quality: (_o = options.quality) != null ? _o : 0.92,
3781
4084
  dpr: (_p = options.dpr) != null ? _p : window.devicePixelRatio || 1,
3782
- backgroundColor: (_q = options.backgroundColor) != null ? _q : ["jpg", "jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null,
4085
+ backgroundColor: (_q = options.backgroundColor) != null ? _q : ["jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null,
3783
4086
  filename: (_r = options.filename) != null ? _r : "snapDOM",
3784
4087
  // NEW flags (user-friendly)
3785
4088
  straighten: (_s = options.straighten) != null ? _s : false,
@@ -3911,20 +4214,20 @@ ${imported}
3911
4214
  } else if (hasW) {
3912
4215
  const k = optW / Math.max(1, refW);
3913
4216
  outW = optW;
3914
- outH = Math.round(refH * k);
4217
+ outH = refH * k;
3915
4218
  } else if (hasH) {
3916
4219
  const k = optH / Math.max(1, refH);
3917
4220
  outH = optH;
3918
- outW = Math.round(refW * k);
4221
+ outW = refW * k;
3919
4222
  } else {
3920
4223
  outW = natW;
3921
4224
  outH = natH;
3922
4225
  }
3923
- outW = Math.round(outW * scale);
3924
- outH = Math.round(outH * scale);
4226
+ outW = outW * scale;
4227
+ outH = outH * scale;
3925
4228
  const canvas = document.createElement("canvas");
3926
- canvas.width = Math.ceil(outW * dpr);
3927
- canvas.height = Math.ceil(outH * dpr);
4229
+ canvas.width = outW * dpr;
4230
+ canvas.height = outH * dpr;
3928
4231
  canvas.style.width = `${outW}px`;
3929
4232
  canvas.style.height = `${outH}px`;
3930
4233
  const ctx = canvas.getContext("2d");
@@ -4042,17 +4345,23 @@ ${imported}
4042
4345
  }
4043
4346
 
4044
4347
  // src/api/snapdom.js
4348
+ snapdom.plugins = (...defs) => {
4349
+ registerPlugins(...defs);
4350
+ return snapdom;
4351
+ };
4045
4352
  var INTERNAL_TOKEN = Symbol("snapdom.internal");
4353
+ var INTERNAL_EXPORT_TOKEN = Symbol("snapdom.internal.silent");
4046
4354
  var _safariWarmup = false;
4047
4355
  function snapdom(element, userOptions) {
4048
4356
  return __async(this, null, function* () {
4049
4357
  if (!element) throw new Error("Element cannot be null or undefined");
4050
4358
  const context = createContext(userOptions);
4359
+ attachSessionPlugins(context, userOptions && userOptions.plugins);
4051
4360
  if (isSafari() && (context.embedFonts === true || hasBackgroundOrMask(element))) {
4052
4361
  for (let i = 0; i < 3; i++) {
4053
4362
  try {
4054
4363
  yield safariWarmup(element, userOptions);
4055
- console.log("Iteraci\xF3n n\xFAmero:", i);
4364
+ console.log("safariWarmup:", i);
4056
4365
  _safariWarmup = false;
4057
4366
  } catch (e) {
4058
4367
  }
@@ -4068,31 +4377,108 @@ ${imported}
4068
4377
  return snapdom.capture(element, context, INTERNAL_TOKEN);
4069
4378
  });
4070
4379
  }
4380
+ snapdom.plugins = (...defs) => {
4381
+ registerPlugins(...defs);
4382
+ return snapdom;
4383
+ };
4071
4384
  snapdom.capture = (el, context, _token) => __async(null, null, function* () {
4072
4385
  if (_token !== INTERNAL_TOKEN) throw new Error("[snapdom.capture] is internal. Use snapdom(...) instead.");
4073
4386
  const url = yield captureDOM(el, context);
4074
- const ensureContext = (opts) => __spreadValues(__spreadValues({}, context), opts || {});
4075
- const withFormat = (format) => (opts) => {
4076
- const next = ensureContext(__spreadProps(__spreadValues({}, opts || {}), { format }));
4077
- const wantsJpeg = format === "jpeg" || format === "jpg";
4078
- const noBg = next.backgroundColor == null || next.backgroundColor === "transparent";
4079
- if (wantsJpeg && noBg) {
4080
- next.backgroundColor = "#ffffff";
4081
- }
4082
- return rasterize(url, next);
4387
+ const coreExports = {
4388
+ img: (ctx, opts) => __async(null, null, function* () {
4389
+ return toImg(url, __spreadValues(__spreadValues({}, ctx), opts || {}));
4390
+ }),
4391
+ svg: (ctx, opts) => __async(null, null, function* () {
4392
+ return toImg(url, __spreadValues(__spreadValues({}, ctx), opts || {}));
4393
+ }),
4394
+ canvas: (ctx, opts) => __async(null, null, function* () {
4395
+ return toCanvas(url, __spreadValues(__spreadValues({}, ctx), opts || {}));
4396
+ }),
4397
+ blob: (ctx, opts) => __async(null, null, function* () {
4398
+ return toBlob(url, __spreadValues(__spreadValues({}, ctx), opts || {}));
4399
+ }),
4400
+ png: (ctx, opts) => __async(null, null, function* () {
4401
+ return rasterize(url, __spreadProps(__spreadValues(__spreadValues({}, ctx), opts || {}), { format: "png" }));
4402
+ }),
4403
+ jpeg: (ctx, opts) => __async(null, null, function* () {
4404
+ return rasterize(url, __spreadProps(__spreadValues(__spreadValues({}, ctx), opts || {}), { format: "jpeg" }));
4405
+ }),
4406
+ webp: (ctx, opts) => __async(null, null, function* () {
4407
+ return rasterize(url, __spreadProps(__spreadValues(__spreadValues({}, ctx), opts || {}), { format: "webp" }));
4408
+ }),
4409
+ download: (ctx, opts) => __async(null, null, function* () {
4410
+ return download(url, __spreadValues(__spreadValues({}, ctx), opts || {}));
4411
+ })
4083
4412
  };
4084
- return {
4413
+ const _pluginExports = {
4414
+ svg: (opts) => toImg(url, __spreadProps(__spreadValues(__spreadValues({}, context), opts || {}), { [INTERNAL_EXPORT_TOKEN]: true })),
4415
+ canvas: (opts) => toCanvas(url, __spreadProps(__spreadValues(__spreadValues({}, context), opts || {}), { [INTERNAL_EXPORT_TOKEN]: true })),
4416
+ png: (opts) => rasterize(url, __spreadProps(__spreadValues(__spreadValues({}, context), opts || {}), { format: "png", [INTERNAL_EXPORT_TOKEN]: true })),
4417
+ jpeg: (opts) => rasterize(url, __spreadProps(__spreadValues(__spreadValues({}, context), opts || {}), { format: "jpeg", [INTERNAL_EXPORT_TOKEN]: true })),
4418
+ jpg: (opts) => rasterize(url, __spreadProps(__spreadValues(__spreadValues({}, context), opts || {}), { format: "jpeg", [INTERNAL_EXPORT_TOKEN]: true })),
4419
+ webp: (opts) => rasterize(url, __spreadProps(__spreadValues(__spreadValues({}, context), opts || {}), { format: "webp", [INTERNAL_EXPORT_TOKEN]: true })),
4420
+ blob: (opts) => toBlob(url, __spreadProps(__spreadValues(__spreadValues({}, context), opts || {}), { [INTERNAL_EXPORT_TOKEN]: true })),
4421
+ img: (opts) => toImg(url, __spreadProps(__spreadValues(__spreadValues({}, context), opts || {}), { [INTERNAL_EXPORT_TOKEN]: true }))
4422
+ };
4423
+ const _defineCtx = __spreadProps(__spreadValues({}, context), { export: { url }, exports: _pluginExports });
4424
+ const providedMaps = yield runAll("defineExports", _defineCtx);
4425
+ const provided = Object.assign({}, ...providedMaps.filter((x) => x && typeof x === "object"));
4426
+ const exportsMap = __spreadValues(__spreadValues({}, coreExports), provided);
4427
+ if (exportsMap.jpeg && !exportsMap.jpg) {
4428
+ exportsMap.jpg = (ctx, opts) => exportsMap.jpeg(ctx, opts);
4429
+ }
4430
+ function normalizeExportOptions(type, opts) {
4431
+ const next = __spreadValues(__spreadValues({}, context), opts || {});
4432
+ if (type === "jpeg" || type === "jpg") {
4433
+ const noBg = next.backgroundColor == null || next.backgroundColor === "transparent";
4434
+ if (noBg) next.backgroundColor = "#ffffff";
4435
+ }
4436
+ return next;
4437
+ }
4438
+ let afterSnapFired = false;
4439
+ let _exportQueue = Promise.resolve();
4440
+ function runExport(type, opts) {
4441
+ return __async(this, null, function* () {
4442
+ const job = () => __async(null, null, function* () {
4443
+ const work = exportsMap[type];
4444
+ if (!work) throw new Error(`[snapdom] Unknown export type: ${type}`);
4445
+ const nextOpts = normalizeExportOptions(type, opts);
4446
+ const ctx = __spreadProps(__spreadValues({}, context), { export: { type, options: nextOpts, url } });
4447
+ yield runHook("beforeExport", ctx);
4448
+ const result2 = yield work(ctx, nextOpts);
4449
+ yield runHook("afterExport", ctx, result2);
4450
+ if (!afterSnapFired) {
4451
+ afterSnapFired = true;
4452
+ yield runHook("afterSnap", context);
4453
+ }
4454
+ return result2;
4455
+ });
4456
+ return _exportQueue = _exportQueue.then(job);
4457
+ });
4458
+ }
4459
+ const result = {
4085
4460
  url,
4086
4461
  toRaw: () => url,
4087
- toImg: (opts) => toImg(url, ensureContext(opts)),
4088
- toSvg: (opts) => toImg(url, ensureContext(opts)),
4089
- toCanvas: (opts) => toCanvas(url, ensureContext(opts)),
4090
- toBlob: (opts) => toBlob(url, ensureContext(opts)),
4091
- toPng: withFormat("png"),
4092
- toJpg: withFormat("jpeg"),
4093
- toWebp: withFormat("webp"),
4094
- download: (opts) => download(url, ensureContext(opts))
4462
+ to: (type, opts) => runExport(type, opts),
4463
+ // Métodos “clásicos” que los tests esperan:
4464
+ toImg: (opts) => runExport("img", opts),
4465
+ toSvg: (opts) => runExport("svg", opts),
4466
+ toCanvas: (opts) => runExport("canvas", opts),
4467
+ toBlob: (opts) => runExport("blob", opts),
4468
+ toPng: (opts) => runExport("png", opts),
4469
+ toJpg: (opts) => runExport("jpg", opts),
4470
+ // ← alias requerido por los tests
4471
+ toWebp: (opts) => runExport("webp", opts),
4472
+ download: (opts) => runExport("download", opts)
4473
+ // ← método directo (no toDownload)
4095
4474
  };
4475
+ for (const key of Object.keys(exportsMap)) {
4476
+ const helper = "to" + key.charAt(0).toUpperCase() + key.slice(1);
4477
+ if (!result[helper]) {
4478
+ result[helper] = (opts) => runExport(key, opts);
4479
+ }
4480
+ }
4481
+ return result;
4096
4482
  });
4097
4483
  snapdom.toRaw = (el, options) => snapdom(el, options).then((result) => result.toRaw());
4098
4484
  snapdom.toImg = (el, options) => snapdom(el, options).then((result) => result.toImg());
@@ -4115,30 +4501,47 @@ ${imported}
4115
4501
  try {
4116
4502
  url = yield captureDOM(element, preflight);
4117
4503
  } catch (e) {
4118
- return;
4119
4504
  }
4120
- yield new Promise((resolve) => {
4121
- const img = new Image();
4122
- img.decoding = "sync";
4123
- img.loading = "eager";
4124
- img.style.position = "fixed";
4125
- img.style.left = 0;
4126
- img.style.top = 0;
4127
- img.style.width = "10px";
4128
- img.style.height = "10px";
4129
- img.style.opacity = "0.01";
4130
- img.style.transform = "translateZ(10px)";
4131
- img.style.willChange = "transform,opacity;";
4132
- img.src = url;
4133
- const cleanup = () => __async(null, null, function* () {
4134
- yield new Promise((r) => setTimeout(r, 100));
4135
- if (img && img.parentNode) img.parentNode.removeChild(img);
4136
- _safariWarmup = true;
4137
- resolve();
4505
+ yield new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
4506
+ if (url) {
4507
+ yield new Promise((resolve) => {
4508
+ const img = new Image();
4509
+ try {
4510
+ img.decoding = "sync";
4511
+ img.loading = "eager";
4512
+ } catch (e) {
4513
+ }
4514
+ img.style.cssText = "position:fixed;left:0px;top:0px;width:10px;height:10px;opacity:0.01;pointer-events:none;";
4515
+ img.src = url;
4516
+ document.body.appendChild(img);
4517
+ (() => __async(null, null, function* () {
4518
+ try {
4519
+ if (typeof img.decode === "function") yield img.decode();
4520
+ } catch (e) {
4521
+ }
4522
+ const start = performance.now();
4523
+ while (!(img.complete && img.naturalWidth > 0) && performance.now() - start < 900) {
4524
+ yield new Promise((r) => setTimeout(r, 50));
4525
+ }
4526
+ yield new Promise((r) => requestAnimationFrame(r));
4527
+ try {
4528
+ img.remove();
4529
+ } catch (e) {
4530
+ }
4531
+ resolve();
4532
+ }))();
4138
4533
  });
4139
- document.body.appendChild(img);
4140
- cleanup();
4534
+ }
4535
+ element.querySelectorAll("canvas").forEach((c) => {
4536
+ try {
4537
+ const ctx = c.getContext("2d", { willReadFrequently: true });
4538
+ if (ctx) {
4539
+ ctx.getImageData(0, 0, 1, 1);
4540
+ }
4541
+ } catch (e) {
4542
+ }
4141
4543
  });
4544
+ _safariWarmup = true;
4142
4545
  });
4143
4546
  }
4144
4547
  function hasBackgroundOrMask(el) {
@@ -4152,6 +4555,7 @@ ${imported}
4152
4555
  const bg = cs.backgroundImage && cs.backgroundImage !== "none";
4153
4556
  const mask = cs.maskImage && cs.maskImage !== "none" || cs.webkitMaskImage && cs.webkitMaskImage !== "none";
4154
4557
  if (bg || mask) return true;
4558
+ if (node.tagName === "CANVAS") return true;
4155
4559
  }
4156
4560
  return false;
4157
4561
  }
@@ -4159,7 +4563,7 @@ ${imported}
4159
4563
  // src/api/preCache.js
4160
4564
  function preCache() {
4161
4565
  return __async(this, arguments, function* (root = document, options = {}) {
4162
- var _a, _b, _c, _d;
4566
+ var _a, _b, _c, _d, _e, _f;
4163
4567
  const {
4164
4568
  embedFonts = true,
4165
4569
  useProxy = ""
@@ -4179,6 +4583,7 @@ ${imported}
4179
4583
  cache.session.styleCache = /* @__PURE__ */ new WeakMap();
4180
4584
  }
4181
4585
  cache.image = cache.image || /* @__PURE__ */ new Map();
4586
+ cache.background = cache.background || /* @__PURE__ */ new Map();
4182
4587
  try {
4183
4588
  yield inlineBackgroundImages(
4184
4589
  root,
@@ -4191,7 +4596,13 @@ ${imported}
4191
4596
  }
4192
4597
  let imgEls = [], allEls = [];
4193
4598
  try {
4194
- if (root == null ? void 0 : root.querySelectorAll) {
4599
+ if (root && root.nodeType === 1) {
4600
+ const descendants = root.querySelectorAll ? Array.from(root.querySelectorAll("*")) : [];
4601
+ allEls = [root, ...descendants];
4602
+ imgEls = [];
4603
+ if (root.tagName === "IMG" && root.getAttribute("src")) imgEls.push(root);
4604
+ imgEls.push(...Array.from(((_d = root.querySelectorAll) == null ? void 0 : _d.call(root, "img[src]")) || []));
4605
+ } else if (root == null ? void 0 : root.querySelectorAll) {
4195
4606
  imgEls = Array.from(root.querySelectorAll("img[src]"));
4196
4607
  allEls = Array.from(root.querySelectorAll("*"));
4197
4608
  }
@@ -4215,17 +4626,18 @@ ${imported}
4215
4626
  for (const el of allEls) {
4216
4627
  let bg = "";
4217
4628
  try {
4218
- bg = getStyle(el).backgroundImage;
4629
+ bg = ((_e = el == null ? void 0 : el.style) == null ? void 0 : _e.backgroundImage) || "";
4630
+ if (!bg || bg === "none") {
4631
+ bg = getStyle(el).backgroundImage;
4632
+ }
4219
4633
  } catch (e) {
4220
4634
  }
4221
4635
  if (bg && bg !== "none") {
4222
- const parts = splitBackgroundImage(bg);
4223
- for (const entry of parts) {
4224
- if (entry.startsWith("url(")) {
4225
- const p = Promise.resolve().then(() => inlineSingleBackgroundEntry(entry, __spreadProps(__spreadValues({}, options), { useProxy }))).catch(() => {
4226
- });
4227
- promises.push(p);
4228
- }
4636
+ const urlEntries = bg.match(/url\((?:[^()"']+|"(?:[^"]*)"|'(?:[^']*)')\)/gi) || [];
4637
+ for (const entry of urlEntries) {
4638
+ const p = Promise.resolve().then(() => inlineSingleBackgroundEntry(entry, __spreadProps(__spreadValues({}, options), { useProxy }))).catch(() => {
4639
+ });
4640
+ promises.push(p);
4229
4641
  }
4230
4642
  }
4231
4643
  }
@@ -4245,7 +4657,7 @@ ${imported}
4245
4657
  usedCodepoints,
4246
4658
  exclude: options.excludeFonts,
4247
4659
  localFonts: options.localFonts,
4248
- useProxy: (_d = options.useProxy) != null ? _d : useProxy
4660
+ useProxy: (_f = options.useProxy) != null ? _f : useProxy
4249
4661
  });
4250
4662
  } catch (e) {
4251
4663
  }