@zumer/snapdom 1.9.10-dev.1 → 1.9.10

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.1
4
+ * v.1.9.10
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: {
@@ -687,6 +690,11 @@
687
690
  const ctx = _resolveCtx(sessionOrCtx, opts);
688
691
  const resetMode = ctx.options && ctx.options.cache || "auto";
689
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
+ }
690
698
  if (NO_DEFAULTS_TAGS.has((_a = source.tagName) == null ? void 0 : _a.toLowerCase())) {
691
699
  const author = (_b = source.getAttribute) == null ? void 0 : _b.call(source, "style");
692
700
  if (author) clone.setAttribute("style", author);
@@ -709,6 +717,31 @@
709
717
  }
710
718
 
711
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
+ }
712
745
  function addNotSlottedRightmost(sel) {
713
746
  sel = sel.trim();
714
747
  if (!sel) return sel;
@@ -830,185 +863,321 @@
830
863
  root.querySelectorAll("*").forEach((el) => el.setAttribute("data-sd-slotted", ""));
831
864
  }
832
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
+ }
833
944
  function deepClone(node, sessionCache, options) {
834
- var _a, _b, _c, _d, _e;
835
- if (!node) throw new Error("Invalid node");
836
- const clonedAssignedNodes = /* @__PURE__ */ new Set();
837
- let pendingSelectValue = null;
838
- if (node.nodeType === Node.ELEMENT_NODE) {
839
- const tag = (node.localName || node.tagName || "").toLowerCase();
840
- if (node.id === "snapdom-sandbox" || node.hasAttribute("data-snapdom-sandbox")) {
841
- 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
+ }
842
959
  }
843
- if (NO_CAPTURE_TAGS.has(tag)) {
844
- return null;
960
+ if (node.nodeType === Node.TEXT_NODE) {
961
+ return node.cloneNode(true);
845
962
  }
846
- }
847
- if (node.nodeType === Node.TEXT_NODE) {
848
- return node.cloneNode(true);
849
- }
850
- if (node.nodeType !== Node.ELEMENT_NODE) {
851
- return node.cloneNode(true);
852
- }
853
- if (node.getAttribute("data-capture") === "exclude") {
854
- const spacer = document.createElement("div");
855
- const rect = node.getBoundingClientRect();
856
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
857
- return spacer;
858
- }
859
- if (options.exclude && Array.isArray(options.exclude)) {
860
- 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") {
861
987
  try {
862
- if ((_a = node.matches) == null ? void 0 : _a.call(node, selector)) {
988
+ if (!options.filter(node)) {
863
989
  const spacer = document.createElement("div");
864
990
  const rect = node.getBoundingClientRect();
865
991
  spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
866
992
  return spacer;
867
993
  }
868
994
  } catch (err) {
869
- console.warn(`Invalid selector in exclude option: ${selector}`, err);
995
+ console.warn("Error in filter function:", err);
870
996
  }
871
997
  }
872
- }
873
- if (typeof options.filter === "function") {
874
- try {
875
- if (!options.filter(node)) {
876
- 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 {
877
1019
  const rect = node.getBoundingClientRect();
1020
+ const spacer = document.createElement("div");
878
1021
  spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1022
+ inlineAllStyles(node, spacer, sessionCache, options);
879
1023
  return spacer;
880
1024
  }
881
- } catch (err) {
882
- console.warn("Error in filter function:", err);
883
1025
  }
884
- }
885
- if (node.tagName === "IFRAME") {
886
- if (options.placeholders) {
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
- inlineAllStyles(node, fallback, sessionCache, options);
890
- return fallback;
891
- } else {
892
- const rect = node.getBoundingClientRect();
893
- const spacer = document.createElement("div");
894
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
895
- inlineAllStyles(node, spacer, sessionCache, options);
896
- return spacer;
897
- }
898
- }
899
- if (node.getAttribute("data-capture") === "placeholder") {
900
- const clone2 = node.cloneNode(false);
901
- sessionCache.nodeMap.set(clone2, node);
902
- inlineAllStyles(node, clone2, sessionCache, options);
903
- const placeholder = document.createElement("div");
904
- placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
905
- placeholder.style.cssText = `color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;`;
906
- clone2.appendChild(placeholder);
907
- return clone2;
908
- }
909
- if (node.tagName === "CANVAS") {
910
- const dataURL = node.toDataURL();
911
- const img = document.createElement("img");
912
- img.src = dataURL;
913
- img.width = node.width;
914
- img.height = node.height;
915
- sessionCache.nodeMap.set(img, node);
916
- inlineAllStyles(node, img, sessionCache, options);
917
- return img;
918
- }
919
- let clone;
920
- try {
921
- clone = node.cloneNode(false);
922
- sessionCache.nodeMap.set(clone, node);
923
- if (node.tagName === "IMG") {
924
- freezeImgSrcset(node, clone);
925
- }
926
- } catch (err) {
927
- console.error("[Snapdom] Failed to clone node:", node, err);
928
- throw err;
929
- }
930
- if (node instanceof HTMLTextAreaElement) {
931
- clone.textContent = node.value;
932
- clone.value = node.value;
933
- const rect = node.getBoundingClientRect();
934
- clone.style.width = `${rect.width}px`;
935
- clone.style.height = `${rect.height}px`;
936
- }
937
- if (node instanceof HTMLInputElement) {
938
- clone.value = node.value;
939
- clone.setAttribute("value", node.value);
940
- if (node.checked !== void 0) {
941
- clone.checked = node.checked;
942
- if (node.checked) clone.setAttribute("checked", "");
943
- 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;
944
1045
  }
945
- }
946
- if (node instanceof HTMLSelectElement) {
947
- pendingSelectValue = node.value;
948
- }
949
- inlineAllStyles(node, clone, sessionCache, options);
950
- if (node.shadowRoot) {
1046
+ let clone;
951
1047
  try {
952
- const slots = node.shadowRoot.querySelectorAll("slot");
953
- for (const s of slots) {
954
- let assigned = [];
1048
+ clone = node.cloneNode(false);
1049
+ sessionCache.nodeMap.set(clone, node);
1050
+ if (node.tagName === "IMG") {
1051
+ freezeImgSrcset(node, clone);
955
1052
  try {
956
- 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);
957
1069
  } catch (e) {
958
- assigned = ((_d = s.assignedNodes) == null ? void 0 : _d.call(s)) || [];
959
1070
  }
960
- for (const an of assigned) clonedAssignedNodes.add(an);
961
1071
  }
962
- } catch (e) {
1072
+ } catch (err) {
1073
+ console.error("[Snapdom] Failed to clone node:", node, err);
1074
+ throw err;
963
1075
  }
964
- const scopeId = nextShadowScopeId(sessionCache);
965
- const scopeSelector = `[data-sd="${scopeId}"]`;
966
- try {
967
- clone.setAttribute("data-sd", scopeId);
968
- } catch (e) {
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
+ }
969
1089
  }
970
- const rawCSS = extractShadowCSS(node.shadowRoot);
971
- const rewritten = rewriteShadowCSS(rawCSS, scopeSelector);
972
- const neededVars = collectCustomPropsFromCSS(rawCSS);
973
- const seed = buildSeedCustomPropsRule(node, neededVars, scopeSelector);
974
- injectScopedStyle(clone, seed + rewritten, scopeId);
975
- const shadowFrag = document.createDocumentFragment();
976
- for (const child of node.shadowRoot.childNodes) {
977
- if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") continue;
978
- const clonedChild = deepClone(child, sessionCache, options);
979
- if (clonedChild) shadowFrag.appendChild(clonedChild);
980
- }
981
- clone.appendChild(shadowFrag);
982
- }
983
- if (node.tagName === "SLOT") {
984
- const assigned = ((_e = node.assignedNodes) == null ? void 0 : _e.call(node, { flatten: true })) || [];
985
- const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
986
- const fragment = document.createDocumentFragment();
987
- for (const child of nodesToClone) {
988
- const clonedChild = deepClone(child, sessionCache, options);
989
- if (clonedChild) {
990
- markSlottedSubtree(clonedChild);
991
- fragment.appendChild(clonedChild);
1090
+ if (node instanceof HTMLSelectElement) {
1091
+ pendingSelectValue = node.value;
1092
+ }
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) {
992
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
+ });
993
1163
  }
994
- return fragment;
995
- }
996
- for (const child of node.childNodes) {
997
- if (clonedAssignedNodes.has(child)) continue;
998
- const clonedChild = deepClone(child, sessionCache, options);
999
- if (clonedChild) clone.appendChild(clonedChild);
1000
- }
1001
- if (pendingSelectValue !== null && clone instanceof HTMLSelectElement) {
1002
- clone.value = pendingSelectValue;
1003
- for (const opt of clone.options) {
1004
- if (opt.value === pendingSelectValue) {
1005
- opt.setAttribute("selected", "");
1006
- } else {
1007
- 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
+ }
1008
1174
  }
1009
1175
  }
