@zumer/snapdom 1.9.11 → 1.9.12-dev.1

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
4
+ * v.1.9.12-dev.1
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -167,10 +167,25 @@
167
167
  var _inflight = /* @__PURE__ */ new Map();
168
168
  var _errorCache = /* @__PURE__ */ new Map();
169
169
  function isSpecialURL(url) {
170
- return /^data:|^blob:|^about:blank$/.test(url);
170
+ return /^data:|^blob:|^about:blank$/i.test(url);
171
+ }
172
+ function isAlreadyProxied(url, useProxy) {
173
+ try {
174
+ const baseHref = typeof location !== "undefined" && location.href ? location.href : "http://localhost/";
175
+ const proxyBaseRaw = useProxy.includes("{url}") ? useProxy.split("{url}")[0] : useProxy;
176
+ const proxyBase = new URL(proxyBaseRaw || ".", baseHref);
177
+ const u = new URL(url, baseHref);
178
+ if (u.origin === proxyBase.origin) return true;
179
+ const sp = u.searchParams;
180
+ if (sp && (sp.has("url") || sp.has("target"))) return true;
181
+ } catch (e) {
182
+ }
183
+ return false;
171
184
  }
172
185
  function shouldProxy(url, useProxy) {
173
- if (!useProxy || isSpecialURL(url)) return false;
186
+ if (!useProxy) return false;
187
+ if (isSpecialURL(url)) return false;
188
+ if (isAlreadyProxied(url, useProxy)) return false;
174
189
  try {
175
190
  const base = typeof location !== "undefined" && location.href ? location.href : "http://localhost/";
176
191
  const u = new URL(url, base);
@@ -181,8 +196,19 @@
181
196
  }
182
197
  function applyProxy(url, useProxy) {
183
198
  if (!useProxy) return url;
184
- if (useProxy.includes("{url}")) return useProxy.replace("{url}", encodeURIComponent(url));
185
- const sep = useProxy.endsWith("?") ? "" : useProxy.includes("?") ? "&" : "?";
199
+ if (useProxy.includes("{url}")) {
200
+ return useProxy.replace("{urlRaw}", safeEncodeURI(url)).replace("{url}", encodeURIComponent(url));
201
+ }
202
+ if (/[?&]url=?$/.test(useProxy)) {
203
+ return `${useProxy}${encodeURIComponent(url)}`;
204
+ }
205
+ if (useProxy.endsWith("?")) {
206
+ return `${useProxy}url=${encodeURIComponent(url)}`;
207
+ }
208
+ if (useProxy.endsWith("/")) {
209
+ return `${useProxy}${safeEncodeURI(url)}`;
210
+ }
211
+ const sep = useProxy.includes("?") ? "&" : "?";
186
212
  return `${useProxy}${sep}url=${encodeURIComponent(url)}`;
187
213
  }
188
214
  function blobToDataURL(blob) {
@@ -212,6 +238,65 @@
212
238
  const errorTTL = (_c = options.errorTTL) != null ? _c : 8e3;
213
239
  const headers = options.headers || {};
214
240
  const silent = !!options.silent;
241
+ if (/^data:/i.test(url)) {
242
+ try {
243
+ if (as === "text") {
244
+ return { ok: true, data: String(url), status: 200, url, fromCache: false };
245
+ }
246
+ if (as === "dataURL") {
247
+ return {
248
+ ok: true,
249
+ data: String(url),
250
+ status: 200,
251
+ url,
252
+ fromCache: false,
253
+ mime: String(url).slice(5).split(";")[0] || ""
254
+ };
255
+ }
256
+ const [, meta = "", data = ""] = String(url).match(/^data:([^,]*),(.*)$/) || [];
257
+ const isBase64 = /;base64/i.test(meta);
258
+ const bin = isBase64 ? atob(data) : decodeURIComponent(data);
259
+ const bytes = new Uint8Array([...bin].map((c) => c.charCodeAt(0)));
260
+ const b = new Blob([bytes], { type: (meta || "").split(";")[0] || "" });
261
+ return { ok: true, data: b, status: 200, url, fromCache: false, mime: b.type || "" };
262
+ } catch (e2) {
263
+ return { ok: false, data: null, status: 0, url, fromCache: false, reason: "special_url_error" };
264
+ }
265
+ }
266
+ if (/^blob:/i.test(url)) {
267
+ try {
268
+ const resp = yield fetch(url);
269
+ if (!resp.ok) {
270
+ return { ok: false, data: null, status: resp.status, url, fromCache: false, reason: "http_error" };
271
+ }
272
+ const blob = yield resp.blob();
273
+ const mime = blob.type || resp.headers.get("content-type") || "";
274
+ if (as === "dataURL") {
275
+ const dataURL = yield blobToDataURL(blob);
276
+ return { ok: true, data: dataURL, status: resp.status, url, fromCache: false, mime };
277
+ }
278
+ if (as === "text") {
279
+ const text = yield blob.text();
280
+ return { ok: true, data: text, status: resp.status, url, fromCache: false, mime };
281
+ }
282
+ return { ok: true, data: blob, status: resp.status, url, fromCache: false, mime };
283
+ } catch (e2) {
284
+ return { ok: false, data: null, status: 0, url, fromCache: false, reason: "network" };
285
+ }
286
+ }
287
+ if (/^about:blank$/i.test(url)) {
288
+ if (as === "dataURL") {
289
+ return {
290
+ ok: true,
291
+ data: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==",
292
+ status: 200,
293
+ url,
294
+ fromCache: false,
295
+ mime: "image/png"
296
+ };
297
+ }
298
+ return { ok: true, data: as === "text" ? "" : new Blob([]), status: 200, url, fromCache: false };
299
+ }
215
300
  const key = makeKey(url, { as, timeout, useProxy, errorTTL });
216
301
  const e = _errorCache.get(key);
217
302
  if (e && e.until > Date.now()) {
@@ -241,7 +326,7 @@
241
326
  const resp = yield fetch(finalURL, { signal: ctrl.signal, credentials: cred, headers });
242
327
  if (!resp.ok) {
243
328
  const result = { ok: false, data: null, status: resp.status, url: finalURL, fromCache: false, reason: "http_error" };
244
- _errorCache.set(key, { until: Date.now() + errorTTL, result });
329
+ if (errorTTL > 0) _errorCache.set(key, { until: Date.now() + errorTTL, result });
245
330
  if (!silent) {
246
331
  const short = `${resp.status} ${resp.statusText || ""}`.trim();
247
332
  snapLogger.warnOnce(
@@ -266,7 +351,9 @@
266
351
  } catch (err) {
267
352
  const reason = err && typeof err === "object" && "name" in err && err.name === "AbortError" ? String(err.message || "").includes("timeout") ? "timeout" : "abort" : "network";
268
353
  const result = { ok: false, data: null, status: 0, url: finalURL, fromCache: false, reason };
269
- _errorCache.set(key, { until: Date.now() + errorTTL, result });
354
+ if (!/^blob:/i.test(url) && errorTTL > 0) {
355
+ _errorCache.set(key, { until: Date.now() + errorTTL, result });
356
+ }
270
357
  if (!silent) {
271
358
  const k = `${reason}:${as}:${new URL(url, (_b2 = location == null ? void 0 : location.href) != null ? _b2 : "http://localhost/").origin}`;
272
359
  const tips = reason === "timeout" ? `Timeout after ${timeout}ms. Consider increasing timeout or using a proxy for ${url}` : reason === "abort" ? `Request aborted while fetching ${as} ${url}` : `Network/CORS issue while fetching ${as} ${url}. A proxy may be required`;
@@ -586,7 +673,10 @@
586
673
  }
587
674
  }
588
675
  function isSafari() {
589
- return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
676
+ const ua = typeof navigator !== "undefined" && navigator.userAgent ? navigator.userAgent : "";
677
+ const isSafariUA = /^((?!chrome|android).)*safari/i.test(ua);
678
+ const isWeChatUA = /(MicroMessenger|wxwork|WeCom|WindowsWechat|MacWechat)/i.test(ua);
679
+ return isSafariUA || isWeChatUA;
590
680
  }
591
681
 
592
682
  // src/modules/styles.js
@@ -621,7 +711,7 @@
621
711
  } catch (e) {
622
712
  }
623
713
  }
624
- function snapshotComputedStyleFull(style) {
714
+ function snapshotComputedStyleFull(style, options = {}) {
625
715
  const out = {};
626
716
  const vis = style.getPropertyValue("visibility");
627
717
  for (let i = 0; i < style.length; i++) {
@@ -632,6 +722,24 @@
632
722
  }
633
723
  out[prop] = val;
634
724
  }
725
+ if (options.embedFonts) {
726
+ const EXTRA_FONT_PROPS = [
727
+ "font-feature-settings",
728
+ "font-variation-settings",
729
+ "font-kerning",
730
+ "font-variant",
731
+ "font-variant-ligatures",
732
+ "font-optical-sizing"
733
+ ];
734
+ for (const prop of EXTRA_FONT_PROPS) {
735
+ if (out[prop]) continue;
736
+ try {
737
+ const v = style.getPropertyValue(prop);
738
+ if (v) out[prop] = v;
739
+ } catch (e) {
740
+ }
741
+ }
742
+ }
635
743
  if (vis === "hidden") out.opacity = "0";
636
744
  return out;
637
745
  }
@@ -644,11 +752,11 @@
644
752
  __snapshotSig.set(snap, sig);
645
753
  return sig;
646
754
  }
647
- function getSnapshot(el, preStyle = null) {
755
+ function getSnapshot(el, preStyle = null, options = {}) {
648
756
  const rec = snapshotCache.get(el);
649
757
  if (rec && rec.epoch === __epoch) return rec.snapshot;
650
758
  const style = preStyle || getComputedStyle(el);
651
- const snap = snapshotComputedStyleFull(style);
759
+ const snap = snapshotComputedStyleFull(style, options);
652
760
  snapshotCache.set(el, { epoch: __epoch, snapshot: snap });
653
761
  return snap;
654
762
  }
@@ -704,7 +812,7 @@
704
812
  session.styleCache.set(source, getComputedStyle(source));
705
813
  }
706
814
  const pre = session.styleCache.get(source);
707
- const snap = getSnapshot(source, pre);
815
+ const snap = getSnapshot(source, pre, ctx.options);
708
816
  const sig = styleSignature(snap);
709
817
  let key = persist.snapshotKeyCache.get(sig);
710
818
  if (!key) {
@@ -956,19 +1064,27 @@
956
1064
  return node.cloneNode(true);
957
1065
  }
958
1066
  if (node.getAttribute("data-capture") === "exclude") {
959
- const spacer = document.createElement("div");
960
- const rect = node.getBoundingClientRect();
961
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
962
- return spacer;
1067
+ if (options.excludeMode === "hide") {
1068
+ const spacer = document.createElement("div");
1069
+ const rect = node.getBoundingClientRect();
1070
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1071
+ return spacer;
1072
+ } else if (options.excludeMode === "remove") {
1073
+ return null;
1074
+ }
963
1075
  }
964
1076
  if (options.exclude && Array.isArray(options.exclude)) {
965
1077
  for (const selector of options.exclude) {
966
1078
  try {
967
1079
  if ((_a = node.matches) == null ? void 0 : _a.call(node, selector)) {
968
- const spacer = document.createElement("div");
969
- const rect = node.getBoundingClientRect();
970
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
971
- return spacer;
1080
+ if (options.excludeMode === "hide") {
1081
+ const spacer = document.createElement("div");
1082
+ const rect = node.getBoundingClientRect();
1083
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1084
+ return spacer;
1085
+ } else if (options.excludeMode === "remove") {
1086
+ return null;
1087
+ }
972
1088
  }
973
1089
  } catch (err) {
974
1090
  console.warn(`Invalid selector in exclude option: ${selector}`, err);
@@ -978,10 +1094,14 @@
978
1094
  if (typeof options.filter === "function") {
979
1095
  try {
980
1096
  if (!options.filter(node)) {
981
- const spacer = document.createElement("div");
982
- const rect = node.getBoundingClientRect();
983
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
984
- return spacer;
1097
+ if (options.filterMode === "hide") {
1098
+ const spacer = document.createElement("div");
1099
+ const rect = node.getBoundingClientRect();
1100
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1101
+ return spacer;
1102
+ } else if (options.filterMode === "remove") {
1103
+ return null;
1104
+ }
985
1105
  }
986
1106
  } catch (err) {
987
1107
  console.warn("Error in filter function:", err);
@@ -1870,7 +1990,186 @@
1870
1990
  });
1871
1991
  }
1872
1992
 
1993
+ // src/modules/counter.js
1994
+ function hasCounters(input) {
1995
+ return /\bcounter\s*\(|\bcounters\s*\(/.test(input || "");
1996
+ }
1997
+ function unquoteDoubleStrings(s) {
1998
+ return (s || "").replace(/"([^"]*)"/g, "$1");
1999
+ }
2000
+ function alpha(n, upper = false) {
2001
+ let s = "", x = Math.max(1, n);
2002
+ while (x > 0) {
2003
+ x--;
2004
+ s = String.fromCharCode(97 + x % 26) + s;
2005
+ x = Math.floor(x / 26);
2006
+ }
2007
+ return upper ? s.toUpperCase() : s;
2008
+ }
2009
+ function roman(n, upper = true) {
2010
+ const map = [[1e3, "M"], [900, "CM"], [500, "D"], [400, "CD"], [100, "C"], [90, "XC"], [50, "L"], [40, "XL"], [10, "X"], [9, "IX"], [5, "V"], [4, "IV"], [1, "I"]];
2011
+ let num = Math.max(1, Math.min(3999, n)), out = "";
2012
+ for (const [v, sym] of map) while (num >= v) {
2013
+ out += sym;
2014
+ num -= v;
2015
+ }
2016
+ return upper ? out : out.toLowerCase();
2017
+ }
2018
+ function formatCounter(value, style) {
2019
+ switch ((style || "decimal").toLowerCase()) {
2020
+ case "decimal":
2021
+ return String(Math.max(0, value));
2022
+ case "decimal-leading-zero":
2023
+ return (value < 10 ? "0" : "") + String(Math.max(0, value));
2024
+ case "lower-alpha":
2025
+ return alpha(value, false);
2026
+ case "upper-alpha":
2027
+ return alpha(value, true);
2028
+ case "lower-roman":
2029
+ return roman(value, false);
2030
+ case "upper-roman":
2031
+ return roman(value, true);
2032
+ default:
2033
+ return String(Math.max(0, value));
2034
+ }
2035
+ }
2036
+ function buildCounterContext(root) {
2037
+ const nodeCounters = /* @__PURE__ */ new WeakMap();
2038
+ const rootEl = root instanceof Document ? root.documentElement : root;
2039
+ const isLi = (el) => el && el.tagName === "LI";
2040
+ const countPrevLi = (li) => {
2041
+ let c = 0, p = li == null ? void 0 : li.parentElement;
2042
+ if (!p) return 0;
2043
+ for (const sib of p.children) {
2044
+ if (sib === li) break;
2045
+ if (sib.tagName === "LI") c++;
2046
+ }
2047
+ return c;
2048
+ };
2049
+ const cloneMap = (m) => {
2050
+ const out = /* @__PURE__ */ new Map();
2051
+ for (const [k, arr] of m) out.set(k, arr.slice());
2052
+ return out;
2053
+ };
2054
+ const applyTo = (baseMap, parentMap, el) => {
2055
+ var _a, _b;
2056
+ const map = cloneMap(baseMap);
2057
+ let reset;
2058
+ try {
2059
+ reset = ((_a = el.style) == null ? void 0 : _a.counterReset) || getComputedStyle(el).counterReset;
2060
+ } catch (e) {
2061
+ }
2062
+ if (reset && reset !== "none") {
2063
+ for (const part of reset.split(",")) {
2064
+ const toks = part.trim().split(/\s+/);
2065
+ const name = toks[0];
2066
+ const val = Number.isFinite(Number(toks[1])) ? Number(toks[1]) : 0;
2067
+ if (!name) continue;
2068
+ const parentStack = parentMap.get(name);
2069
+ if (parentStack && parentStack.length) {
2070
+ const s = parentStack.slice();
2071
+ s.push(val);
2072
+ map.set(name, s);
2073
+ } else {
2074
+ map.set(name, [val]);
2075
+ }
2076
+ }
2077
+ }
2078
+ let inc;
2079
+ try {
2080
+ inc = ((_b = el.style) == null ? void 0 : _b.counterIncrement) || getComputedStyle(el).counterIncrement;
2081
+ } catch (e) {
2082
+ }
2083
+ if (inc && inc !== "none") {
2084
+ for (const part of inc.split(",")) {
2085
+ const toks = part.trim().split(/\s+/);
2086
+ const name = toks[0];
2087
+ const by = Number.isFinite(Number(toks[1])) ? Number(toks[1]) : 1;
2088
+ if (!name) continue;
2089
+ const stack = map.get(name) || [];
2090
+ if (stack.length === 0) stack.push(0);
2091
+ stack[stack.length - 1] += by;
2092
+ map.set(name, stack);
2093
+ }
2094
+ }
2095
+ try {
2096
+ const cs = getComputedStyle(el);
2097
+ if (cs.display === "list-item" && isLi(el)) {
2098
+ const p = el.parentElement;
2099
+ let idx = 1;
2100
+ if (p && p.tagName === "OL") {
2101
+ const startAttr = p.getAttribute("start");
2102
+ const start = Number.isFinite(Number(startAttr)) ? Number(startAttr) : 1;
2103
+ const prev = countPrevLi(el);
2104
+ const ownAttr = el.getAttribute("value");
2105
+ idx = Number.isFinite(Number(ownAttr)) ? Number(ownAttr) : start + prev;
2106
+ } else {
2107
+ idx = 1 + countPrevLi(el);
2108
+ }
2109
+ const s = map.get("list-item") || [];
2110
+ if (s.length === 0) s.push(0);
2111
+ s[s.length - 1] = idx;
2112
+ map.set("list-item", s);
2113
+ }
2114
+ } catch (e) {
2115
+ }
2116
+ return map;
2117
+ };
2118
+ const build = (el, parentMap, carryMap) => {
2119
+ const curr = applyTo(carryMap, parentMap, el);
2120
+ nodeCounters.set(el, curr);
2121
+ let nextCarry = curr;
2122
+ for (const child of el.children) {
2123
+ const childCarry = build(child, curr, nextCarry);
2124
+ nextCarry = childCarry;
2125
+ }
2126
+ return curr;
2127
+ };
2128
+ const empty = /* @__PURE__ */ new Map();
2129
+ build(rootEl, empty, empty);
2130
+ return {
2131
+ get(node, name) {
2132
+ var _a;
2133
+ const s = (_a = nodeCounters.get(node)) == null ? void 0 : _a.get(name);
2134
+ return s && s.length ? s[s.length - 1] : 0;
2135
+ },
2136
+ getStack(node, name) {
2137
+ var _a;
2138
+ const s = (_a = nodeCounters.get(node)) == null ? void 0 : _a.get(name);
2139
+ return s ? s.slice() : [];
2140
+ }
2141
+ };
2142
+ }
2143
+ function resolveCountersInContent(raw, node, ctx) {
2144
+ if (!raw || raw === "none") return raw;
2145
+ try {
2146
+ const RX = /\b(counter|counters)\s*\(([^)]+)\)/g;
2147
+ let out = raw.replace(RX, (_, fn, args) => {
2148
+ var _a, _b, _c, _d;
2149
+ const parts = String(args).split(",").map((s) => s.trim());
2150
+ if (fn === "counter") {
2151
+ const name = (_a = parts[0]) == null ? void 0 : _a.replace(/^["']|["']$/g, "");
2152
+ const style = (parts[1] || "decimal").toLowerCase();
2153
+ const v = ctx.get(node, name);
2154
+ return formatCounter(v, style);
2155
+ } else {
2156
+ const name = (_b = parts[0]) == null ? void 0 : _b.replace(/^["']|["']$/g, "");
2157
+ const sep = (_d = (_c = parts[1]) == null ? void 0 : _c.replace(/^["']|["']$/g, "")) != null ? _d : "";
2158
+ const style = (parts[2] || "decimal").toLowerCase();
2159
+ const stack = ctx.getStack(node, name);
2160
+ if (!stack.length) return "";
2161
+ const pieces = stack.map((v) => formatCounter(v, style));
2162
+ return pieces.join(sep);
2163
+ }
2164
+ });
2165
+ return unquoteDoubleStrings(out);
2166
+ } catch (e) {
2167
+ return "- ";
2168
+ }
2169
+ }
2170
+
1873
2171
  // src/modules/pseudo.js
2172
+ var counterCtx = null;
1874
2173
  function inlinePseudoElements(source, clone, sessionCache, options) {
1875
2174
  return __async(this, null, function* () {
1876
2175
  var _a;
@@ -1909,7 +2208,11 @@
1909
2208
  continue;
1910
2209
  }
1911
2210
  const content = style.content;
1912
- const cleanContent = /counter\s*\(|counters\s*\(/.test(content) ? "- " : parseContent(content);
2211
+ let cleanContent = parseContent(content);
2212
+ if (hasCounters(content)) {
2213
+ counterCtx = buildCounterContext(source.ownerDocument || document);
2214
+ cleanContent = resolveCountersInContent(cleanContent, source, counterCtx);
2215
+ }
1913
2216
  const bg = style.backgroundImage;
1914
2217
  const bgColor = style.backgroundColor;
1915
2218
  const fontFamily = style.fontFamily;
@@ -2309,8 +2612,8 @@
2309
2612
  if (!img.height) img.height = img.naturalHeight || 100;
2310
2613
  return;
2311
2614
  }
2312
- const { defaultImageUrl } = options || {};
2313
- if (defaultImageUrl) {
2615
+ const { fallbackURL } = options || {};
2616
+ if (fallbackURL) {
2314
2617
  try {
2315
2618
  const dsW = parseInt(((_a = img.dataset) == null ? void 0 : _a.snapdomWidth) || "", 10) || 0;
2316
2619
  const dsH = parseInt(((_b = img.dataset) == null ? void 0 : _b.snapdomHeight) || "", 10) || 0;
@@ -2318,14 +2621,14 @@
2318
2621
  const attrH = parseInt(img.getAttribute("height") || "", 10) || 0;
2319
2622
  const styleW = parseFloat(((_c = img.style) == null ? void 0 : _c.width) || "") || 0;
2320
2623
  const styleH = parseFloat(((_d = img.style) == null ? void 0 : _d.height) || "") || 0;
2321
- const width = dsW || styleW || attrW || img.width || void 0;
2322
- const height = dsH || styleH || attrH || img.height || void 0;
2323
- const fallbackUrl = typeof defaultImageUrl === "function" ? yield defaultImageUrl({ width, height, src, element: img }) : defaultImageUrl;
2624
+ const fbW = dsW || styleW || attrW || img.width || void 0;
2625
+ const fbH = dsH || styleH || attrH || img.height || void 0;
2626
+ const fallbackUrl = typeof fallbackURL === "function" ? yield fallbackURL({ width: fbW, height: fbH, src, element: img }) : fallbackURL;
2324
2627
  if (fallbackUrl) {
2325
2628
  const fallbackData = yield snapFetch(fallbackUrl, { as: "dataURL", useProxy: options.useProxy });
2326
2629
  img.src = fallbackData.data;
2327
- if (!img.width && width) img.width = width;
2328
- if (!img.height && height) img.height = height;
2630
+ if (!img.width && fbW) img.width = fbW;
2631
+ if (!img.height && fbH) img.height = fbH;
2329
2632
  if (!img.width) img.width = img.naturalWidth || 100;
2330
2633
  if (!img.height) img.height = img.naturalHeight || 100;
2331
2634
  return;
@@ -2447,6 +2750,64 @@
2447
2750
  });
2448
2751
  }
2449
2752
 
2753
+ // src/modules/lineClamp.js
2754
+ function lineClamp(el) {
2755
+ var _a;
2756
+ if (!el) return () => {
2757
+ };
2758
+ const lines = getClamp(el);
2759
+ if (lines <= 0) return () => {
2760
+ };
2761
+ if (!isPlainTextContainer(el)) return () => {
2762
+ };
2763
+ const cs = getComputedStyle(el);
2764
+ const targetH = Math.round(usedLineHeightPx(cs) * lines + vpad(cs));
2765
+ const original = (_a = el.textContent) != null ? _a : "";
2766
+ const prevText = original;
2767
+ if (el.scrollHeight <= targetH + 0.5) {
2768
+ return () => {
2769
+ };
2770
+ }
2771
+ let lo = 0, hi = original.length, best = -1;
2772
+ while (lo <= hi) {
2773
+ const mid = lo + hi >> 1;
2774
+ el.textContent = original.slice(0, mid) + "\u2026";
2775
+ if (el.scrollHeight <= targetH + 0.5) {
2776
+ best = mid;
2777
+ lo = mid + 1;
2778
+ } else {
2779
+ hi = mid - 1;
2780
+ }
2781
+ }
2782
+ el.textContent = (best >= 0 ? original.slice(0, best) : "") + "\u2026";
2783
+ return () => {
2784
+ el.textContent = prevText;
2785
+ };
2786
+ }
2787
+ function getClamp(el) {
2788
+ const cs = getComputedStyle(el);
2789
+ let v = cs.getPropertyValue("-webkit-line-clamp") || cs.getPropertyValue("line-clamp");
2790
+ v = (v || "").trim();
2791
+ const n = parseInt(v, 10);
2792
+ return Number.isFinite(n) && n > 0 ? n : 0;
2793
+ }
2794
+ function usedLineHeightPx(cs) {
2795
+ const lh = (cs.lineHeight || "").trim();
2796
+ const fs = parseFloat(cs.fontSize) || 16;
2797
+ if (!lh || lh === "normal") return Math.round(fs * 1.2);
2798
+ if (lh.endsWith("px")) return parseFloat(lh);
2799
+ if (/^\d+(\.\d+)?$/.test(lh)) return Math.round(parseFloat(lh) * fs);
2800
+ if (lh.endsWith("%")) return Math.round(parseFloat(lh) / 100 * fs);
2801
+ return Math.round(fs * 1.2);
2802
+ }
2803
+ function vpad(cs) {
2804
+ return (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0);
2805
+ }
2806
+ function isPlainTextContainer(el) {
2807
+ if (el.childElementCount > 0) return false;
2808
+ return Array.from(el.childNodes).some((n) => n.nodeType === Node.TEXT_NODE);
2809
+ }
2810
+
2450
2811
  // src/core/capture.js
2451
2812
  function captureDOM(element, options) {
2452
2813
  return __async(this, null, function* () {
@@ -2458,7 +2819,12 @@
2458
2819
  let baseCSS = "";
2459
2820
  let dataURL;
2460
2821
  let svgString;
2461
- ({ clone, classCSS, styleCache } = yield prepareClone(element, options));
2822
+ const undoClamp = lineClamp(element);
2823
+ try {
2824
+ ({ clone, classCSS, styleCache } = yield prepareClone(element, options));
2825
+ } finally {
2826
+ undoClamp();
2827
+ }
2462
2828
  yield new Promise((resolve) => {
2463
2829
  idle(() => __async(null, null, function* () {
2464
2830
  yield inlineImages(clone, options);
@@ -2480,7 +2846,7 @@
2480
2846
  const families = new Set(
2481
2847
  Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
2482
2848
  );
2483
- yield ensureFontsReady(families, 5);
2849
+ yield ensureFontsReady(families, 2);
2484
2850
  }
2485
2851
  fontsCSS = yield embedCustomFonts({
2486
2852
  required,
@@ -2509,28 +2875,28 @@
2509
2875
  yield new Promise((resolve) => {
2510
2876
  idle(() => {
2511
2877
  var _a, _b, _c, _d, _e;
2512
- const rect = getNaturalBorderBoxSize(element);
2513
- let w = rect.width;
2514
- let h = rect.height;
2878
+ const rect = element.getBoundingClientRect();
2879
+ let w = Number(rect.width) || 0;
2880
+ let h = Number(rect.height) || 0;
2515
2881
  const hasW = Number.isFinite(options.width);
2516
2882
  const hasH = Number.isFinite(options.height);
2517
2883
  const scaleOpt = (_a = options.scale) != null ? _a : 1;
2518
2884
  const hasScale = typeof scaleOpt === "number" && scaleOpt !== 1;
2519
2885
  if (!hasScale) {
2520
- const aspect = rect.width / rect.height;
2886
+ const aspect = h > 0 ? w / h : 1;
2521
2887
  if (hasW && hasH) {
2522
- w = options.width;
2523
- h = options.height;
2888
+ w = Number(options.width);
2889
+ h = Number(options.height);
2524
2890
  } else if (hasW) {
2525
- w = options.width;
2526
- h = w / aspect;
2891
+ w = Number(options.width);
2892
+ h = w / (aspect || 1);
2527
2893
  } else if (hasH) {
2528
- h = options.height;
2529
- w = h * aspect;
2894
+ h = Number(options.height);
2895
+ w = h * (aspect || 1);
2530
2896
  }
2531
2897
  }
2532
- w = Math.max(1, Math.ceil(w));
2533
- h = Math.max(1, Math.ceil(h));
2898
+ w = Math.max(1, Math.ceil(Number.isFinite(w) ? w : 1));
2899
+ h = Math.max(1, Math.ceil(Number.isFinite(h) ? h : 1));
2534
2900
  const csEl = getComputedStyle(element);
2535
2901
  const baseTransform = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
2536
2902
  const ind = readIndividualTransforms(element);
@@ -2601,7 +2967,9 @@
2601
2967
  fo.appendChild(container);
2602
2968
  const serializer = new XMLSerializer();
2603
2969
  const foString = serializer.serializeToString(fo);
2604
- const svgHeader = `<svg xmlns="${svgNS}" width="${outW}" height="${outH}" viewBox="0 0 ${outW} ${outH}">`;
2970
+ const finalW = !hasScale && (hasW || hasH) ? w : outW;
2971
+ const finalH = !hasScale && (hasH || hasW) ? h : outH;
2972
+ const svgHeader = `<svg xmlns="${svgNS}" width="${finalW}" height="${finalH}" viewBox="0 0 ${outW} ${outH}">`;
2605
2973
  const svgFooter = "</svg>";
2606
2974
  svgString = svgHeader + foString + svgFooter;
2607
2975
  dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
@@ -2613,17 +2981,6 @@
2613
2981
  return dataURL;
2614
2982
  });
2615
2983
  }
2616
- function getNaturalBorderBoxSize(el) {
2617
- const cs = getComputedStyle(el);
2618
- const px = (v) => parseFloat(v) || 0;
2619
- let w = px(cs.width);
2620
- let h = px(cs.height);
2621
- if (cs.boxSizing === "content-box") {
2622
- w += px(cs.paddingLeft) + px(cs.paddingRight) + px(cs.borderLeftWidth) + px(cs.borderRightWidth);
2623
- h += px(cs.paddingTop) + px(cs.paddingBottom) + px(cs.borderTopWidth) + px(cs.borderBottomWidth);
2624
- }
2625
- return { width: w, height: h };
2626
- }
2627
2984
  function matrixFromComputed(el) {
2628
2985
  const tr = getComputedStyle(el).transform;
2629
2986
  if (!tr || tr === "none") return new DOMMatrix();
@@ -2654,31 +3011,40 @@
2654
3011
  return { minX, minY, width: maxX - minX, height: maxY - minY };
2655
3012
  }
2656
3013
  function readIndividualTransforms(el) {
2657
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
3014
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
2658
3015
  const out = { rotate: "0deg", scale: null, translate: null };
2659
- const map = (_a = el.computedStyleMap) == null ? void 0 : _a.call(el);
3016
+ const map = typeof el.computedStyleMap === "function" ? el.computedStyleMap() : null;
2660
3017
  if (map) {
2661
- const rot = map.get("rotate");
3018
+ const safeGet = (prop) => {
3019
+ try {
3020
+ if (typeof map.has === "function" && !map.has(prop)) return null;
3021
+ if (typeof map.get !== "function") return null;
3022
+ return map.get(prop);
3023
+ } catch (e) {
3024
+ return null;
3025
+ }
3026
+ };
3027
+ const rot = safeGet("rotate");
2662
3028
  if (rot) {
2663
- const ang = (_b = rot.angle) != null ? _b : rot;
2664
- if (ang && "unit" in ang) {
3029
+ const ang = (_a = rot.angle) != null ? _a : rot;
3030
+ if (ang && typeof ang === "object" && "unit" in ang) {
2665
3031
  out.rotate = ang.unit === "rad" ? ang.value * 180 / Math.PI + "deg" : ang.value + ang.unit;
2666
3032
  } else {
2667
3033
  out.rotate = String(rot);
2668
3034
  }
2669
3035
  }
2670
- const sc = map.get("scale");
3036
+ const sc = safeGet("scale");
2671
3037
  if (sc && sc.length) {
2672
- const sx = (_d = (_c = sc[0]) == null ? void 0 : _c.value) != null ? _d : 1;
2673
- const sy = (_f = (_e = sc[1]) == null ? void 0 : _e.value) != null ? _f : sx;
3038
+ const sx = (_c = (_b = sc[0]) == null ? void 0 : _b.value) != null ? _c : 1;
3039
+ const sy = (_e = (_d = sc[1]) == null ? void 0 : _d.value) != null ? _e : sx;
2674
3040
  out.scale = `${sx} ${sy}`;
2675
3041
  }
2676
- const tr = map.get("translate");
3042
+ const tr = safeGet("translate");
2677
3043
  if (tr && tr.length) {
2678
- const tx = (_h = (_g = tr[0]) == null ? void 0 : _g.value) != null ? _h : 0;
2679
- const ty = (_j = (_i = tr[1]) == null ? void 0 : _i.value) != null ? _j : 0;
2680
- const ux = (_l = (_k = tr[0]) == null ? void 0 : _k.unit) != null ? _l : "px";
2681
- const uy = (_n = (_m = tr[1]) == null ? void 0 : _m.unit) != null ? _n : "px";
3044
+ const tx = (_g = (_f = tr[0]) == null ? void 0 : _f.value) != null ? _g : 0;
3045
+ const ty = (_i = (_h = tr[1]) == null ? void 0 : _h.value) != null ? _i : 0;
3046
+ const ux = (_k = (_j = tr[0]) == null ? void 0 : _j.unit) != null ? _k : "px";
3047
+ const uy = (_m = (_l = tr[1]) == null ? void 0 : _l.unit) != null ? _m : "px";
2682
3048
  out.translate = `${tx}${ux} ${ty}${uy}`;
2683
3049
  }
2684
3050
  return out;
@@ -2749,7 +3115,7 @@
2749
3115
  return "soft";
2750
3116
  }
2751
3117
  function createContext(options = {}) {
2752
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
3118
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
2753
3119
  const resolvedFormat = (_a = options.format) != null ? _a : "png";
2754
3120
  const cachePolicy = normalizeCachePolicy(options.cache);
2755
3121
  return {
@@ -2759,29 +3125,31 @@
2759
3125
  scale: (_d = options.scale) != null ? _d : 1,
2760
3126
  // DOM filters
2761
3127
  exclude: (_e = options.exclude) != null ? _e : [],
2762
- filter: (_f = options.filter) != null ? _f : null,
3128
+ excludeMode: (_f = options.excludeMode) != null ? _f : "hide",
3129
+ filter: (_g = options.filter) != null ? _g : null,
3130
+ filterMode: (_h = options.filterMode) != null ? _h : "hide",
2763
3131
  placeholders: options.placeholders !== false,
2764
3132
  // default true
2765
3133
  // Fuentes
2766
- embedFonts: (_g = options.embedFonts) != null ? _g : false,
3134
+ embedFonts: (_i = options.embedFonts) != null ? _i : false,
2767
3135
  iconFonts: Array.isArray(options.iconFonts) ? options.iconFonts : options.iconFonts ? [options.iconFonts] : [],
2768
3136
  localFonts: Array.isArray(options.localFonts) ? options.localFonts : [],
2769
- excludeFonts: (_h = options.excludeFonts) != null ? _h : void 0,
2770
- defaultImageUrl: (_i = options.defaultImageUrl) != null ? _i : void 0,
3137
+ excludeFonts: (_j = options.excludeFonts) != null ? _j : void 0,
3138
+ fallbackURL: (_k = options.fallbackURL) != null ? _k : void 0,
2771
3139
  /** Cache policy (disabled|full|auto|soft). Default: "soft" */
2772
3140
  /** @type {CachePolicy} */
2773
3141
  cache: cachePolicy,
2774
3142
  // Red
2775
3143
  useProxy: typeof options.useProxy === "string" ? options.useProxy : "",
2776
3144
  // Salida
2777
- width: (_j = options.width) != null ? _j : null,
2778
- height: (_k = options.height) != null ? _k : null,
3145
+ width: (_l = options.width) != null ? _l : null,
3146
+ height: (_m = options.height) != null ? _m : null,
2779
3147
  format: resolvedFormat,
2780
- type: (_l = options.type) != null ? _l : "svg",
2781
- quality: (_m = options.quality) != null ? _m : 0.92,
2782
- dpr: (_n = options.dpr) != null ? _n : window.devicePixelRatio || 1,
2783
- backgroundColor: (_o = options.backgroundColor) != null ? _o : ["jpg", "jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null,
2784
- filename: (_p = options.filename) != null ? _p : "snapDOM"
3148
+ type: (_n = options.type) != null ? _n : "svg",
3149
+ quality: (_o = options.quality) != null ? _o : 0.92,
3150
+ dpr: (_p = options.dpr) != null ? _p : window.devicePixelRatio || 1,
3151
+ backgroundColor: (_q = options.backgroundColor) != null ? _q : ["jpg", "jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null,
3152
+ filename: (_r = options.filename) != null ? _r : "snapDOM"
2785
3153
  // Plugins (reservado para futura activación)
2786
3154
  // plugins: normalizePlugins(...),
2787
3155
  };
@@ -2805,24 +3173,12 @@
2805
3173
  function toCanvas(url, options) {
2806
3174
  return __async(this, null, function* () {
2807
3175
  const { scale, dpr } = options;
2808
- const safari = isSafari();
2809
3176
  const img = new Image();
2810
3177
  img.crossOrigin = "anonymous";
2811
3178
  img.loading = "eager";
2812
3179
  img.decoding = "sync";
2813
3180
  img.src = url;
2814
- let tempSlot = null;
2815
- if (safari) {
2816
- tempSlot = document.createElement("div");
2817
- tempSlot.setAttribute("aria-hidden", "true");
2818
- tempSlot.style.cssText = "position:absolute;left:-99999px;top:0;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none;contain:size layout style;";
2819
- document.body.appendChild(tempSlot);
2820
- tempSlot.appendChild(img);
2821
- }
2822
3181
  yield img.decode();
2823
- if (safari) {
2824
- yield new Promise((r) => setTimeout(r, 100));
2825
- }
2826
3182
  const width = img.naturalWidth * scale;
2827
3183
  const height = img.naturalHeight * scale;
2828
3184
  const canvas = document.createElement("canvas");
@@ -2833,12 +3189,6 @@
2833
3189
  const ctx = canvas.getContext("2d");
2834
3190
  if (dpr !== 1) ctx.scale(dpr, dpr);
2835
3191
  ctx.drawImage(img, 0, 0, width, height);
2836
- if (tempSlot && tempSlot.parentNode) {
2837
- try {
2838
- tempSlot.parentNode.removeChild(tempSlot);
2839
- } catch (e) {
2840
- }
2841
- }
2842
3192
  return canvas;
2843
3193
  });
2844
3194
  }
@@ -2880,6 +3230,7 @@
2880
3230
  // src/exporters/download.js
2881
3231
  function download(url, options) {
2882
3232
  return __async(this, null, function* () {
3233
+ options.dpr = 1;
2883
3234
  if (options.format === "svg") {
2884
3235
  const blob = yield toBlob(url, __spreadProps(__spreadValues({}, options), { type: "svg" }));
2885
3236
  const objectURL = URL.createObjectURL(blob);
@@ -2901,9 +3252,20 @@
2901
3252
 
2902
3253
  // src/api/snapdom.js
2903
3254
  var INTERNAL_TOKEN = Symbol("snapdom.internal");
3255
+ var _safariWarmup = false;
2904
3256
  function snapdom(element, userOptions) {
2905
3257
  return __async(this, null, function* () {
2906
3258
  if (!element) throw new Error("Element cannot be null or undefined");
3259
+ if (isSafari()) {
3260
+ for (let i = 0; i < 3; i++) {
3261
+ try {
3262
+ yield safariWarmup(element, userOptions);
3263
+ console.log("Iteraci\xF3n n\xFAmero:", i);
3264
+ _safariWarmup = false;
3265
+ } catch (error) {
3266
+ }
3267
+ }
3268
+ }
2907
3269
  const context = createContext(userOptions);
2908
3270
  if (context.iconFonts && context.iconFonts.length > 0) extendIconFonts(context.iconFonts);
2909
3271
  if (!context.snap) {
@@ -2948,26 +3310,68 @@
2948
3310
  snapdom.toJpg = (el, options) => snapdom(el, __spreadProps(__spreadValues({}, options), { format: "jpeg" })).then((result) => result.toJpg());
2949
3311
  snapdom.toWebp = (el, options) => snapdom(el, __spreadProps(__spreadValues({}, options), { format: "webp" })).then((result) => result.toWebp());
2950
3312
  snapdom.download = (el, options) => snapdom(el, options).then((result) => result.download());
3313
+ function safariWarmup(element, baseOptions) {
3314
+ return __async(this, null, function* () {
3315
+ if (_safariWarmup) return;
3316
+ const preflight = __spreadProps(__spreadValues({}, baseOptions), {
3317
+ fast: true,
3318
+ embedFonts: true,
3319
+ scale: 0.2
3320
+ });
3321
+ let url;
3322
+ try {
3323
+ url = yield captureDOM(element, preflight);
3324
+ } catch (e) {
3325
+ return;
3326
+ }
3327
+ yield new Promise((resolve) => {
3328
+ const img = new Image();
3329
+ img.decoding = "sync";
3330
+ img.loading = "eager";
3331
+ img.style.position = "fixed";
3332
+ img.style.left = 0;
3333
+ img.style.top = 0;
3334
+ img.style.width = "10px";
3335
+ img.style.height = "10px";
3336
+ img.style.opacity = "0.01";
3337
+ img.style.transform = "translateZ(10px)";
3338
+ img.style.willChange = "transform,opacity;";
3339
+ img.src = url;
3340
+ const cleanup = () => __async(null, null, function* () {
3341
+ yield new Promise((r) => setTimeout(r, 100));
3342
+ if (img && img.parentNode) img.parentNode.removeChild(img);
3343
+ _safariWarmup = true;
3344
+ resolve();
3345
+ });
3346
+ document.body.appendChild(img);
3347
+ cleanup();
3348
+ });
3349
+ });
3350
+ }
2951
3351
 
2952
3352
  // src/api/preCache.js
2953
3353
  function preCache() {
2954
3354
  return __async(this, arguments, function* (root = document, options = {}) {
2955
- var _a;
3355
+ var _a, _b, _c, _d;
2956
3356
  const {
2957
3357
  embedFonts = true,
2958
- cacheOpt = "full",
2959
3358
  useProxy = ""
2960
3359
  } = options;
2961
- applyCachePolicy(cacheOpt);
3360
+ const cacheMode = (_b = (_a = options.cache) != null ? _a : options.cacheOpt) != null ? _b : "full";
3361
+ applyCachePolicy(cacheMode);
2962
3362
  try {
2963
- yield document.fonts.ready;
3363
+ yield (_c = document.fonts) == null ? void 0 : _c.ready;
3364
+ } catch (e) {
3365
+ }
3366
+ try {
3367
+ precacheCommonTags();
2964
3368
  } catch (e) {
2965
3369
  }
2966
- precacheCommonTags();
2967
3370
  cache.session = cache.session || {};
2968
3371
  if (!cache.session.styleCache) {
2969
3372
  cache.session.styleCache = /* @__PURE__ */ new WeakMap();
2970
3373
  }
3374
+ cache.image = cache.image || /* @__PURE__ */ new Map();
2971
3375
  try {
2972
3376
  yield inlineBackgroundImages(
2973
3377
  root,
@@ -2988,12 +3392,15 @@
2988
3392
  }
2989
3393
  const promises = [];
2990
3394
  for (const img of imgEls) {
2991
- const src = img == null ? void 0 : img.src;
3395
+ const src = (img == null ? void 0 : img.currentSrc) || (img == null ? void 0 : img.src);
2992
3396
  if (!src) continue;
2993
3397
  if (!cache.image.has(src)) {
2994
- const p = Promise.resolve().then(() => snapFetch(src, { as: "dataURL", useProxy })).then((dataURL) => {
2995
- cache.image.set(src, dataURL);
2996
- }).catch(() => {
3398
+ const p = Promise.resolve().then(() => __async(null, null, function* () {
3399
+ const res = yield snapFetch(src, { as: "dataURL", useProxy });
3400
+ if ((res == null ? void 0 : res.ok) && typeof res.data === "string") {
3401
+ cache.image.set(src, res.data);
3402
+ }
3403
+ })).catch(() => {
2997
3404
  });
2998
3405
  promises.push(p);
2999
3406
  }
@@ -3019,7 +3426,8 @@
3019
3426
  try {
3020
3427
  const required = collectUsedFontVariants(root);
3021
3428
  const usedCodepoints = collectUsedCodepoints(root);
3022
- if (typeof isSafari === "function" ? isSafari() : !!isSafari) {
3429
+ const safari = typeof isSafari === "function" ? isSafari() : !!isSafari;
3430
+ if (safari) {
3023
3431
  const families = new Set(
3024
3432
  Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
3025
3433
  );
@@ -3030,7 +3438,7 @@
3030
3438
  usedCodepoints,
3031
3439
  exclude: options.excludeFonts,
3032
3440
  localFonts: options.localFonts,
3033
- useProxy: (_a = options.useProxy) != null ? _a : useProxy
3441
+ useProxy: (_d = options.useProxy) != null ? _d : useProxy
3034
3442
  });
3035
3443
  } catch (e) {
3036
3444
  }