@zumer/snapdom 1.9.10-dev.0 → 1.9.10-dev.2

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.10-dev.0
4
+ * v.1.9.10-dev.2
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -82,10 +82,13 @@
82
82
  cache.session.styleMap = /* @__PURE__ */ new Map();
83
83
  cache.session.nodeMap = /* @__PURE__ */ new Map();
84
84
  cache.session.styleCache = /* @__PURE__ */ new WeakMap();
85
- cache.image.clear();
86
- cache.background.clear();
87
- cache.resource.clear();
88
- cache.font.clear();
85
+ cache.computedStyle = /* @__PURE__ */ new WeakMap();
86
+ cache.baseStyle = /* @__PURE__ */ new Map();
87
+ cache.defaultStyle = /* @__PURE__ */ new Map();
88
+ cache.image = /* @__PURE__ */ new Map();
89
+ cache.background = /* @__PURE__ */ new Map();
90
+ cache.resource = /* @__PURE__ */ new Map();
91
+ cache.font = /* @__PURE__ */ new Set();
89
92
  return;
90
93
  }
91
94
  default: {
@@ -344,7 +347,6 @@
344
347
  "script",
345
348
  "template",
346
349
  // SVG whole namespace (safe for LeaderLine/presentation attrs)
347
- "svg",
348
350
  "g",
349
351
  "defs",
350
352
  "use",
@@ -688,6 +690,11 @@
688
690
  const ctx = _resolveCtx(sessionOrCtx, opts);
689
691
  const resetMode = ctx.options && ctx.options.cache || "auto";
690
692
  if (resetMode !== "disabled") setupInvalidationOnce(document.documentElement);
693
+ if (resetMode === "disabled" && !ctx.session.__bumpedForDisabled) {
694
+ bumpEpoch();
695
+ snapshotKeyCache.clear();
696
+ ctx.session.__bumpedForDisabled = true;
697
+ }
691
698
  if (NO_DEFAULTS_TAGS.has((_a = source.tagName) == null ? void 0 : _a.toLowerCase())) {
692
699
  const author = (_b = source.getAttribute) == null ? void 0 : _b.call(source, "style");
693
700
  if (author) clone.setAttribute("style", author);
@@ -710,6 +717,31 @@
710
717
  }
711
718
 
712
719
  // src/core/clone.js
720
+ function idleCallback(childList, callback, fast) {
721
+ return Promise.all(childList.map((child) => {
722
+ return new Promise((resolve) => {
723
+ function deal() {
724
+ idle(
725
+ (deadline) => {
726
+ if (deadline instanceof IdleDeadline) {
727
+ if (deadline.timeRemaining()) {
728
+ callback(child, resolve);
729
+ } else {
730
+ deal();
731
+ }
732
+ } else {
733
+ callback(child, resolve);
734
+ }
735
+ },
736
+ {
737
+ fast
738
+ }
739
+ );
740
+ }
741
+ deal();
742
+ });
743
+ }));
744
+ }
713
745
  function addNotSlottedRightmost(sel) {
714
746
  sel = sel.trim();
715
747
  if (!sel) return sel;
@@ -831,177 +863,321 @@
831
863
  root.querySelectorAll("*").forEach((el) => el.setAttribute("data-sd-slotted", ""));
832
864
  }
833
865
  }