1010
- }
1011
- return clone;
1176
+ if (pendingTextAreaValue !== null && clone instanceof HTMLTextAreaElement) {
1177
+ clone.textContent = pendingTextAreaValue;
1178
+ }
1179
+ return clone;
1180
+ });
1012
1181
  }
1013
1182
 
1014
1183
  // src/modules/iconFonts.js
@@ -1898,7 +2067,7 @@
1898
2067
  console.warn("inlineExternal defs or symbol failed:", e);
1899
2068
  }
1900
2069
  try {
1901
- clone = deepClone(element, sessionCache, options, element);
2070
+ clone = yield deepClone(element, sessionCache, options, element);
1902
2071
  } catch (e) {
1903
2072
  console.warn("deepClone failed:", e);
1904
2073
  throw e;
@@ -2132,6 +2301,7 @@
2132
2301
  return __async(this, arguments, function* (clone, options = {}) {
2133
2302
  const imgs = Array.from(clone.querySelectorAll("img"));
2134
2303
  const processImg = (img) => __async(null, null, function* () {
2304
+ var _a, _b, _c, _d;
2135
2305
  if (!img.getAttribute("src")) {
2136
2306
  const eff = img.currentSrc || img.src || "";
2137
2307
  if (eff) img.setAttribute("src", eff);
@@ -2147,6 +2317,30 @@
2147
2317
  if (!img.height) img.height = img.naturalHeight || 100;
2148
2318
  return;
2149
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
+ }
2150
2344
  const w = img.width || img.naturalWidth || 100;
2151
2345
  const h = img.height || img.naturalHeight || 100;
2152
2346
  if (options.placeholders !== false) {
@@ -2181,17 +2375,40 @@
2181
2375
  function inlineBackgroundImages(_0, _1, _2) {
2182
2376
  return __async(this, arguments, function* (source, clone, styleCache, options = {}) {
2183
2377
  const queue = [[source, clone]];
2184
- const imageProps = [
2378
+ const URL_PROPS = [
2185
2379
  "background-image",
2380
+ // Mask shorthands & images (both standard and WebKit)
2186
2381
  "mask",
2187
2382
  "mask-image",
2383
+ "-webkit-mask",
2188
2384
  "-webkit-mask-image",
2385
+ // Mask sources (rare, but keep)
2189
2386
  "mask-source",
2190
2387
  "mask-box-image-source",
2191
2388
  "mask-border-source",
2192
2389
  "-webkit-mask-box-image-source",
2390
+ // Border image
2193
2391
  "border-image",
2194
- "border-image-source",
2392
+ "border-image-source"
2393
+ ];
2394
+ const MASK_LAYOUT_PROPS = [
2395
+ "mask-position",
2396
+ "mask-size",
2397
+ "mask-repeat",
2398
+ // WebKit variants
2399
+ "-webkit-mask-position",
2400
+ "-webkit-mask-size",
2401
+ "-webkit-mask-repeat",
2402
+ // Extra (optional but helpful across engines)
2403
+ "mask-origin",
2404
+ "mask-clip",
2405
+ "-webkit-mask-origin",
2406
+ "-webkit-mask-clip",
2407
+ // Some engines expose X/Y position separately:
2408
+ "-webkit-mask-position-x",
2409
+ "-webkit-mask-position-y"
2410
+ ];
2411
+ const BORDER_AUX_PROPS = [
2195
2412
  "border-image-slice",
2196
2413
  "border-image-width",
2197
2414
  "border-image-outset",
@@ -2206,10 +2423,7 @@
2206
2423
  const bis = style.getPropertyValue("border-image-source");
2207
2424
  return bi && bi !== "none" || bis && bis !== "none";
2208
2425
  })();
2209
- for (const prop of imageProps) {
2210
- if (["border-image-slice", "border-image-width", "border-image-outset", "border-image-repeat"].includes(prop) && !hasBorderImage) {
2211
- continue;
2212
- }
2426
+ for (const prop of URL_PROPS) {
2213
2427
  const val = style.getPropertyValue(prop);
2214
2428
  if (!val || val === "none") continue;
2215
2429
  const splits = splitBackgroundImage(val);
@@ -2220,6 +2434,18 @@
2220
2434
  cloneNode.style.setProperty(prop, inlined.join(", "));
2221
2435
  }
2222
2436
  }
2437
+ for (const prop of MASK_LAYOUT_PROPS) {
2438
+ const val = style.getPropertyValue(prop);
2439
+ if (!val || val === "initial") continue;
2440
+ cloneNode.style.setProperty(prop, val);
2441
+ }
2442
+ if (hasBorderImage) {
2443
+ for (const prop of BORDER_AUX_PROPS) {
2444
+ const val = style.getPropertyValue(prop);
2445
+ if (!val || val === "initial") continue;
2446
+ cloneNode.style.setProperty(prop, val);
2447
+ }
2448
+ }
2223
2449
  const sChildren = Array.from(srcNode.children);
2224
2450
  const cChildren = Array.from(cloneNode.children);
2225
2451
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
@@ -2264,7 +2490,13 @@
2264
2490
  );
2265
2491
  yield ensureFontsReady(families, 2);
2266
2492
  }
2267
- fontsCSS = yield embedCustomFonts({ required, usedCodepoints, preCached: false, exclude: options.excludeFonts, useProxy: options.useProxy });
2493
+ fontsCSS = yield embedCustomFonts({
2494
+ required,
2495
+ usedCodepoints,
2496
+ preCached: false,
2497
+ exclude: options.excludeFonts,
2498
+ useProxy: options.useProxy
2499
+ });
2268
2500
  resolve();
2269
2501
  }), { fast });
2270
2502
  });
@@ -2284,12 +2516,14 @@
2284
2516
  }
2285
2517
  yield new Promise((resolve) => {
2286
2518
  idle(() => {
2287
- const rect = element.getBoundingClientRect();
2519
+ var _a, _b, _c, _d, _e;
2520
+ const rect = getNaturalBorderBoxSize(element);
2288
2521
  let w = rect.width;
2289
2522
  let h = rect.height;
2290
2523
  const hasW = Number.isFinite(options.width);
2291
2524
  const hasH = Number.isFinite(options.height);
2292
- const hasScale = typeof scale === "number" && scale !== 1;
2525
+ const scaleOpt = (_a = options.scale) != null ? _a : 1;
2526
+ const hasScale = typeof scaleOpt === "number" && scaleOpt !== 1;
2293
2527
  if (!hasScale) {
2294
2528
  const aspect = rect.width / rect.height;
2295
2529
  if (hasW && hasH) {
@@ -2303,30 +2537,79 @@
2303
2537
  w = h * aspect;
2304
2538
  }
2305
2539
  }
2306
- w = Math.ceil(w);
2307
- h = Math.ceil(h);
2308
- clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
2309
- clone.style.transformOrigin = "top left";
2310
- if (!hasScale && (hasW || hasH)) {
2311
- const originalW = rect.width;
2312
- const originalH = rect.height;
2313
- const scaleX = w / originalW;
2314
- const scaleY = h / originalH;
2315
- const existingTransform = clone.style.transform || "";
2316
- const scaleTransform = `scale(${scaleX}, ${scaleY})`;
2317
- clone.style.transform = `${scaleTransform} ${existingTransform}`.trim();
2540
+ w = Math.max(1, Math.ceil(w));
2541
+ h = Math.max(1, Math.ceil(h));
2542
+ const csEl = getComputedStyle(element);
2543
+ const baseTransform = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
2544
+ const ind = readIndividualTransforms(element);
2545
+ let outW, outH, tx, ty, cancelTranslateX = 0, cancelTranslateY = 0;
2546
+ if (!hasBBoxAffectingTransform(element)) {
2547
+ const r = element.getBoundingClientRect();
2548
+ const vp = inflateRect(r, { top: 0, right: 0, bottom: 0, left: 0 });
2549
+ outW = Math.max(1, vp.width);
2550
+ outH = Math.max(1, vp.height);
2551
+ tx = -(vp.x - Math.floor(r.left));
2552
+ ty = -(vp.y - Math.floor(r.top));
2553
+ } else {
2554
+ const TOTAL = readTotalTransformMatrix({
2555
+ baseTransform,
2556
+ rotate: ind.rotate || "0deg",
2557
+ scale: ind.scale,
2558
+ translate: ind.translate
2559
+ });
2560
+ const e = (_c = (_b = TOTAL.e) != null ? _b : TOTAL.m41) != null ? _c : 0;
2561
+ const f = (_e = (_d = TOTAL.f) != null ? _d : TOTAL.m42) != null ? _e : 0;
2562
+ cancelTranslateX = -e;
2563
+ cancelTranslateY = -f;
2564
+ const M2D = TOTAL.is2D ? matrix2DNoTranslate(TOTAL) : matrix2DNoTranslate(new DOMMatrix(TOTAL.toString()));
2565
+ const bb = bboxFromMatrix(w, h, M2D);
2566
+ outW = Math.max(1, Math.ceil(bb.width));
2567
+ outH = Math.max(1, Math.ceil(bb.height));
2568
+ tx = -bb.minX;
2569
+ ty = -bb.minY;
2318
2570
  }
2319
2571
  const svgNS = "http://www.w3.org/2000/svg";
2320
2572
  const fo = document.createElementNS(svgNS, "foreignObject");
2321
2573
  fo.setAttribute("width", "100%");
2322
2574
  fo.setAttribute("height", "100%");
2575
+ fo.setAttribute("x", String(tx));
2576
+ fo.setAttribute("y", String(ty));
2577
+ fo.style.overflow = "visible";
2323
2578
  const styleTag = document.createElement("style");
2324
- styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;}" + classCSS;
2579
+ styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + classCSS;
2325
2580
  fo.appendChild(styleTag);
2326
- fo.appendChild(clone);
2581
+ const container = document.createElement("div");
2582
+ container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
2583
+ container.style.width = `${w}px`;
2584
+ container.style.height = `${h}px`;
2585
+ container.style.overflow = "visible";
2586
+ container.style.transformOrigin = "0 0";
2587
+ const cancels = [];
2588
+ if (cancelTranslateX || cancelTranslateY) {
2589
+ cancels.push(`translate(${cancelTranslateX}px, ${cancelTranslateY}px)`);
2590
+ }
2591
+ if (!hasScale && (hasW || hasH)) {
2592
+ const sxWH = rect.width ? w / rect.width : 1;
2593
+ const syWH = rect.height ? h / rect.height : 1;
2594
+ cancels.push(`scale(${sxWH}, ${syWH})`);
2595
+ } else if (hasScale) {
2596
+ cancels.push(`scale(${scaleOpt})`);
2597
+ }
2598
+ if (cancels.length) container.style.transform = cancels.join(" ");
2599
+ clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
2600
+ clone.style.transformOrigin = "0 0";
2601
+ clone.style.position = "static";
2602
+ clone.style.left = "0";
2603
+ clone.style.top = "0";
2604
+ if (baseTransform) clone.style.transform = baseTransform;
2605
+ clone.style.rotate = ind.rotate || "0deg";
2606
+ clone.style.scale = ind.scale || "1";
2607
+ clone.style.translate = ind.translate || "0 0";
2608
+ container.appendChild(clone);
2609
+ fo.appendChild(container);
2327
2610
  const serializer = new XMLSerializer();
2328
2611
  const foString = serializer.serializeToString(fo);
2329
- const svgHeader = `<svg xmlns="${svgNS}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`;
2612
+ const svgHeader = `<svg xmlns="${svgNS}" width="${outW}" height="${outH}" viewBox="0 0 ${outW} ${outH}">`;
2330
2613
  const svgFooter = "</svg>";
2331
2614
  svgString = svgHeader + foString + svgFooter;
2332
2615
  dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
@@ -2338,11 +2621,132 @@
2338
2621
  return dataURL;
2339
2622
  });
2340
2623
  }
2624
+ function getNaturalBorderBoxSize(el) {
2625
+ const cs = getComputedStyle(el);
2626
+ const px = (v) => parseFloat(v) || 0;
2627
+ let w = px(cs.width);
2628
+ let h = px(cs.height);
2629
+ if (cs.boxSizing === "content-box") {
2630
+ w += px(cs.paddingLeft) + px(cs.paddingRight) + px(cs.borderLeftWidth) + px(cs.borderRightWidth);
2631
+ h += px(cs.paddingTop) + px(cs.paddingBottom) + px(cs.borderTopWidth) + px(cs.borderBottomWidth);
2632
+ }
2633
+ return { width: w, height: h };
2634
+ }
2635
+ function matrixFromComputed(el) {
2636
+ const tr = getComputedStyle(el).transform;
2637
+ if (!tr || tr === "none") return new DOMMatrix();
2638
+ try {
2639
+ return new DOMMatrix(tr);
2640
+ } catch (e) {
2641
+ return new WebKitCSSMatrix(tr);
2642
+ }
2643
+ }
2644
+ function matrix2DNoTranslate(M) {
2645
+ return new DOMMatrix([M.a, M.b, M.c, M.d, 0, 0]);
2646
+ }
2647
+ function bboxFromMatrix(w, h, M2D) {
2648
+ const pts = [
2649
+ { x: 0, y: 0 },
2650
+ { x: w, y: 0 },
2651
+ { x: 0, y: h },
2652
+ { x: w, y: h }
2653
+ ];
2654
+ const t = pts.map((p) => ({
2655
+ x: M2D.a * p.x + M2D.c * p.y,
2656
+ y: M2D.b * p.x + M2D.d * p.y
2657
+ }));
2658
+ const minX = Math.min(...t.map((p) => p.x));
2659
+ const maxX = Math.max(...t.map((p) => p.x));
2660
+ const minY = Math.min(...t.map((p) => p.y));
2661
+ const maxY = Math.max(...t.map((p) => p.y));
2662
+ return { minX, minY, width: maxX - minX, height: maxY - minY };
2663
+ }
2664
+ function readIndividualTransforms(el) {
2665
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
2666
+ const out = { rotate: "0deg", scale: null, translate: null };
2667
+ const map = (_a = el.computedStyleMap) == null ? void 0 : _a.call(el);
2668
+ if (map) {
2669
+ const rot = map.get("rotate");
2670
+ if (rot) {
2671
+ const ang = (_b = rot.angle) != null ? _b : rot;
2672
+ if (ang && "unit" in ang) {
2673
+ out.rotate = ang.unit === "rad" ? ang.value * 180 / Math.PI + "deg" : ang.value + ang.unit;
2674
+ } else {
2675
+ out.rotate = String(rot);
2676
+ }
2677
+ }
2678
+ const sc = map.get("scale");
2679
+ if (sc && sc.length) {
2680
+ const sx = (_d = (_c = sc[0]) == null ? void 0 : _c.value) != null ? _d : 1;
2681
+ const sy = (_f = (_e = sc[1]) == null ? void 0 : _e.value) != null ? _f : sx;
2682
+ out.scale = `${sx} ${sy}`;
2683
+ }
2684
+ const tr = map.get("translate");
2685
+ if (tr && tr.length) {
2686
+ const tx = (_h = (_g = tr[0]) == null ? void 0 : _g.value) != null ? _h : 0;
2687
+ const ty = (_j = (_i = tr[1]) == null ? void 0 : _i.value) != null ? _j : 0;
2688
+ const ux = (_l = (_k = tr[0]) == null ? void 0 : _k.unit) != null ? _l : "px";
2689
+ const uy = (_n = (_m = tr[1]) == null ? void 0 : _m.unit) != null ? _n : "px";
2690
+ out.translate = `${tx}${ux} ${ty}${uy}`;
2691
+ }
2692
+ return out;
2693
+ }
2694
+ const cs = getComputedStyle(el);
2695
+ out.rotate = cs.rotate && cs.rotate !== "none" ? cs.rotate : "0deg";
2696
+ out.scale = cs.scale && cs.scale !== "none" ? cs.scale : null;
2697
+ out.translate = cs.translate && cs.translate !== "none" ? cs.translate : null;
2698
+ return out;
2699
+ }
2700
+ function hasBBoxAffectingTransform(el) {
2701
+ const t = getComputedStyle(el).transform || "none";
2702
+ if (t === "none" || t === "matrix(1, 0, 0, 1, 0, 0)") return false;
2703
+ if (/^matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*,/i.test(t)) return false;
2704
+ return true;
2705
+ }
2706
+ function inflateRect(r, p) {
2707
+ const x = Math.floor(r.left - p.left);
2708
+ const y = Math.floor(r.top - p.top);
2709
+ const w = Math.ceil(r.width + p.left + p.right);
2710
+ const h = Math.ceil(r.height + p.top + p.bottom);
2711
+ return { x, y, width: w, height: h };
2712
+ }
2713
+ var __measureHost = null;
2714
+ function getMeasureHost() {
2715
+ if (__measureHost) return __measureHost;
2716
+ const n = document.createElement("div");
2717
+ n.id = "snapdom-measure-slot";
2718
+ n.setAttribute("aria-hidden", "true");
2719
+ Object.assign(n.style, {
2720
+ position: "absolute",
2721
+ left: "-99999px",
2722
+ top: "0px",
2723
+ width: "0px",
2724
+ height: "0px",
2725
+ overflow: "hidden",
2726
+ opacity: "0",
2727
+ pointerEvents: "none",
2728
+ contain: "size layout style"
2729
+ });
2730
+ document.documentElement.appendChild(n);
2731
+ __measureHost = n;
2732
+ return n;
2733
+ }
2734
+ function readTotalTransformMatrix(t) {
2735
+ const host = getMeasureHost();
2736
+ const tmp = document.createElement("div");
2737
+ tmp.style.transformOrigin = "0 0";
2738
+ if (t.baseTransform) tmp.style.transform = t.baseTransform;
2739
+ if (t.rotate) tmp.style.rotate = t.rotate;
2740
+ if (t.scale) tmp.style.scale = t.scale;
2741
+ if (t.translate) tmp.style.translate = t.translate;
2742
+ host.appendChild(tmp);
2743
+ const M = matrixFromComputed(tmp);
2744
+ host.removeChild(tmp);
2745
+ return M;
2746
+ }
2341
2747
 
2342
2748
  // src/core/context.js
2343
2749
  function normalizeCachePolicy(v) {
2344
- if (v === true) return "soft";
2345
- if (v === false) return "disabled";
2346
2750
  if (typeof v === "string") {
2347
2751
  const s = v.toLowerCase().trim();
2348
2752
  if (s === "disabled" || s === "full" || s === "auto" || s === "soft") return (
@@ -2353,7 +2757,7 @@
2353
2757
  return "soft";
2354
2758
  }
2355
2759
  function createContext(options = {}) {
2356
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
2760
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
2357
2761
  const resolvedFormat = (_a = options.format) != null ? _a : "png";
2358
2762
  const cachePolicy = normalizeCachePolicy(options.cache);
2359
2763
  return {
@@ -2371,20 +2775,21 @@
2371
2775
  iconFonts: Array.isArray(options.iconFonts) ? options.iconFonts : options.iconFonts ? [options.iconFonts] : [],
2372
2776
  localFonts: Array.isArray(options.localFonts) ? options.localFonts : [],
2373
2777
  excludeFonts: (_h = options.excludeFonts) != null ? _h : void 0,
2778
+ defaultImageUrl: (_i = options.defaultImageUrl) != null ? _i : void 0,
2374
2779
  /** Cache policy (disabled|full|auto|soft). Default: "soft" */
2375
2780
  /** @type {CachePolicy} */
2376
2781
  cache: cachePolicy,
2377
2782
  // Red
2378
2783
  useProxy: typeof options.useProxy === "string" ? options.useProxy : "",
2379
2784
  // Salida
2380
- width: (_i = options.width) != null ? _i : null,
2381
- height: (_j = options.height) != null ? _j : null,
2785
+ width: (_j = options.width) != null ? _j : null,
2786
+ height: (_k = options.height) != null ? _k : null,
2382
2787
  format: resolvedFormat,
2383
- type: (_k = options.type) != null ? _k : "svg",
2384
- quality: (_l = options.quality) != null ? _l : 0.92,
2385
- dpr: (_m = options.dpr) != null ? _m : window.devicePixelRatio || 1,
2386
- backgroundColor: (_n = options.backgroundColor) != null ? _n : ["jpg", "jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null,
2387
- filename: (_o = options.filename) != null ? _o : "snapDOM"
2788
+ type: (_l = options.type) != null ? _l : "svg",
2789
+ quality: (_m = options.quality) != null ? _m : 0.92,
2790
+ dpr: (_n = options.dpr) != null ? _n : window.devicePixelRatio || 1,
2791
+ backgroundColor: (_o = options.backgroundColor) != null ? _o : ["jpg", "jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null,
2792
+ filename: (_p = options.filename) != null ? _p : "snapDOM"
2388
2793
  // Plugins (reservado para futura activación)
2389
2794
  // plugins: normalizePlugins(...),
2390
2795
  };
@@ -2407,7 +2812,7 @@
2407
2812
  // src/exporters/toCanvas.js
2408
2813
  function toCanvas(url, options) {
2409
2814
  return __async(this, null, function* () {
2410
- const { scale: scale2, dpr } = options;
2815
+ const { scale, dpr } = options;
2411
2816
  const safari = isSafari();
2412
2817
  const img = new Image();
2413
2818
  img.crossOrigin = "anonymous";
@@ -2426,8 +2831,8 @@
2426
2831
  if (safari) {
2427
2832
  yield new Promise((r) => setTimeout(r, 100));
2428
2833
  }
2429
- const width = img.naturalWidth * scale2;
2430
- const height = img.naturalHeight * scale2;
2834
+ const width = img.naturalWidth * scale;
2835
+ const height = img.naturalHeight * scale;
2431
2836
  const canvas = document.createElement("canvas");
2432
2837
  canvas.width = Math.ceil(width * dpr);
2433
2838
  canvas.height = Math.ceil(height * dpr);
@@ -2509,16 +2914,28 @@
2509
2914
  if (!element) throw new Error("Element cannot be null or undefined");
2510
2915
  const context = createContext(userOptions);
2511
2916
  if (context.iconFonts && context.iconFonts.length > 0) extendIconFonts(context.iconFonts);
2917
+ if (!context.snap) {
2918
+ context.snap = {
2919
+ toPng: (el, opts) => snapdom.toPng(el, opts),
2920
+ toImg: (el, opts) => snapdom.toImg(el, opts)
2921
+ };
2922
+ }
2512
2923
  return snapdom.capture(element, context, INTERNAL_TOKEN);
2513
2924
  });
2514
2925
  }
2515
2926
  snapdom.capture = (el, context, _token) => __async(null, null, function* () {
2516
- if (_token !== INTERNAL_TOKEN) {
2517
- throw new Error("[snapdom.capture] is internal. Use snapdom(...) instead.");
2518
- }
2927
+ if (_token !== INTERNAL_TOKEN) throw new Error("[snapdom.capture] is internal. Use snapdom(...) instead.");
2519
2928
  const url = yield captureDOM(el, context);
2520
2929
  const ensureContext = (opts) => __spreadValues(__spreadValues({}, context), opts || {});
2521
- const withFormat = (format) => (opts) => rasterize(url, ensureContext(__spreadProps(__spreadValues({}, opts || {}), { format })));
2930
+ const withFormat = (format) => (opts) => {
2931
+ const next = ensureContext(__spreadProps(__spreadValues({}, opts || {}), { format }));
2932
+ const wantsJpeg = format === "jpeg" || format === "jpg";
2933
+ const noBg = next.backgroundColor == null || next.backgroundColor === "transparent";
2934
+ if (wantsJpeg && noBg) {
2935
+ next.backgroundColor = "#ffffff";
2936
+ }
2937
+ return rasterize(url, next);
2938
+ };
2522
2939
  return {
2523
2940
  url,
2524
2941
  toRaw: () => url,