866
+ function getAccessibleIframeDocument(iframe, attempts = 3) {
867
+ return __async(this, null, function* () {
868
+ const probe = () => {
869
+ var _a;
870
+ try {
871
+ return iframe.contentDocument || ((_a = iframe.contentWindow) == null ? void 0 : _a.document) || null;
872
+ } catch (e) {
873
+ return null;
874
+ }
875
+ };
876
+ let doc = probe();
877
+ let i = 0;
878
+ while (i < attempts && (!doc || !doc.body && !doc.documentElement)) {
879
+ yield new Promise((r) => setTimeout(r, 0));
880
+ doc = probe();
881
+ i++;
882
+ }
883
+ return doc && (doc.body || doc.documentElement) ? doc : null;
884
+ });
885
+ }
886
+ function measureContentBox(el) {
887
+ const rect = el.getBoundingClientRect();
888
+ let bl = 0, br = 0, bt = 0, bb = 0;
889
+ try {
890
+ const cs = getComputedStyle(el);
891
+ bl = parseFloat(cs.borderLeftWidth) || 0;
892
+ br = parseFloat(cs.borderRightWidth) || 0;
893
+ bt = parseFloat(cs.borderTopWidth) || 0;
894
+ bb = parseFloat(cs.borderBottomWidth) || 0;
895
+ } catch (e) {
896
+ }
897
+ const contentWidth = Math.max(0, Math.round(rect.width - (bl + br)));
898
+ const contentHeight = Math.max(0, Math.round(rect.height - (bt + bb)));
899
+ return { contentWidth, contentHeight, rect };
900
+ }
901
+ function pinIframeViewport(doc, w, h) {
902
+ const style = doc.createElement("style");
903
+ style.setAttribute("data-sd-iframe-pin", "");
904
+ style.textContent = `html, body {margin: 0 !important;padding: 0 !important;width: ${w}px !important;height: ${h}px !important;min-width: ${w}px !important;min-height: ${h}px !important;box-sizing: border-box !important;overflow: hidden !important;background-clip: border-box !important;}`;
905
+ (doc.head || doc.documentElement).appendChild(style);
906
+ return () => {
907
+ try {
908
+ style.remove();
909
+ } catch (e) {
910
+ }
911
+ };
912
+ }
913
+ function rasterizeIframe(iframe, sessionCache, options) {
914
+ return __async(this, null, function* () {
915
+ const doc = yield getAccessibleIframeDocument(iframe, 3);
916
+ if (!doc) throw new Error("iframe document not accessible/ready");
917
+ const { contentWidth, contentHeight, rect } = measureContentBox(iframe);
918
+ const snap = options == null ? void 0 : options.snap;
919
+ if (!snap || typeof snap.toPng !== "function") {
920
+ throw new Error("snapdom.toPng not available in iframe or window");
921
+ }
922
+ const nested = __spreadProps(__spreadValues({}, options), { scale: 1 });
923
+ const unpin = pinIframeViewport(doc, contentWidth, contentHeight);
924
+ let imgEl;
925
+ try {
926
+ imgEl = yield snap.toPng(doc.documentElement, nested);
927
+ } finally {
928
+ unpin();
929
+ }
930
+ imgEl.style.display = "block";
931
+ imgEl.style.width = `${contentWidth}px`;
932
+ imgEl.style.height = `${contentHeight}px`;
933
+ const wrapper = document.createElement("div");
934
+ sessionCache.nodeMap.set(wrapper, iframe);
935
+ inlineAllStyles(iframe, wrapper, sessionCache, options);
936
+ wrapper.style.overflow = "hidden";
937
+ wrapper.style.display = "block";
938
+ if (!wrapper.style.width) wrapper.style.width = `${Math.round(rect.width)}px`;
939
+ if (!wrapper.style.height) wrapper.style.height = `${Math.round(rect.height)}px`;
940
+ wrapper.appendChild(imgEl);
941
+ return wrapper;
942
+ });
943
+ }
834
944
  function deepClone(node, sessionCache, options) {
835
- var _a, _b, _c, _d, _e;
836
- if (!node) throw new Error("Invalid node");
837
- const clonedAssignedNodes = /* @__PURE__ */ new Set();
838
- let pendingSelectValue = null;
839
- if (node.nodeType === Node.ELEMENT_NODE) {
840
- const tag = (node.localName || node.tagName || "").toLowerCase();
841
- if (node.id === "snapdom-sandbox" || node.hasAttribute("data-snapdom-sandbox")) {
842
- return null;
945
+ return __async(this, null, function* () {
946
+ var _a, _b, _c, _d, _e, _f;
947
+ if (!node) throw new Error("Invalid node");
948
+ const clonedAssignedNodes = /* @__PURE__ */ new Set();
949
+ let pendingSelectValue = null;
950
+ let pendingTextAreaValue = null;
951
+ if (node.nodeType === Node.ELEMENT_NODE) {
952
+ const tag = (node.localName || node.tagName || "").toLowerCase();
953
+ if (node.id === "snapdom-sandbox" || node.hasAttribute("data-snapdom-sandbox")) {
954
+ return null;
955
+ }
956
+ if (NO_CAPTURE_TAGS.has(tag)) {
957
+ return null;
958
+ }
843
959
  }
844
- if (NO_CAPTURE_TAGS.has(tag)) {
845
- return null;
960
+ if (node.nodeType === Node.TEXT_NODE) {
961
+ return node.cloneNode(true);
846
962
  }
847
- }
848
- if (node.nodeType === Node.TEXT_NODE) {
849
- return node.cloneNode(true);
850
- }
851
- if (node.nodeType !== Node.ELEMENT_NODE) {
852
- return node.cloneNode(true);
853
- }
854
- if (node.getAttribute("data-capture") === "exclude") {
855
- const spacer = document.createElement("div");
856
- const rect = node.getBoundingClientRect();
857
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
858
- return spacer;
859
- }
860
- if (options.exclude && Array.isArray(options.exclude)) {
861
- for (const selector of options.exclude) {
963
+ if (node.nodeType !== Node.ELEMENT_NODE) {
964
+ return node.cloneNode(true);
965
+ }
966
+ if (node.getAttribute("data-capture") === "exclude") {
967
+ const spacer = document.createElement("div");
968
+ const rect = node.getBoundingClientRect();
969
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
970
+ return spacer;
971
+ }
972
+ if (options.exclude && Array.isArray(options.exclude)) {
973
+ for (const selector of options.exclude) {
974
+ try {
975
+ if ((_a = node.matches) == null ? void 0 : _a.call(node, selector)) {
976
+ const spacer = document.createElement("div");
977
+ const rect = node.getBoundingClientRect();
978
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
979
+ return spacer;
980
+ }
981
+ } catch (err) {
982
+ console.warn(`Invalid selector in exclude option: ${selector}`, err);
983
+ }
984
+ }
985
+ }
986
+ if (typeof options.filter === "function") {
862
987
  try {
863
- if ((_a = node.matches) == null ? void 0 : _a.call(node, selector)) {
988
+ if (!options.filter(node)) {
864
989
  const spacer = document.createElement("div");
865
990
  const rect = node.getBoundingClientRect();
866
991
  spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
867
992
  return spacer;
868
993
  }
869
994
  } catch (err) {
870
- console.warn(`Invalid selector in exclude option: ${selector}`, err);
995
+ console.warn("Error in filter function:", err);
871
996
  }
872
997
  }
873
- }
874
- if (typeof options.filter === "function") {
875
- try {
876
- if (!options.filter(node)) {
877
- const spacer = document.createElement("div");
998
+ if (node.tagName === "IFRAME") {
999
+ let sameOrigin = false;
1000
+ try {
1001
+ sameOrigin = !!(node.contentDocument || ((_b = node.contentWindow) == null ? void 0 : _b.document));
1002
+ } catch (e) {
1003
+ sameOrigin = false;
1004
+ }
1005
+ if (sameOrigin) {
1006
+ try {
1007
+ const wrapper = yield rasterizeIframe(node, sessionCache, options);
1008
+ return wrapper;
1009
+ } catch (err) {
1010
+ console.warn("[SnapDOM] iframe rasterization failed, fallback:", err);
1011
+ }
1012
+ }
1013
+ if (options.placeholders) {
1014
+ const fallback = document.createElement("div");
1015
+ 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;`;
1016
+ inlineAllStyles(node, fallback, sessionCache, options);
1017
+ return fallback;
1018
+ } else {
878
1019
  const rect = node.getBoundingClientRect();
1020
+ const spacer = document.createElement("div");
879
1021
  spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1022
+ inlineAllStyles(node, spacer, sessionCache, options);
880
1023
  return spacer;
881
1024
  }
882
- } catch (err) {
883
- console.warn("Error in filter function:", err);
884
1025
  }
885
- }
886
- if (node.tagName === "IFRAME") {
887
- const fallback = document.createElement("div");
888
- 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;`;
889
- return fallback;
890
- }
891
- if (node.getAttribute("data-capture") === "placeholder") {
892
- const clone2 = node.cloneNode(false);
893
- sessionCache.nodeMap.set(clone2, node);
894
- inlineAllStyles(node, clone2, sessionCache, options);
895
- const placeholder = document.createElement("div");
896
- placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
897
- placeholder.style.cssText = `color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;`;
898
- clone2.appendChild(placeholder);
899
- return clone2;
900
- }
901
- if (node.tagName === "CANVAS") {
902
- const dataURL = node.toDataURL();
903
- const img = document.createElement("img");
904
- img.src = dataURL;
905
- img.width = node.width;
906
- img.height = node.height;
907
- sessionCache.nodeMap.set(img, node);
908
- inlineAllStyles(node, img, sessionCache, options);
909
- return img;
910
- }
911
- let clone;
912
- try {
913
- clone = node.cloneNode(false);
914
- sessionCache.nodeMap.set(clone, node);
915
- if (node.tagName === "IMG") {
916
- freezeImgSrcset(node, clone);
917
- }
918
- } catch (err) {
919
- console.error("[Snapdom] Failed to clone node:", node, err);
920
- throw err;
921
- }
922
- if (node instanceof HTMLTextAreaElement) {
923
- clone.textContent = node.value;
924
- clone.value = node.value;
925
- const rect = node.getBoundingClientRect();
926
- clone.style.width = `${rect.width}px`;
927
- clone.style.height = `${rect.height}px`;
928
- return clone;
929
- }
930
- if (node instanceof HTMLInputElement) {
931
- clone.value = node.value;
932
- clone.setAttribute("value", node.value);
933
- if (node.checked !== void 0) {
934
- clone.checked = node.checked;
935
- if (node.checked) clone.setAttribute("checked", "");
936
- if (node.indeterminate) clone.indeterminate = node.indeterminate;
1026
+ if (node.getAttribute("data-capture") === "placeholder") {
1027
+ const clone2 = node.cloneNode(false);
1028
+ sessionCache.nodeMap.set(clone2, node);
1029
+ inlineAllStyles(node, clone2, sessionCache, options);
1030
+ const placeholder = document.createElement("div");
1031
+ placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
1032
+ placeholder.style.cssText = `color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;`;
1033
+ clone2.appendChild(placeholder);
1034
+ return clone2;
1035
+ }
1036
+ if (node.tagName === "CANVAS") {
1037
+ const dataURL = node.toDataURL();
1038
+ const img = document.createElement("img");
1039
+ img.src = dataURL;
1040
+ img.width = node.width;
1041
+ img.height = node.height;
1042
+ sessionCache.nodeMap.set(img, node);
1043
+ inlineAllStyles(node, img, sessionCache, options);
1044
+ return img;
937
1045
  }
938
- }
939
- if (node instanceof HTMLSelectElement) {
940
- pendingSelectValue = node.value;
941
- }
942
- inlineAllStyles(node, clone, sessionCache, options);
943
- if (node.shadowRoot) {
1046
+ let clone;
944
1047
  try {
945
- const slots = node.shadowRoot.querySelectorAll("slot");
946
- for (const s of slots) {
947
- let assigned = [];
1048
+ clone = node.cloneNode(false);
1049
+ sessionCache.nodeMap.set(clone, node);
1050
+ if (node.tagName === "IMG") {
1051
+ freezeImgSrcset(node, clone);
948
1052
  try {
949
- assigned = ((_b = s.assignedNodes) == null ? void 0 : _b.call(s, { flatten: true })) || ((_c = s.assignedNodes) == null ? void 0 : _c.call(s)) || [];
1053
+ const rect = node.getBoundingClientRect();
1054
+ let w = Math.round(rect.width || 0);
1055
+ let h = Math.round(rect.height || 0);
1056
+ if (!w || !h) {
1057
+ const computed = window.getComputedStyle(node);
1058
+ const cssW = parseFloat(computed.width) || 0;
1059
+ const cssH = parseFloat(computed.height) || 0;
1060
+ const attrW = parseInt(node.getAttribute("width") || "", 10) || 0;
1061
+ const attrH = parseInt(node.getAttribute("height") || "", 10) || 0;
1062
+ const propW = node.width || node.naturalWidth || 0;
1063
+ const propH = node.height || node.naturalHeight || 0;
1064
+ w = Math.round(w || cssW || attrW || propW || 0);
1065
+ h = Math.round(h || cssH || attrH || propH || 0);
1066
+ }
1067
+ if (w) clone.dataset.snapdomWidth = String(w);
1068
+ if (h) clone.dataset.snapdomHeight = String(h);
950
1069
  } catch (e) {
951
- assigned = ((_d = s.assignedNodes) == null ? void 0 : _d.call(s)) || [];
952
1070
  }
953
- for (const an of assigned) clonedAssignedNodes.add(an);
954
1071
  }
955
- } catch (e) {
1072
+ } catch (err) {
1073
+ console.error("[Snapdom] Failed to clone node:", node, err);
1074
+ throw err;
1075
+ }
1076
+ if (node instanceof HTMLTextAreaElement) {
1077
+ const rect = node.getBoundingClientRect();
1078
+ clone.style.width = `${rect.width}px`;
1079
+ clone.style.height = `${rect.height}px`;
1080
+ }
1081
+ if (node instanceof HTMLInputElement) {
1082
+ clone.value = node.value;
1083
+ clone.setAttribute("value", node.value);
1084
+ if (node.checked !== void 0) {
1085
+ clone.checked = node.checked;
1086
+ if (node.checked) clone.setAttribute("checked", "");
1087
+ if (node.indeterminate) clone.indeterminate = node.indeterminate;
1088
+ }
956
1089
  }
957
- const scopeId = nextShadowScopeId(sessionCache);
958
- const scopeSelector = `[data-sd="${scopeId}"]`;
959
- try {
960
- clone.setAttribute("data-sd", scopeId);
961
- } catch (e) {
1090
+ if (node instanceof HTMLSelectElement) {
1091
+ pendingSelectValue = node.value;
962
1092
  }
963
- const rawCSS = extractShadowCSS(node.shadowRoot);
964
- const rewritten = rewriteShadowCSS(rawCSS, scopeSelector);
965
- const neededVars = collectCustomPropsFromCSS(rawCSS);
966
- const seed = buildSeedCustomPropsRule(node, neededVars, scopeSelector);
967
- injectScopedStyle(clone, seed + rewritten, scopeId);
968
- const shadowFrag = document.createDocumentFragment();
969
- for (const child of node.shadowRoot.childNodes) {
970
- if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") continue;
971
- const clonedChild = deepClone(child, sessionCache, options);
972
- if (clonedChild) shadowFrag.appendChild(clonedChild);
973
- }
974
- clone.appendChild(shadowFrag);
975
- }
976
- if (node.tagName === "SLOT") {
977
- const assigned = ((_e = node.assignedNodes) == null ? void 0 : _e.call(node, { flatten: true })) || [];
978
- const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
979
- const fragment = document.createDocumentFragment();
980
- for (const child of nodesToClone) {
981
- const clonedChild = deepClone(child, sessionCache, options);
982
- if (clonedChild) {
983
- markSlottedSubtree(clonedChild);
984
- fragment.appendChild(clonedChild);
1093
+ if (node instanceof HTMLTextAreaElement) {
1094
+ pendingTextAreaValue = node.value;
1095
+ }
1096
+ inlineAllStyles(node, clone, sessionCache, options);
1097
+ if (node.shadowRoot) {
1098
+ let callback2 = function(child, resolve) {
1099
+ if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
1100
+ return resolve(null);
1101
+ } else {
1102
+ deepClone(child, sessionCache, options).then((clonedChild) => {
1103
+ resolve(clonedChild || null);
1104
+ }).catch((e) => {
1105
+ resolve(null);
1106
+ });
1107
+ }
1108
+ };
1109
+ try {
1110
+ const slots = node.shadowRoot.querySelectorAll("slot");
1111
+ for (const s of slots) {
1112
+ let assigned = [];
1113
+ try {
1114
+ assigned = ((_c = s.assignedNodes) == null ? void 0 : _c.call(s, { flatten: true })) || ((_d = s.assignedNodes) == null ? void 0 : _d.call(s)) || [];
1115
+ } catch (e) {
1116
+ assigned = ((_e = s.assignedNodes) == null ? void 0 : _e.call(s)) || [];
1117
+ }
1118
+ for (const an of assigned) clonedAssignedNodes.add(an);
1119
+ }
1120
+ } catch (e) {
1121
+ }
1122
+ const scopeId = nextShadowScopeId(sessionCache);
1123
+ const scopeSelector = `[data-sd="${scopeId}"]`;
1124
+ try {
1125
+ clone.setAttribute("data-sd", scopeId);
1126
+ } catch (e) {
985
1127
  }
1128
+ const rawCSS = extractShadowCSS(node.shadowRoot);
1129
+ const rewritten = rewriteShadowCSS(rawCSS, scopeSelector);
1130
+ const neededVars = collectCustomPropsFromCSS(rawCSS);
1131
+ const seed = buildSeedCustomPropsRule(node, neededVars, scopeSelector);
1132
+ injectScopedStyle(clone, seed + rewritten, scopeId);
1133
+ const shadowFrag = document.createDocumentFragment();
1134
+ const cloneList2 = yield idleCallback(Array.from(node.shadowRoot.childNodes), callback2, options.fast);
1135
+ shadowFrag.append(...cloneList2.filter((clonedChild) => !!clonedChild));
1136
+ clone.appendChild(shadowFrag);
1137
+ }
1138
+ if (node.tagName === "SLOT") {
1139
+ let callback2 = function(child, resolve) {
1140
+ deepClone(child, sessionCache, options).then((clonedChild) => {
1141
+ if (clonedChild) {
1142
+ markSlottedSubtree(clonedChild);
1143
+ }
1144
+ resolve(clonedChild || null);
1145
+ }).catch((e) => {
1146
+ resolve(null);
1147
+ });
1148
+ };
1149
+ const assigned = ((_f = node.assignedNodes) == null ? void 0 : _f.call(node, { flatten: true })) || [];
1150
+ const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
1151
+ const fragment = document.createDocumentFragment();
1152
+ const cloneList2 = yield idleCallback(Array.from(nodesToClone), callback2, options.fast);
1153
+ fragment.append(...cloneList2.filter((clonedChild) => !!clonedChild));
1154
+ return fragment;
1155
+ }
1156
+ function callback(child, resolve) {
1157
+ if (clonedAssignedNodes.has(child)) return resolve(null);
1158
+ deepClone(child, sessionCache, options).then((clonedChild) => {
1159
+ resolve(clonedChild || null);
1160
+ }).catch((e) => {
1161
+ resolve(null);
1162
+ });
986
1163
  }
987
- return fragment;
988
- }
989
- for (const child of node.childNodes) {
990
- if (clonedAssignedNodes.has(child)) continue;
991
- const clonedChild = deepClone(child, sessionCache, options);
992
- if (clonedChild) clone.appendChild(clonedChild);
993
- }
994
- if (pendingSelectValue !== null && clone instanceof HTMLSelectElement) {
995
- clone.value = pendingSelectValue;
996
- for (const opt of clone.options) {
997
- if (opt.value === pendingSelectValue) {
998
- opt.setAttribute("selected", "");
999
- } else {
1000
- opt.removeAttribute("selected");
1164
+ const cloneList = yield idleCallback(Array.from(node.childNodes), callback, options.fast);
1165
+ clone.append(...cloneList.filter((clonedChild) => !!clonedChild));
1166
+ if (pendingSelectValue !== null && clone instanceof HTMLSelectElement) {
1167
+ clone.value = pendingSelectValue;
1168
+ for (const opt of clone.options) {
1169
+ if (opt.value === pendingSelectValue) {
1170
+ opt.setAttribute("selected", "");
1171
+ } else {
1172
+ opt.removeAttribute("selected");
1173
+ }
1001
1174
  }
1002
1175
  }
1003
- }
1004
- return clone;
1176
+ if (pendingTextAreaValue !== null && clone instanceof HTMLTextAreaElement) {
1177
+ clone.textContent = pendingTextAreaValue;
1178
+ }
1179
+ return clone;
1180
+ });
1005
1181
  }
1006
1182
 
1007
1183
  // src/modules/iconFonts.js
@@ -1891,7 +2067,7 @@
1891
2067
  console.warn("inlineExternal defs or symbol failed:", e);
1892
2068
  }
1893
2069
  try {
1894
- clone = deepClone(element, sessionCache, options, element);
2070
+ clone = yield deepClone(element, sessionCache, options, element);
1895
2071
  } catch (e) {
1896
2072
  console.warn("deepClone failed:", e);
1897
2073
  throw e;
@@ -2125,6 +2301,7 @@
2125
2301
  return __async(this, arguments, function* (clone, options = {}) {
2126
2302
  const imgs = Array.from(clone.querySelectorAll("img"));
2127
2303
  const processImg = (img) => __async(null, null, function* () {
2304
+ var _a, _b, _c, _d;
2128
2305
  if (!img.getAttribute("src")) {
2129
2306
  const eff = img.currentSrc || img.src || "";
2130
2307
  if (eff) img.setAttribute("src", eff);
@@ -2140,22 +2317,52 @@
2140
2317
  if (!img.height) img.height = img.naturalHeight || 100;
2141
2318
  return;
2142
2319
  }
2320
+ const { defaultImageUrl } = options || {};
2321
+ if (defaultImageUrl) {
2322
+ try {
2323
+ const dsW = parseInt(((_a = img.dataset) == null ? void 0 : _a.snapdomWidth) || "", 10) || 0;
2324
+ const dsH = parseInt(((_b = img.dataset) == null ? void 0 : _b.snapdomHeight) || "", 10) || 0;
2325
+ const attrW = parseInt(img.getAttribute("width") || "", 10) || 0;
2326
+ const attrH = parseInt(img.getAttribute("height") || "", 10) || 0;
2327
+ const styleW = parseFloat(((_c = img.style) == null ? void 0 : _c.width) || "") || 0;
2328
+ const styleH = parseFloat(((_d = img.style) == null ? void 0 : _d.height) || "") || 0;
2329
+ const width = dsW || styleW || attrW || img.width || void 0;
2330
+ const height = dsH || styleH || attrH || img.height || void 0;
2331
+ const fallbackUrl = typeof defaultImageUrl === "function" ? yield defaultImageUrl({ width, height, src, element: img }) : defaultImageUrl;
2332
+ if (fallbackUrl) {
2333
+ const fallbackData = yield snapFetch(fallbackUrl, { as: "dataURL", useProxy: options.useProxy });
2334
+ img.src = fallbackData.data;
2335
+ if (!img.width && width) img.width = width;
2336
+ if (!img.height && height) img.height = height;
2337
+ if (!img.width) img.width = img.naturalWidth || 100;
2338
+ if (!img.height) img.height = img.naturalHeight || 100;
2339
+ return;
2340
+ }
2341
+ } catch (e) {
2342
+ }
2343
+ }
2143
2344
  const w = img.width || img.naturalWidth || 100;
2144
2345
  const h = img.height || img.naturalHeight || 100;
2145
- const fallback = document.createElement("div");
2146
- fallback.style.cssText = [
2147
- `width:${w}px`,
2148
- `height:${h}px`,
2149
- "background:#ccc",
2150
- "display:inline-block",
2151
- "text-align:center",
2152
- `line-height:${h}px`,
2153
- "color:#666",
2154
- "font-size:12px",
2155
- "overflow:hidden"
2156
- ].join(";");
2157
- fallback.textContent = "img";
2158
- img.replaceWith(fallback);
2346
+ if (options.placeholders !== false) {
2347
+ const fallback = document.createElement("div");
2348
+ fallback.style.cssText = [
2349
+ `width:${w}px`,
2350
+ `height:${h}px`,
2351
+ "background:#ccc",
2352
+ "display:inline-block",
2353
+ "text-align:center",
2354
+ `line-height:${h}px`,
2355
+ "color:#666",
2356
+ "font-size:12px",
2357
+ "overflow:hidden"
2358
+ ].join(";");
2359
+ fallback.textContent = "img";
2360
+ img.replaceWith(fallback);
2361
+ } else {
2362
+ const spacer = document.createElement("div");
2363
+ spacer.style.cssText = `display:inline-block;width:${w}px;height:${h}px;visibility:hidden;`;
2364
+ img.replaceWith(spacer);
2365
+ }
2159
2366
  });
2160
2367
  for (let i = 0; i < imgs.length; i += 4) {
2161
2368
  const group = imgs.slice(i, i + 4).map(processImg);
@@ -2251,7 +2458,13 @@
2251
2458
  );
2252
2459
  yield ensureFontsReady(families, 2);
2253
2460
  }
2254
- fontsCSS = yield embedCustomFonts({ required, usedCodepoints, preCached: false, exclude: options.excludeFonts, useProxy: options.useProxy });
2461
+ fontsCSS = yield embedCustomFonts({
2462
+ required,
2463
+ usedCodepoints,
2464
+ preCached: false,
2465
+ exclude: options.excludeFonts,
2466
+ useProxy: options.useProxy
2467
+ });
2255
2468
  resolve();
2256
2469
  }), { fast });
2257
2470
  });
@@ -2271,12 +2484,14 @@
2271
2484
  }
2272
2485
  yield new Promise((resolve) => {
2273
2486
  idle(() => {
2274
- const rect = element.getBoundingClientRect();
2487
+ var _a, _b, _c, _d, _e;
2488
+ const rect = getNaturalBorderBoxSize(element);
2275
2489
  let w = rect.width;
2276
2490
  let h = rect.height;
2277
2491
  const hasW = Number.isFinite(options.width);
2278
2492
  const hasH = Number.isFinite(options.height);
2279
- const hasScale = typeof scale === "number" && scale !== 1;
2493
+ const scaleOpt = (_a = options.scale) != null ? _a : 1;
2494
+ const hasScale = typeof scaleOpt === "number" && scaleOpt !== 1;
2280
2495
  if (!hasScale) {
2281
2496
  const aspect = rect.width / rect.height;
2282
2497
  if (hasW && hasH) {
@@ -2290,30 +2505,79 @@
2290
2505
  w = h * aspect;
2291
2506
  }
2292
2507
  }
2293
- w = Math.ceil(w);
2294
- h = Math.ceil(h);
2295
- clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
2296
- clone.style.transformOrigin = "top left";
2297
- if (!hasScale && (hasW || hasH)) {
2298
- const originalW = rect.width;
2299
- const originalH = rect.height;
2300
- const scaleX = w / originalW;
2301
- const scaleY = h / originalH;
2302
- const existingTransform = clone.style.transform || "";
2303
- const scaleTransform = `scale(${scaleX}, ${scaleY})`;
2304
- clone.style.transform = `${scaleTransform} ${existingTransform}`.trim();
2508
+ w = Math.max(1, Math.ceil(w));
2509
+ h = Math.max(1, Math.ceil(h));
2510
+ const csEl = getComputedStyle(element);
2511
+ const baseTransform = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
2512
+ const ind = readIndividualTransforms(element);
2513
+ let outW, outH, tx, ty, cancelTranslateX = 0, cancelTranslateY = 0;
2514
+ if (!hasBBoxAffectingTransform(element)) {
2515
+ const r = element.getBoundingClientRect();
2516
+ const vp = inflateRect(r, { top: 0, right: 0, bottom: 0, left: 0 });
2517
+ outW = Math.max(1, vp.width);
2518
+ outH = Math.max(1, vp.height);
2519
+ tx = -(vp.x - Math.floor(r.left));
2520
+ ty = -(vp.y - Math.floor(r.top));
2521
+ } else {
2522
+ const TOTAL = readTotalTransformMatrix({
2523
+ baseTransform,
2524
+ rotate: ind.rotate || "0deg",
2525
+ scale: ind.scale,
2526
+ translate: ind.translate
2527
+ });
2528
+ const e = (_c = (_b = TOTAL.e) != null ? _b : TOTAL.m41) != null ? _c : 0;
2529
+ const f = (_e = (_d = TOTAL.f) != null ? _d : TOTAL.m42) != null ? _e : 0;
2530
+ cancelTranslateX = -e;
2531
+ cancelTranslateY = -f;
2532
+ const M2D = TOTAL.is2D ? matrix2DNoTranslate(TOTAL) : matrix2DNoTranslate(new DOMMatrix(TOTAL.toString()));
2533
+ const bb = bboxFromMatrix(w, h, M2D);
2534
+ outW = Math.max(1, Math.ceil(bb.width));
2535
+ outH = Math.max(1, Math.ceil(bb.height));
2536
+ tx = -bb.minX;
2537
+ ty = -bb.minY;
2305
2538
  }
2306
2539
  const svgNS = "http://www.w3.org/2000/svg";
2307
2540
  const fo = document.createElementNS(svgNS, "foreignObject");
2308
2541
  fo.setAttribute("width", "100%");
2309
2542
  fo.setAttribute("height", "100%");
2543
+ fo.setAttribute("x", String(tx));
2544
+ fo.setAttribute("y", String(ty));
2545
+ fo.style.overflow = "visible";
2310
2546
  const styleTag = document.createElement("style");
2311
- styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;}" + classCSS;
2547
+ styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + classCSS;
2312
2548
  fo.appendChild(styleTag);
2313
- fo.appendChild(clone);
2549
+ const container = document.createElement("div");
2550
+ container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
2551
+ container.style.width = `${w}px`;
2552
+ container.style.height = `${h}px`;
2553
+ container.style.overflow = "visible";
2554
+ container.style.transformOrigin = "0 0";
2555
+ const cancels = [];
2556
+ if (cancelTranslateX || cancelTranslateY) {
2557
+ cancels.push(`translate(${cancelTranslateX}px, ${cancelTranslateY}px)`);
2558
+ }
2559
+ if (!hasScale && (hasW || hasH)) {
2560
+ const sxWH = rect.width ? w / rect.width : 1;
2561
+ const syWH = rect.height ? h / rect.height : 1;
2562
+ cancels.push(`scale(${sxWH}, ${syWH})`);
2563
+ } else if (hasScale) {
2564
+ cancels.push(`scale(${scaleOpt})`);
2565
+ }
2566
+ if (cancels.length) container.style.transform = cancels.join(" ");
2567
+ clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
2568
+ clone.style.transformOrigin = "0 0";
2569
+ clone.style.position = "static";
2570
+ clone.style.left = "0";
2571
+ clone.style.top = "0";
2572
+ if (baseTransform) clone.style.transform = baseTransform;
2573
+ clone.style.rotate = ind.rotate || "0deg";
2574
+ clone.style.scale = ind.scale || "1";
2575
+ clone.style.translate = ind.translate || "0 0";
2576
+ container.appendChild(clone);
2577
+ fo.appendChild(container);
2314
2578
  const serializer = new XMLSerializer();
2315
2579
  const foString = serializer.serializeToString(fo);
2316
- const svgHeader = `<svg xmlns="${svgNS}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`;
2580
+ const svgHeader = `<svg xmlns="${svgNS}" width="${outW}" height="${outH}" viewBox="0 0 ${outW} ${outH}">`;
2317
2581
  const svgFooter = "</svg>";
2318
2582
  svgString = svgHeader + foString + svgFooter;
2319
2583
  dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
@@ -2325,6 +2589,129 @@
2325
2589
  return dataURL;
2326
2590
  });
2327
2591
  }
2592
+ function getNaturalBorderBoxSize(el) {
2593
+ const cs = getComputedStyle(el);
2594
+ const px = (v) => parseFloat(v) || 0;
2595
+ let w = px(cs.width);
2596
+ let h = px(cs.height);
2597
+ if (cs.boxSizing === "content-box") {
2598
+ w += px(cs.paddingLeft) + px(cs.paddingRight) + px(cs.borderLeftWidth) + px(cs.borderRightWidth);
2599
+ h += px(cs.paddingTop) + px(cs.paddingBottom) + px(cs.borderTopWidth) + px(cs.borderBottomWidth);
2600
+ }
2601
+ return { width: w, height: h };
2602
+ }
2603
+ function matrixFromComputed(el) {
2604
+ const tr = getComputedStyle(el).transform;
2605
+ if (!tr || tr === "none") return new DOMMatrix();
2606
+ try {
2607
+ return new DOMMatrix(tr);
2608
+ } catch (e) {
2609
+ return new WebKitCSSMatrix(tr);
2610
+ }
2611
+ }
2612
+ function matrix2DNoTranslate(M) {
2613
+ return new DOMMatrix([M.a, M.b, M.c, M.d, 0, 0]);
2614
+ }
2615
+ function bboxFromMatrix(w, h, M2D) {
2616
+ const pts = [
2617
+ { x: 0, y: 0 },
2618
+ { x: w, y: 0 },
2619
+ { x: 0, y: h },
2620
+ { x: w, y: h }
2621
+ ];
2622
+ const t = pts.map((p) => ({
2623
+ x: M2D.a * p.x + M2D.c * p.y,
2624
+ y: M2D.b * p.x + M2D.d * p.y
2625
+ }));
2626
+ const minX = Math.min(...t.map((p) => p.x));
2627
+ const maxX = Math.max(...t.map((p) => p.x));
2628
+ const minY = Math.min(...t.map((p) => p.y));
2629
+ const maxY = Math.max(...t.map((p) => p.y));
2630
+ return { minX, minY, width: maxX - minX, height: maxY - minY };
2631
+ }
2632
+ function readIndividualTransforms(el) {
2633
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
2634
+ const out = { rotate: "0deg", scale: null, translate: null };
2635
+ const map = (_a = el.computedStyleMap) == null ? void 0 : _a.call(el);
2636
+ if (map) {
2637
+ const rot = map.get("rotate");
2638
+ if (rot) {
2639
+ const ang = (_b = rot.angle) != null ? _b : rot;
2640
+ if (ang && "unit" in ang) {
2641
+ out.rotate = ang.unit === "rad" ? ang.value * 180 / Math.PI + "deg" : ang.value + ang.unit;
2642
+ } else {
2643
+ out.rotate = String(rot);
2644
+ }
2645
+ }
2646
+ const sc = map.get("scale");
2647
+ if (sc && sc.length) {
2648
+ const sx = (_d = (_c = sc[0]) == null ? void 0 : _c.value) != null ? _d : 1;
2649
+ const sy = (_f = (_e = sc[1]) == null ? void 0 : _e.value) != null ? _f : sx;
2650
+ out.scale = `${sx} ${sy}`;
2651
+ }
2652
+ const tr = map.get("translate");
2653
+ if (tr && tr.length) {
2654
+ const tx = (_h = (_g = tr[0]) == null ? void 0 : _g.value) != null ? _h : 0;
2655
+ const ty = (_j = (_i = tr[1]) == null ? void 0 : _i.value) != null ? _j : 0;
2656
+ const ux = (_l = (_k = tr[0]) == null ? void 0 : _k.unit) != null ? _l : "px";
2657
+ const uy = (_n = (_m = tr[1]) == null ? void 0 : _m.unit) != null ? _n : "px";
2658
+ out.translate = `${tx}${ux} ${ty}${uy}`;
2659
+ }
2660
+ return out;
2661
+ }
2662
+ const cs = getComputedStyle(el);
2663
+ out.rotate = cs.rotate && cs.rotate !== "none" ? cs.rotate : "0deg";
2664
+ out.scale = cs.scale && cs.scale !== "none" ? cs.scale : null;
2665
+ out.translate = cs.translate && cs.translate !== "none" ? cs.translate : null;
2666
+ return out;
2667
+ }
2668
+ function hasBBoxAffectingTransform(el) {
2669
+ const t = getComputedStyle(el).transform || "none";
2670
+ if (t === "none" || t === "matrix(1, 0, 0, 1, 0, 0)") return false;
2671
+ if (/^matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*,/i.test(t)) return false;
2672
+ return true;
2673
+ }
2674
+ function inflateRect(r, p) {
2675
+ const x = Math.floor(r.left - p.left);
2676
+ const y = Math.floor(r.top - p.top);
2677
+ const w = Math.ceil(r.width + p.left + p.right);
2678
+ const h = Math.ceil(r.height + p.top + p.bottom);
2679
+ return { x, y, width: w, height: h };
2680
+ }
2681
+ var __measureHost = null;
2682
+ function getMeasureHost() {
2683
+ if (__measureHost) return __measureHost;
2684
+ const n = document.createElement("div");
2685
+ n.id = "snapdom-measure-slot";
2686
+ n.setAttribute("aria-hidden", "true");
2687
+ Object.assign(n.style, {
2688
+ position: "absolute",
2689
+ left: "-99999px",
2690
+ top: "0px",
2691
+ width: "0px",
2692
+ height: "0px",
2693
+ overflow: "hidden",
2694
+ opacity: "0",
2695
+ pointerEvents: "none",
2696
+ contain: "size layout style"
2697
+ });
2698
+ document.documentElement.appendChild(n);
2699
+ __measureHost = n;
2700
+ return n;
2701
+ }
2702
+ function readTotalTransformMatrix(t) {
2703
+ const host = getMeasureHost();
2704
+ const tmp = document.createElement("div");
2705
+ tmp.style.transformOrigin = "0 0";
2706
+ if (t.baseTransform) tmp.style.transform = t.baseTransform;
2707
+ if (t.rotate) tmp.style.rotate = t.rotate;
2708
+ if (t.scale) tmp.style.scale = t.scale;
2709
+ if (t.translate) tmp.style.translate = t.translate;
2710
+ host.appendChild(tmp);
2711
+ const M = matrixFromComputed(tmp);
2712
+ host.removeChild(tmp);
2713
+ return M;
2714
+ }
2328
2715
 
2329
2716
  // src/core/context.js
2330
2717
  function normalizeCachePolicy(v) {
@@ -2340,7 +2727,7 @@
2340
2727
  return "soft";
2341
2728
  }
2342
2729
  function createContext(options = {}) {
2343
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
2730
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
2344
2731
  const resolvedFormat = (_a = options.format) != null ? _a : "png";
2345
2732
  const cachePolicy = normalizeCachePolicy(options.cache);
2346
2733
  return {
@@ -2351,25 +2738,28 @@
2351
2738
  // DOM filters
2352
2739
  exclude: (_e = options.exclude) != null ? _e : [],
2353
2740
  filter: (_f = options.filter) != null ? _f : null,
2741
+ placeholders: options.placeholders !== false,
2742
+ // default true
2354
2743
  // Fuentes
2355
2744
  embedFonts: (_g = options.embedFonts) != null ? _g : false,
2356
2745
  iconFonts: Array.isArray(options.iconFonts) ? options.iconFonts : options.iconFonts ? [options.iconFonts] : [],
2357
2746
  localFonts: Array.isArray(options.localFonts) ? options.localFonts : [],
2358
2747
  excludeFonts: (_h = options.excludeFonts) != null ? _h : void 0,
2748
+ defaultImageUrl: (_i = options.defaultImageUrl) != null ? _i : void 0,
2359
2749
  /** Cache policy (disabled|full|auto|soft). Default: "soft" */
2360
2750
  /** @type {CachePolicy} */
2361
2751
  cache: cachePolicy,
2362
2752
  // Red
2363
2753
  useProxy: typeof options.useProxy === "string" ? options.useProxy : "",
2364
2754
  // Salida
2365
- width: (_i = options.width) != null ? _i : null,
2366
- height: (_j = options.height) != null ? _j : null,
2755
+ width: (_j = options.width) != null ? _j : null,
2756
+ height: (_k = options.height) != null ? _k : null,
2367
2757
  format: resolvedFormat,
2368
- type: (_k = options.type) != null ? _k : "svg",
2369
- quality: (_l = options.quality) != null ? _l : 0.92,
2370
- dpr: (_m = options.dpr) != null ? _m : window.devicePixelRatio || 1,
2371
- backgroundColor: (_n = options.backgroundColor) != null ? _n : ["jpg", "jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null,
2372
- filename: (_o = options.filename) != null ? _o : "snapDOM"
2758
+ type: (_l = options.type) != null ? _l : "svg",
2759
+ quality: (_m = options.quality) != null ? _m : 0.92,
2760
+ dpr: (_n = options.dpr) != null ? _n : window.devicePixelRatio || 1,
2761
+ backgroundColor: (_o = options.backgroundColor) != null ? _o : ["jpg", "jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null,
2762
+ filename: (_p = options.filename) != null ? _p : "snapDOM"
2373
2763
  // Plugins (reservado para futura activación)
2374
2764
  // plugins: normalizePlugins(...),
2375
2765
  };
@@ -2392,31 +2782,41 @@
2392
2782
  // src/exporters/toCanvas.js
2393
2783
  function toCanvas(url, options) {
2394
2784
  return __async(this, null, function* () {
2785
+ const { scale, dpr } = options;
2786
+ const safari = isSafari();
2395
2787
  const img = new Image();
2396
2788
  img.crossOrigin = "anonymous";
2397
2789
  img.loading = "eager";
2398
2790
  img.decoding = "sync";
2399
2791
  img.src = url;
2400
- const isSafariBrowser = isSafari();
2401
- let appended = false;
2402
- if (isSafariBrowser) {
2403
- document.body.appendChild(img);
2404
- appended = true;
2792
+ let tempSlot = null;
2793
+ if (safari) {
2794
+ tempSlot = document.createElement("div");
2795
+ tempSlot.setAttribute("aria-hidden", "true");
2796
+ tempSlot.style.cssText = "position:absolute;left:-99999px;top:0;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none;contain:size layout style;";
2797
+ document.body.appendChild(tempSlot);
2798
+ tempSlot.appendChild(img);
2405
2799
  }
2406
2800
  yield img.decode();
2407
- if (isSafariBrowser) yield new Promise((resolve) => setTimeout(resolve, 100));
2408
- const width = img.naturalWidth * options.scale;
2409
- const height = img.naturalHeight * options.scale;
2801
+ if (safari) {
2802
+ yield new Promise((r) => setTimeout(r, 100));
2803
+ }
2804
+ const width = img.naturalWidth * scale;
2805
+ const height = img.naturalHeight * scale;
2410
2806
  const canvas = document.createElement("canvas");
2411
- const dpr = options.dpr;
2412
2807
  canvas.width = Math.ceil(width * dpr);
2413
2808
  canvas.height = Math.ceil(height * dpr);
2414
2809
  canvas.style.width = `${width}px`;
2415
2810
  canvas.style.height = `${height}px`;
2416
2811
  const ctx = canvas.getContext("2d");
2417
- ctx.scale(dpr, dpr);
2812
+ if (dpr !== 1) ctx.scale(dpr, dpr);
2418
2813
  ctx.drawImage(img, 0, 0, width, height);
2419
- if (appended) img.remove();
2814
+ if (tempSlot && tempSlot.parentNode) {
2815
+ try {
2816
+ tempSlot.parentNode.removeChild(tempSlot);
2817
+ } catch (e) {
2818
+ }
2819
+ }
2420
2820
  return canvas;
2421
2821
  });
2422
2822
  }
@@ -2484,6 +2884,14 @@
2484
2884
  if (!element) throw new Error("Element cannot be null or undefined");
2485
2885
  const context = createContext(userOptions);
2486
2886
  if (context.iconFonts && context.iconFonts.length > 0) extendIconFonts(context.iconFonts);
2887
+ if (!context.snap) {
2888
+ context.snap = {
2889
+ /** Delegates to this module’s PNG path (tu build devuelve <img>). */
2890
+ toPng: (el, opts) => snapdom.toPng(el, opts),
2891
+ /** Útil si en algún flujo necesitás vector o <img> directo. */
2892
+ toImg: (el, opts) => snapdom.toImg(el, opts)
2893
+ };
2894
+ }
2487
2895
  return snapdom.capture(element, context, INTERNAL_TOKEN);
2488
2896
  });
2489
2897
  }