@zumer/snapdom 1.9.8 → 1.9.10-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/snapdom.js CHANGED
@@ -1,13 +1,15 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.9.8
4
+ * v.1.9.10-dev.0
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
8
8
 
9
9
  (() => {
10
10
  var __defProp = Object.defineProperty;
11
+ var __defProps = Object.defineProperties;
12
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
11
13
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
12
14
  var __hasOwnProp = Object.prototype.hasOwnProperty;
13
15
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -23,18 +25,7 @@
23
25
  }
24
26
  return a;
25
27
  };
26
- var __objRest = (source, exclude) => {
27
- var target = {};
28
- for (var prop in source)
29
- if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
30
- target[prop] = source[prop];
31
- if (source != null && __getOwnPropSymbols)
32
- for (var prop of __getOwnPropSymbols(source)) {
33
- if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
34
- target[prop] = source[prop];
35
- }
36
- return target;
37
- };
28
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
38
29
  var __async = (__this, __arguments, generator) => {
39
30
  return new Promise((resolve, reject) => {
40
31
  var fulfilled = (value) => {
@@ -65,23 +56,314 @@
65
56
  baseStyle: /* @__PURE__ */ new Map(),
66
57
  computedStyle: /* @__PURE__ */ new WeakMap(),
67
58
  font: /* @__PURE__ */ new Set(),
68
- snapshot: /* @__PURE__ */ new WeakMap(),
69
- snapshotKey: /* @__PURE__ */ new Map(),
70
- preStyleMap: /* @__PURE__ */ new Map(),
71
- preStyle: /* @__PURE__ */ new WeakMap(),
72
- preNodeMap: /* @__PURE__ */ new Map(),
73
- reset: resetCache
59
+ session: {
60
+ styleMap: /* @__PURE__ */ new Map(),
61
+ styleCache: /* @__PURE__ */ new WeakMap(),
62
+ nodeMap: /* @__PURE__ */ new Map()
63
+ }
74
64
  };
75
- function resetCache() {
76
- cache.computedStyle = /* @__PURE__ */ new WeakMap();
77
- cache.snapshot = /* @__PURE__ */ new WeakMap();
78
- cache.snapshotKey.clear();
79
- cache.preStyleMap.clear();
80
- cache.preStyle = /* @__PURE__ */ new WeakMap();
81
- cache.preNodeMap.clear();
65
+ function applyCachePolicy(policy = "soft") {
66
+ switch (policy) {
67
+ case "auto": {
68
+ cache.session.styleMap = /* @__PURE__ */ new Map();
69
+ cache.session.nodeMap = /* @__PURE__ */ new Map();
70
+ return;
71
+ }
72
+ case "soft": {
73
+ cache.session.styleMap = /* @__PURE__ */ new Map();
74
+ cache.session.nodeMap = /* @__PURE__ */ new Map();
75
+ cache.session.styleCache = /* @__PURE__ */ new WeakMap();
76
+ return;
77
+ }
78
+ case "full": {
79
+ return;
80
+ }
81
+ case "disabled": {
82
+ cache.session.styleMap = /* @__PURE__ */ new Map();
83
+ cache.session.nodeMap = /* @__PURE__ */ new Map();
84
+ cache.session.styleCache = /* @__PURE__ */ new WeakMap();
85
+ cache.image.clear();
86
+ cache.background.clear();
87
+ cache.resource.clear();
88
+ cache.font.clear();
89
+ return;
90
+ }
91
+ default: {
92
+ cache.session.styleMap = /* @__PURE__ */ new Map();
93
+ cache.session.nodeMap = /* @__PURE__ */ new Map();
94
+ cache.session.styleCache = /* @__PURE__ */ new WeakMap();
95
+ return;
96
+ }
97
+ }
98
+ }
99
+
100
+ // src/utils/helpers.js
101
+ function extractURL(value) {
102
+ const match = value.match(/url\((['"]?)(.*?)(\1)\)/);
103
+ if (!match) return null;
104
+ const url = match[2].trim();
105
+ if (url.startsWith("#")) return null;
106
+ return url;
107
+ }
108
+ function stripTranslate(transform) {
109
+ if (!transform || transform === "none") return "";
110
+ let cleaned = transform.replace(/translate[XY]?\([^)]*\)/g, "");
111
+ cleaned = cleaned.replace(/matrix\(([^)]+)\)/g, (_, values) => {
112
+ const parts = values.split(",").map((s) => s.trim());
113
+ if (parts.length !== 6) return `matrix(${values})`;
114
+ parts[4] = "0";
115
+ parts[5] = "0";
116
+ return `matrix(${parts.join(", ")})`;
117
+ });
118
+ cleaned = cleaned.replace(/matrix3d\(([^)]+)\)/g, (_, values) => {
119
+ const parts = values.split(",").map((s) => s.trim());
120
+ if (parts.length !== 16) return `matrix3d(${values})`;
121
+ parts[12] = "0";
122
+ parts[13] = "0";
123
+ return `matrix3d(${parts.join(", ")})`;
124
+ });
125
+ return cleaned.trim().replace(/\s{2,}/g, " ");
126
+ }
127
+ function safeEncodeURI(uri) {
128
+ if (/%[0-9A-Fa-f]{2}/.test(uri)) return uri;
129
+ try {
130
+ return encodeURI(uri);
131
+ } catch (e) {
132
+ return uri;
133
+ }
82
134
  }
83
135
 
84
- // src/utils/cssTools.js
136
+ // src/modules/snapFetch.js
137
+ function createSnapLogger(prefix = "[snapDOM]", { ttlMs = 5 * 6e4, maxEntries = 12 } = {}) {
138
+ const seen = /* @__PURE__ */ new Map();
139
+ let emitted = 0;
140
+ function log(level, key, msg) {
141
+ if (emitted >= maxEntries) return;
142
+ const now = Date.now();
143
+ const until = seen.get(key) || 0;
144
+ if (until > now) return;
145
+ seen.set(key, now + ttlMs);
146
+ emitted++;
147
+ if (level === "warn" && console && console.warn) console.warn(`${prefix} ${msg}`);
148
+ else if (console && console.error) console.error(`${prefix} ${msg}`);
149
+ }
150
+ return {
151
+ warnOnce(key, msg) {
152
+ log("warn", key, msg);
153
+ },
154
+ errorOnce(key, msg) {
155
+ log("error", key, msg);
156
+ },
157
+ reset() {
158
+ seen.clear();
159
+ emitted = 0;
160
+ }
161
+ };
162
+ }
163
+ var snapLogger = createSnapLogger("[snapDOM]", { ttlMs: 3 * 6e4, maxEntries: 10 });
164
+ var _inflight = /* @__PURE__ */ new Map();
165
+ var _errorCache = /* @__PURE__ */ new Map();
166
+ function isSpecialURL(url) {
167
+ return /^data:|^blob:|^about:blank$/.test(url);
168
+ }
169
+ function shouldProxy(url, useProxy) {
170
+ if (!useProxy || isSpecialURL(url)) return false;
171
+ try {
172
+ const base = typeof location !== "undefined" && location.href ? location.href : "http://localhost/";
173
+ const u = new URL(url, base);
174
+ return typeof location !== "undefined" ? u.origin !== location.origin : true;
175
+ } catch (e) {
176
+ return !!useProxy;
177
+ }
178
+ }
179
+ function applyProxy(url, useProxy) {
180
+ if (!useProxy) return url;
181
+ if (useProxy.includes("{url}")) return useProxy.replace("{url}", encodeURIComponent(url));
182
+ const sep = useProxy.endsWith("?") ? "" : useProxy.includes("?") ? "&" : "?";
183
+ return `${useProxy}${sep}url=${encodeURIComponent(url)}`;
184
+ }
185
+ function blobToDataURL(blob) {
186
+ return new Promise((res, rej) => {
187
+ const fr = new FileReader();
188
+ fr.onload = () => res(String(fr.result || ""));
189
+ fr.onerror = () => rej(new Error("read_failed"));
190
+ fr.readAsDataURL(blob);
191
+ });
192
+ }
193
+ function makeKey(url, o) {
194
+ var _a, _b;
195
+ return [
196
+ o.as || "blob",
197
+ (_a = o.timeout) != null ? _a : 3e3,
198
+ o.useProxy || "",
199
+ (_b = o.errorTTL) != null ? _b : 8e3,
200
+ url
201
+ ].join("|");
202
+ }
203
+ function snapFetch(_0) {
204
+ return __async(this, arguments, function* (url, options = {}) {
205
+ var _a, _b, _c;
206
+ const as = (_a = options.as) != null ? _a : "blob";
207
+ const timeout = (_b = options.timeout) != null ? _b : 3e3;
208
+ const useProxy = options.useProxy || "";
209
+ const errorTTL = (_c = options.errorTTL) != null ? _c : 8e3;
210
+ const headers = options.headers || {};
211
+ const silent = !!options.silent;
212
+ const key = makeKey(url, { as, timeout, useProxy, errorTTL });
213
+ const e = _errorCache.get(key);
214
+ if (e && e.until > Date.now()) {
215
+ return __spreadProps(__spreadValues({}, e.result), { fromCache: true });
216
+ } else if (e) {
217
+ _errorCache.delete(key);
218
+ }
219
+ const inflight = _inflight.get(key);
220
+ if (inflight) return inflight;
221
+ const finalURL = shouldProxy(url, useProxy) ? applyProxy(url, useProxy) : url;
222
+ let cred = options.credentials;
223
+ if (!cred) {
224
+ try {
225
+ const base = typeof location !== "undefined" && location.href ? location.href : "http://localhost/";
226
+ const u = new URL(url, base);
227
+ const sameOrigin = typeof location !== "undefined" && u.origin === location.origin;
228
+ cred = sameOrigin ? "include" : "omit";
229
+ } catch (e2) {
230
+ cred = "omit";
231
+ }
232
+ }
233
+ const ctrl = new AbortController();
234
+ const timer = setTimeout(() => ctrl.abort("timeout"), timeout);
235
+ const p = (() => __async(null, null, function* () {
236
+ var _a2, _b2;
237
+ try {
238
+ const resp = yield fetch(finalURL, { signal: ctrl.signal, credentials: cred, headers });
239
+ if (!resp.ok) {
240
+ const result = { ok: false, data: null, status: resp.status, url: finalURL, fromCache: false, reason: "http_error" };
241
+ _errorCache.set(key, { until: Date.now() + errorTTL, result });
242
+ if (!silent) {
243
+ const short = `${resp.status} ${resp.statusText || ""}`.trim();
244
+ snapLogger.warnOnce(
245
+ `http:${resp.status}:${as}:${new URL(url, (_a2 = location == null ? void 0 : location.href) != null ? _a2 : "http://localhost/").origin}`,
246
+ `HTTP error ${short} while fetching ${as} ${url}`
247
+ );
248
+ }
249
+ options.onError && options.onError(result);
250
+ return result;
251
+ }
252
+ if (as === "text") {
253
+ const text = yield resp.text();
254
+ return { ok: true, data: text, status: resp.status, url: finalURL, fromCache: false };
255
+ }
256
+ const blob = yield resp.blob();
257
+ const mime = blob.type || resp.headers.get("content-type") || "";
258
+ if (as === "dataURL") {
259
+ const dataURL = yield blobToDataURL(blob);
260
+ return { ok: true, data: dataURL, status: resp.status, url: finalURL, fromCache: false, mime };
261
+ }
262
+ return { ok: true, data: blob, status: resp.status, url: finalURL, fromCache: false, mime };
263
+ } catch (err) {
264
+ const reason = err && typeof err === "object" && "name" in err && err.name === "AbortError" ? String(err.message || "").includes("timeout") ? "timeout" : "abort" : "network";
265
+ const result = { ok: false, data: null, status: 0, url: finalURL, fromCache: false, reason };
266
+ _errorCache.set(key, { until: Date.now() + errorTTL, result });
267
+ if (!silent) {
268
+ const k = `${reason}:${as}:${new URL(url, (_b2 = location == null ? void 0 : location.href) != null ? _b2 : "http://localhost/").origin}`;
269
+ 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`;
270
+ snapLogger.errorOnce(k, tips);
271
+ }
272
+ options.onError && options.onError(result);
273
+ return result;
274
+ } finally {
275
+ clearTimeout(timer);
276
+ _inflight.delete(key);
277
+ }
278
+ }))();
279
+ _inflight.set(key, p);
280
+ return p;
281
+ });
282
+ }
283
+
284
+ // src/utils/image.js
285
+ function createBackground(baseCanvas, backgroundColor) {
286
+ if (!backgroundColor || !baseCanvas.width || !baseCanvas.height) {
287
+ return baseCanvas;
288
+ }
289
+ const temp = document.createElement("canvas");
290
+ temp.width = baseCanvas.width;
291
+ temp.height = baseCanvas.height;
292
+ const ctx = temp.getContext("2d");
293
+ ctx.fillStyle = backgroundColor;
294
+ ctx.fillRect(0, 0, temp.width, temp.height);
295
+ ctx.drawImage(baseCanvas, 0, 0);
296
+ return temp;
297
+ }
298
+ function inlineSingleBackgroundEntry(_0) {
299
+ return __async(this, arguments, function* (entry, options = {}) {
300
+ const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
301
+ if (isGradient || entry.trim() === "none") {
302
+ return entry;
303
+ }
304
+ const rawUrl = extractURL(entry);
305
+ if (!rawUrl) {
306
+ return entry;
307
+ }
308
+ const encodedUrl = safeEncodeURI(rawUrl);
309
+ if (cache.background.has(encodedUrl)) {
310
+ const dataUrl = cache.background.get(encodedUrl);
311
+ return dataUrl ? `url("${dataUrl}")` : "none";
312
+ }
313
+ try {
314
+ const dataUrl = yield snapFetch(encodedUrl, { as: "dataURL", useProxy: options.useProxy });
315
+ if (dataUrl.ok) {
316
+ cache.background.set(encodedUrl, dataUrl.data);
317
+ return `url("${dataUrl.data}")`;
318
+ }
319
+ cache.background.set(encodedUrl, null);
320
+ return "none";
321
+ } catch (e) {
322
+ cache.background.set(encodedUrl, null);
323
+ return "none";
324
+ }
325
+ });
326
+ }
327
+
328
+ // src/utils/css.js
329
+ var NO_CAPTURE_TAGS = /* @__PURE__ */ new Set([
330
+ "meta",
331
+ "script",
332
+ "noscript",
333
+ "title",
334
+ "link",
335
+ "template"
336
+ ]);
337
+ var NO_DEFAULTS_TAGS = /* @__PURE__ */ new Set([
338
+ // non-painting / head stuff
339
+ "meta",
340
+ "link",
341
+ "style",
342
+ "title",
343
+ "noscript",
344
+ "script",
345
+ "template",
346
+ // SVG whole namespace (safe for LeaderLine/presentation attrs)
347
+ "svg",
348
+ "g",
349
+ "defs",
350
+ "use",
351
+ "marker",
352
+ "mask",
353
+ "clipPath",
354
+ "pattern",
355
+ "path",
356
+ "polygon",
357
+ "polyline",
358
+ "line",
359
+ "circle",
360
+ "ellipse",
361
+ "rect",
362
+ "filter",
363
+ "lineargradient",
364
+ "radialgradient",
365
+ "stop"
366
+ ]);
85
367
  var commonTags = [
86
368
  "div",
87
369
  "span",
@@ -108,12 +390,6 @@
108
390
  "h4",
109
391
  "h5",
110
392
  "h6",
111
- "svg",
112
- "path",
113
- "circle",
114
- "rect",
115
- "line",
116
- "g",
117
393
  "table",
118
394
  "thead",
119
395
  "tbody",
@@ -123,28 +399,33 @@
123
399
  ];
124
400
  function precacheCommonTags() {
125
401
  for (let tag of commonTags) {
126
- getDefaultStyleForTag(tag);
402
+ const t = String(tag).toLowerCase();
403
+ if (NO_CAPTURE_TAGS.has(t)) continue;
404
+ if (NO_DEFAULTS_TAGS.has(t)) continue;
405
+ getDefaultStyleForTag(t);
127
406
  }
128
407
  }
129
408
  function getDefaultStyleForTag(tagName) {
130
- if (cache.defaultStyle.has(tagName)) {
131
- return cache.defaultStyle.get(tagName);
132
- }
133
- const skipTags = /* @__PURE__ */ new Set(["script", "style", "meta", "link", "noscript", "template", "defs", "symbol", "title", "metadata", "desc"]);
134
- if (skipTags.has(tagName)) {
409
+ tagName = String(tagName).toLowerCase();
410
+ if (NO_DEFAULTS_TAGS.has(tagName)) {
135
411
  const empty = {};
136
412
  cache.defaultStyle.set(tagName, empty);
137
413
  return empty;
138
414
  }
415
+ if (cache.defaultStyle.has(tagName)) {
416
+ return cache.defaultStyle.get(tagName);
417
+ }
139
418
  let sandbox = document.getElementById("snapdom-sandbox");
140
419
  if (!sandbox) {
141
420
  sandbox = document.createElement("div");
142
421
  sandbox.id = "snapdom-sandbox";
422
+ sandbox.setAttribute("data-snapdom-sandbox", "true");
423
+ sandbox.setAttribute("aria-hidden", "true");
143
424
  sandbox.style.position = "absolute";
144
425
  sandbox.style.left = "-9999px";
145
426
  sandbox.style.top = "-9999px";
146
- sandbox.style.width = "0";
147
- sandbox.style.height = "0";
427
+ sandbox.style.width = "0px";
428
+ sandbox.style.height = "0px";
148
429
  sandbox.style.overflow = "hidden";
149
430
  document.body.appendChild(sandbox);
150
431
  }
@@ -154,32 +435,55 @@
154
435
  const styles = getComputedStyle(el);
155
436
  const defaults = {};
156
437
  for (let prop of styles) {
157
- defaults[prop] = styles.getPropertyValue(prop);
438
+ if (shouldIgnoreProp(prop)) continue;
439
+ const value = styles.getPropertyValue(prop);
440
+ defaults[prop] = value;
158
441
  }
159
442
  sandbox.removeChild(el);
160
443
  cache.defaultStyle.set(tagName, defaults);
161
444
  return defaults;
162
445
  }
163
- var IGNORED_PROPS = /* @__PURE__ */ new Set([
164
- "-webkit-locale"
446
+ var NO_PAINT_TOKEN = /(?:^|-)(animation|transition)(?:-|$)/i;
447
+ var NO_PAINT_PREFIX = /^(--|view-timeline|scroll-timeline|animation-trigger|offset-|position-try|app-region|interactivity|overlay|view-transition|-webkit-locale|-webkit-user-(?:drag|modify)|-webkit-tap-highlight-color|-webkit-text-security)$/i;
448
+ var NO_PAINT_EXACT = /* @__PURE__ */ new Set([
449
+ // Interaction hints
450
+ "cursor",
451
+ "pointer-events",
452
+ "touch-action",
453
+ "user-select",
454
+ // Printing/speech/reading-mode hints
455
+ "print-color-adjust",
456
+ "speak",
457
+ "reading-flow",
458
+ "reading-order",
459
+ // Anchoring/container/timeline scopes (metadata for layout queries)
460
+ "anchor-name",
461
+ "anchor-scope",
462
+ "container-name",
463
+ "container-type",
464
+ "timeline-scope"
165
465
  ]);
166
- function getStyleKey(snapshot, tagName, compress = false) {
466
+ function shouldIgnoreProp(prop) {
467
+ const p = String(prop).toLowerCase();
468
+ if (NO_PAINT_EXACT.has(p)) return true;
469
+ if (NO_PAINT_PREFIX.test(p)) return true;
470
+ if (NO_PAINT_TOKEN.test(p)) return true;
471
+ return false;
472
+ }
473
+ function getStyleKey(snapshot, tagName) {
474
+ tagName = String(tagName || "").toLowerCase();
475
+ if (NO_DEFAULTS_TAGS.has(tagName)) {
476
+ return "";
477
+ }
167
478
  const entries = [];
168
- const defaultStyles = getDefaultStyleForTag(tagName);
479
+ const defaults = getDefaultStyleForTag(tagName);
169
480
  for (let [prop, value] of Object.entries(snapshot)) {
170
- if (IGNORED_PROPS.has(prop)) continue;
171
- if (!compress) {
172
- if (value) {
173
- entries.push(`${prop}:${value}`);
174
- }
175
- } else {
176
- const defaultValue = defaultStyles[prop];
177
- if (value && value !== defaultValue) {
178
- entries.push(`${prop}:${value}`);
179
- }
180
- }
481
+ if (shouldIgnoreProp(prop)) continue;
482
+ const def = defaults[prop];
483
+ if (value && value !== def) entries.push(`${prop}:${value}`);
181
484
  }
182
- return entries.sort().join(";");
485
+ entries.sort();
486
+ return entries.join(";");
183
487
  }
184
488
  function collectUsedTagNames(root) {
185
489
  const tagSet = /* @__PURE__ */ new Set();
@@ -200,6 +504,7 @@
200
504
  const styles = cache.defaultStyle.get(tagName);
201
505
  if (!styles) continue;
202
506
  const key = Object.entries(styles).map(([k, v]) => `${k}:${v};`).sort().join("");
507
+ if (!key) continue;
203
508
  if (!groups.has(key)) {
204
509
  groups.set(key, []);
205
510
  }
@@ -212,46 +517,13 @@
212
517
  }
213
518
  return css;
214
519
  }
215
- function generateCSSClasses() {
216
- const keySet = new Set(cache.preStyleMap.values());
520
+ function generateCSSClasses(styleMap) {
521
+ const keys = Array.from(new Set(styleMap.values())).filter(Boolean).sort();
217
522
  const classMap = /* @__PURE__ */ new Map();
218
- let counter = 1;
219
- for (const key of keySet) {
220
- if (!key.trim()) continue;
221
- classMap.set(key, `c${counter++}`);
222
- }
523
+ let i = 1;
524
+ for (const k of keys) classMap.set(k, `c${i++}`);
223
525
  return classMap;
224
526
  }
225
-
226
- // src/utils/helpers.js
227
- function inlineSingleBackgroundEntry(_0) {
228
- return __async(this, arguments, function* (entry, options = {}) {
229
- const rawUrl = extractURL(entry);
230
- const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
231
- if (rawUrl) {
232
- const encodedUrl = safeEncodeURI(rawUrl);
233
- if (cache.background.has(encodedUrl)) {
234
- return options.skipInline ? void 0 : `url(${cache.background.get(encodedUrl)})`;
235
- } else {
236
- const dataUrl = yield fetchImage(encodedUrl, { useProxy: options.useProxy });
237
- cache.background.set(encodedUrl, dataUrl);
238
- return options.skipInline ? void 0 : `url("${dataUrl}")`;
239
- }
240
- }
241
- if (isGradient || entry === "none") {
242
- return entry;
243
- }
244
- return entry;
245
- });
246
- }
247
- function idle(fn, { fast = false } = {}) {
248
- if (fast) return fn();
249
- if ("requestIdleCallback" in window) {
250
- requestIdleCallback(fn, { timeout: 50 });
251
- } else {
252
- setTimeout(fn, 1);
253
- }
254
- }
255
527
  function getStyle(el, pseudo = null) {
256
528
  if (!(el instanceof Element)) {
257
529
  return window.getComputedStyle(el, pseudo);
@@ -278,124 +550,6 @@
278
550
  }
279
551
  return clean;
280
552
  }
281
- function extractURL(value) {
282
- const match = value.match(/url\((['"]?)(.*?)(\1)\)/);
283
- if (!match) return null;
284
- const url = match[2].trim();
285
- if (url.startsWith("#")) return null;
286
- return url;
287
- }
288
- function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
289
- function getCrossOriginMode(url) {
290
- try {
291
- const parsed = new URL(url, window.location.href);
292
- return parsed.origin === window.location.origin ? "use-credentials" : "anonymous";
293
- } catch (e) {
294
- return "anonymous";
295
- }
296
- }
297
- function fetchWithFallback(url) {
298
- return __async(this, null, function* () {
299
- const fetchBlobAsDataURL = (fetchUrl) => fetch(fetchUrl, {
300
- mode: "cors",
301
- credentials: getCrossOriginMode(fetchUrl) === "use-credentials" ? "include" : "omit"
302
- }).then((r) => r.blob()).then((blob) => new Promise((resolve, reject) => {
303
- const reader = new FileReader();
304
- reader.onloadend = () => {
305
- const base64 = reader.result;
306
- if (typeof base64 !== "string" || !base64.startsWith("data:image/")) {
307
- reject(new Error("Invalid image data URL"));
308
- return;
309
- }
310
- resolve(base64);
311
- };
312
- reader.onerror = () => reject(new Error("FileReader error"));
313
- reader.readAsDataURL(blob);
314
- }));
315
- try {
316
- return yield fetchBlobAsDataURL(url);
317
- } catch (e) {
318
- if (useProxy && typeof useProxy === "string") {
319
- const proxied = useProxy.replace(/\/$/, "") + safeEncodeURI(url);
320
- try {
321
- return yield fetchBlobAsDataURL(proxied);
322
- } catch (e2) {
323
- throw new Error("[SnapDOM - fetchImage] CORS restrictions prevented image capture (even via proxy)");
324
- }
325
- } else {
326
- throw new Error("[SnapDOM - fetchImage] Fetch fallback failed and no proxy provided");
327
- }
328
- }
329
- });
330
- }
331
- const crossOriginValue = getCrossOriginMode(src);
332
- if (cache.image.has(src)) {
333
- return Promise.resolve(cache.image.get(src));
334
- }
335
- const isDataURI = src.startsWith("data:image/");
336
- if (isDataURI) {
337
- cache.image.set(src, src);
338
- return Promise.resolve(src);
339
- }
340
- const isSVG = /\.svg(\?.*)?$/i.test(src);
341
- if (isSVG) {
342
- return (() => __async(null, null, function* () {
343
- try {
344
- const response = yield fetch(src, {
345
- mode: "cors",
346
- credentials: crossOriginValue === "use-credentials" ? "include" : "omit"
347
- });
348
- const svgText = yield response.text();
349
- const encoded = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`;
350
- cache.image.set(src, encoded);
351
- return encoded;
352
- } catch (e) {
353
- return fetchWithFallback(src);
354
- }
355
- }))();
356
- }
357
- return new Promise((resolve, reject) => {
358
- const timeoutId = setTimeout(() => {
359
- reject(new Error("[SnapDOM - fetchImage] Image load timed out"));
360
- }, timeout);
361
- const image = new Image();
362
- image.crossOrigin = crossOriginValue;
363
- image.onload = () => __async(null, null, function* () {
364
- clearTimeout(timeoutId);
365
- try {
366
- yield image.decode();
367
- const canvas = document.createElement("canvas");
368
- canvas.width = image.width;
369
- canvas.height = image.height;
370
- const ctx = canvas.getContext("2d");
371
- ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
372
- const dataURL = canvas.toDataURL("image/png");
373
- cache.image.set(src, dataURL);
374
- resolve(dataURL);
375
- } catch (e) {
376
- try {
377
- const fallbackDataURL = yield fetchWithFallback(src);
378
- cache.image.set(src, fallbackDataURL);
379
- resolve(fallbackDataURL);
380
- } catch (e2) {
381
- reject(e2);
382
- }
383
- }
384
- });
385
- image.onerror = () => __async(null, null, function* () {
386
- clearTimeout(timeoutId);
387
- console.error(`[SnapDOM - fetchImage] Image failed to load: ${src}`);
388
- try {
389
- const fallbackDataURL = yield fetchWithFallback(src);
390
- cache.image.set(src, fallbackDataURL);
391
- resolve(fallbackDataURL);
392
- } catch (e) {
393
- reject(e);
394
- }
395
- });
396
- image.src = src;
397
- });
398
- }
399
553
  function snapshotComputedStyle(style) {
400
554
  const snap = {};
401
555
  for (let prop of style) {
@@ -403,36 +557,6 @@
403
557
  }
404
558
  return snap;
405
559
  }
406
- function isSafari() {
407
- return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
408
- }
409
- function stripTranslate(transform) {
410
- if (!transform || transform === "none") return "";
411
- let cleaned = transform.replace(/translate[XY]?\([^)]*\)/g, "");
412
- cleaned = cleaned.replace(/matrix\(([^)]+)\)/g, (_, values) => {
413
- const parts = values.split(",").map((s) => s.trim());
414
- if (parts.length !== 6) return `matrix(${values})`;
415
- parts[4] = "0";
416
- parts[5] = "0";
417
- return `matrix(${parts.join(", ")})`;
418
- });
419
- cleaned = cleaned.replace(/matrix3d\(([^)]+)\)/g, (_, values) => {
420
- const parts = values.split(",").map((s) => s.trim());
421
- if (parts.length !== 16) return `matrix3d(${values})`;
422
- parts[12] = "0";
423
- parts[13] = "0";
424
- return `matrix3d(${parts.join(", ")})`;
425
- });
426
- return cleaned.trim().replace(/\s{2,}/g, " ");
427
- }
428
- function safeEncodeURI(uri) {
429
- if (/%[0-9A-Fa-f]{2}/.test(uri)) return uri;
430
- try {
431
- return encodeURI(uri);
432
- } catch (e) {
433
- return uri;
434
- }
435
- }
436
560
  function splitBackgroundImage(bg) {
437
561
  const parts = [];
438
562
  let depth = 0;
@@ -450,52 +574,277 @@
450
574
  return parts;
451
575
  }
452
576
 
577
+ // src/utils/browser.js
578
+ function idle(fn, { fast = false } = {}) {
579
+ if (fast) return fn();
580
+ if ("requestIdleCallback" in window) {
581
+ requestIdleCallback(fn, { timeout: 50 });
582
+ } else {
583
+ setTimeout(fn, 1);
584
+ }
585
+ }
586
+ function isSafari() {
587
+ return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
588
+ }
589
+
453
590
  // src/modules/styles.js
591
+ var snapshotCache = /* @__PURE__ */ new WeakMap();
592
+ var snapshotKeyCache = /* @__PURE__ */ new Map();
593
+ var __epoch = 0;
594
+ function bumpEpoch() {
595
+ __epoch++;
596
+ }
597
+ var __wired = false;
598
+ function setupInvalidationOnce(root = document.documentElement) {
599
+ var _a, _b;
600
+ if (__wired) return;
601
+ __wired = true;
602
+ try {
603
+ const domObs = new MutationObserver(() => bumpEpoch());
604
+ domObs.observe(root, { subtree: true, childList: true, characterData: true, attributes: true });
605
+ } catch (e) {
606
+ }
607
+ try {
608
+ const headObs = new MutationObserver(() => bumpEpoch());
609
+ headObs.observe(document.head, { subtree: true, childList: true, characterData: true, attributes: true });
610
+ } catch (e) {
611
+ }
612
+ try {
613
+ const f = document.fonts;
614
+ if (f) {
615
+ (_a = f.addEventListener) == null ? void 0 : _a.call(f, "loadingdone", bumpEpoch);
616
+ (_b = f.ready) == null ? void 0 : _b.then(() => bumpEpoch()).catch(() => {
617
+ });
618
+ }
619
+ } catch (e) {
620
+ }
621
+ }
454
622
  function snapshotComputedStyleFull(style) {
455
- const result = {};
456
- const computedVisibility = style.getPropertyValue("visibility");
623
+ const out = {};
624
+ const vis = style.getPropertyValue("visibility");
457
625
  for (let i = 0; i < style.length; i++) {
458
626
  const prop = style[i];
459
627
  let val = style.getPropertyValue(prop);
460
628
  if ((prop === "background-image" || prop === "content") && val.includes("url(") && !val.includes("data:")) {
461
629
  val = "none";
462
630
  }
463
- result[prop] = val;
631
+ out[prop] = val;
464
632
  }
465
- if (computedVisibility === "hidden") {
466
- result.opacity = "0";
633
+ if (vis === "hidden") out.opacity = "0";
634
+ return out;
635
+ }
636
+ var __snapshotSig = /* @__PURE__ */ new WeakMap();
637
+ function styleSignature(snap) {
638
+ let sig = __snapshotSig.get(snap);
639
+ if (sig) return sig;
640
+ const entries = Object.entries(snap).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
641
+ sig = entries.map(([k, v]) => `${k}:${v}`).join(";");
642
+ __snapshotSig.set(snap, sig);
643
+ return sig;
644
+ }
645
+ function getSnapshot(el, preStyle = null) {
646
+ const rec = snapshotCache.get(el);
647
+ if (rec && rec.epoch === __epoch) return rec.snapshot;
648
+ const style = preStyle || getComputedStyle(el);
649
+ const snap = snapshotComputedStyleFull(style);
650
+ snapshotCache.set(el, { epoch: __epoch, snapshot: snap });
651
+ return snap;
652
+ }
653
+ function _resolveCtx(sessionOrCtx, opts) {
654
+ if (sessionOrCtx && sessionOrCtx.session && sessionOrCtx.persist) return sessionOrCtx;
655
+ if (sessionOrCtx && (sessionOrCtx.styleMap || sessionOrCtx.styleCache || sessionOrCtx.nodeMap)) {
656
+ return {
657
+ session: sessionOrCtx,
658
+ persist: {
659
+ snapshotKeyCache,
660
+ defaultStyle: cache.defaultStyle,
661
+ baseStyle: cache.baseStyle,
662
+ image: cache.image,
663
+ resource: cache.resource,
664
+ background: cache.background,
665
+ font: cache.font
666
+ },
667
+ options: opts || {}
668
+ };
467
669
  }
468
- return result;
670
+ return {
671
+ session: cache.session,
672
+ persist: {
673
+ snapshotKeyCache,
674
+ defaultStyle: cache.defaultStyle,
675
+ baseStyle: cache.baseStyle,
676
+ image: cache.image,
677
+ resource: cache.resource,
678
+ background: cache.background,
679
+ font: cache.font
680
+ },
681
+ options: sessionOrCtx || opts || {}
682
+ };
469
683
  }
470
- function inlineAllStyles(source, clone, compress) {
471
- var _a;
472
- if (source.tagName === "STYLE") return;
473
- if (!cache.preStyle.has(source)) {
474
- cache.preStyle.set(source, getStyle(source));
475
- }
476
- const style = cache.preStyle.get(source);
477
- if (!cache.snapshot.has(source)) {
478
- const snapshot2 = snapshotComputedStyleFull(style);
479
- cache.snapshot.set(source, snapshot2);
480
- }
481
- const snapshot = cache.snapshot.get(source);
482
- const hash = Object.entries(snapshot).sort(([a], [b]) => a.localeCompare(b)).map(([prop, val]) => `${prop}:${val}`).join(";");
483
- if (cache.snapshotKey.has(hash)) {
484
- cache.preStyleMap.set(clone, cache.snapshotKey.get(hash));
485
- return;
486
- }
487
- const tagName = ((_a = source.tagName) == null ? void 0 : _a.toLowerCase()) || "div";
488
- const key = getStyleKey(snapshot, tagName, compress);
489
- cache.snapshotKey.set(hash, key);
490
- cache.preStyleMap.set(clone, key);
684
+ function inlineAllStyles(source, clone, sessionOrCtx, opts) {
685
+ return __async(this, null, function* () {
686
+ var _a, _b, _c;
687
+ if (source.tagName === "STYLE") return;
688
+ const ctx = _resolveCtx(sessionOrCtx, opts);
689
+ const resetMode = ctx.options && ctx.options.cache || "auto";
690
+ if (resetMode !== "disabled") setupInvalidationOnce(document.documentElement);
691
+ if (NO_DEFAULTS_TAGS.has((_a = source.tagName) == null ? void 0 : _a.toLowerCase())) {
692
+ const author = (_b = source.getAttribute) == null ? void 0 : _b.call(source, "style");
693
+ if (author) clone.setAttribute("style", author);
694
+ }
695
+ const { session, persist } = ctx;
696
+ if (!session.styleCache.has(source)) {
697
+ session.styleCache.set(source, getComputedStyle(source));
698
+ }
699
+ const pre = session.styleCache.get(source);
700
+ const snap = getSnapshot(source, pre);
701
+ const sig = styleSignature(snap);
702
+ let key = persist.snapshotKeyCache.get(sig);
703
+ if (!key) {
704
+ const tag = ((_c = source.tagName) == null ? void 0 : _c.toLowerCase()) || "div";
705
+ key = getStyleKey(snap, tag);
706
+ persist.snapshotKeyCache.set(sig, key);
707
+ }
708
+ session.styleMap.set(clone, key);
709
+ });
491
710
  }
492
711
 
493
712
  // src/core/clone.js
494
- function deepClone(node, compress, options = {}, originalRoot) {
495
- var _a, _b;
713
+ function addNotSlottedRightmost(sel) {
714
+ sel = sel.trim();
715
+ if (!sel) return sel;
716
+ if (/\:not\(\s*\[data-sd-slotted\]\s*\)\s*$/.test(sel)) return sel;
717
+ return `${sel}:not([data-sd-slotted])`;
718
+ }
719
+ function wrapWithScope(selectorList, scopeSelector, excludeSlotted = true) {
720
+ return selectorList.split(",").map((s) => s.trim()).filter(Boolean).map((s) => {
721
+ if (s.startsWith(":where(")) return s;
722
+ if (s.startsWith("@")) return s;
723
+ const body = excludeSlotted ? addNotSlottedRightmost(s) : s;
724
+ return `:where(${scopeSelector} ${body})`;
725
+ }).join(", ");
726
+ }
727
+ function rewriteShadowCSS(cssText, scopeSelector) {
728
+ if (!cssText) return "";
729
+ cssText = cssText.replace(/:host\(([^)]+)\)/g, (_, sel) => {
730
+ return `:where(${scopeSelector}:is(${sel.trim()}))`;
731
+ });
732
+ cssText = cssText.replace(/:host\b/g, `:where(${scopeSelector})`);
733
+ cssText = cssText.replace(/:host-context\(([^)]+)\)/g, (_, sel) => {
734
+ return `:where(:where(${sel.trim()}) ${scopeSelector})`;
735
+ });
736
+ cssText = cssText.replace(/::slotted\(([^)]+)\)/g, (_, sel) => {
737
+ return `:where(${scopeSelector} ${sel.trim()})`;
738
+ });
739
+ cssText = cssText.replace(/(^|})(\s*)([^@}{]+){/g, (_, brace, ws, selectorList) => {
740
+ const wrapped = wrapWithScope(
741
+ selectorList,
742
+ scopeSelector,
743
+ /*excludeSlotted*/
744
+ true
745
+ );
746
+ return `${brace}${ws}${wrapped}{`;
747
+ });
748
+ return cssText;
749
+ }
750
+ function nextShadowScopeId(sessionCache) {
751
+ sessionCache.shadowScopeSeq = (sessionCache.shadowScopeSeq || 0) + 1;
752
+ return `s${sessionCache.shadowScopeSeq}`;
753
+ }
754
+ function extractShadowCSS(sr) {
755
+ let css = "";
756
+ try {
757
+ sr.querySelectorAll("style").forEach((s) => {
758
+ css += (s.textContent || "") + "\n";
759
+ });
760
+ const sheets = sr.adoptedStyleSheets || [];
761
+ for (const sh of sheets) {
762
+ try {
763
+ if (sh && sh.cssRules) {
764
+ for (const rule of sh.cssRules) css += rule.cssText + "\n";
765
+ }
766
+ } catch (e) {
767
+ }
768
+ }
769
+ } catch (e) {
770
+ }
771
+ return css;
772
+ }
773
+ function injectScopedStyle(hostClone, cssText, scopeId) {
774
+ if (!cssText) return;
775
+ const style = document.createElement("style");
776
+ style.setAttribute("data-sd", scopeId);
777
+ style.textContent = cssText;
778
+ hostClone.insertBefore(style, hostClone.firstChild || null);
779
+ }
780
+ function freezeImgSrcset(original, cloned) {
781
+ try {
782
+ const chosen = original.currentSrc || original.src || "";
783
+ if (!chosen) return;
784
+ cloned.setAttribute("src", chosen);
785
+ cloned.removeAttribute("srcset");
786
+ cloned.removeAttribute("sizes");
787
+ cloned.loading = "eager";
788
+ cloned.decoding = "sync";
789
+ } catch (e) {
790
+ }
791
+ }
792
+ function collectCustomPropsFromCSS(cssText) {
793
+ const out = /* @__PURE__ */ new Set();
794
+ if (!cssText) return out;
795
+ const re = /var\(\s*(--[A-Za-z0-9_-]+)\b/g;
796
+ let m;
797
+ while (m = re.exec(cssText)) out.add(m[1]);
798
+ return out;
799
+ }
800
+ function resolveCustomProp(el, name) {
801
+ try {
802
+ const cs = getComputedStyle(el);
803
+ let v = cs.getPropertyValue(name).trim();
804
+ if (v) return v;
805
+ } catch (e) {
806
+ }
807
+ try {
808
+ const rootCS = getComputedStyle(document.documentElement);
809
+ let v = rootCS.getPropertyValue(name).trim();
810
+ if (v) return v;
811
+ } catch (e) {
812
+ }
813
+ return "";
814
+ }
815
+ function buildSeedCustomPropsRule(hostEl, names, scopeSelector) {
816
+ const decls = [];
817
+ for (const name of names) {
818
+ const val = resolveCustomProp(hostEl, name);
819
+ if (val) decls.push(`${name}: ${val};`);
820
+ }
821
+ if (!decls.length) return "";
822
+ return `${scopeSelector}{${decls.join("")}}
823
+ `;
824
+ }
825
+ function markSlottedSubtree(root) {
826
+ if (!root) return;
827
+ if (root.nodeType === Node.ELEMENT_NODE) {
828
+ root.setAttribute("data-sd-slotted", "");
829
+ }
830
+ if (root.querySelectorAll) {
831
+ root.querySelectorAll("*").forEach((el) => el.setAttribute("data-sd-slotted", ""));
832
+ }
833
+ }
834
+ function deepClone(node, sessionCache, options) {
835
+ var _a, _b, _c, _d, _e;
496
836
  if (!node) throw new Error("Invalid node");
497
837
  const clonedAssignedNodes = /* @__PURE__ */ new Set();
498
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;
843
+ }
844
+ if (NO_CAPTURE_TAGS.has(tag)) {
845
+ return null;
846
+ }
847
+ }
499
848
  if (node.nodeType === Node.TEXT_NODE) {
500
849
  return node.cloneNode(true);
501
850
  }
@@ -524,7 +873,7 @@
524
873
  }
525
874
  if (typeof options.filter === "function") {
526
875
  try {
527
- if (!options.filter(node, originalRoot || node)) {
876
+ if (!options.filter(node)) {
528
877
  const spacer = document.createElement("div");
529
878
  const rect = node.getBoundingClientRect();
530
879
  spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
@@ -541,8 +890,8 @@
541
890
  }
542
891
  if (node.getAttribute("data-capture") === "placeholder") {
543
892
  const clone2 = node.cloneNode(false);
544
- cache.preNodeMap.set(clone2, node);
545
- inlineAllStyles(node, clone2, compress);
893
+ sessionCache.nodeMap.set(clone2, node);
894
+ inlineAllStyles(node, clone2, sessionCache, options);
546
895
  const placeholder = document.createElement("div");
547
896
  placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
548
897
  placeholder.style.cssText = `color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;`;
@@ -555,14 +904,17 @@
555
904
  img.src = dataURL;
556
905
  img.width = node.width;
557
906
  img.height = node.height;
558
- cache.preNodeMap.set(img, node);
559
- inlineAllStyles(node, img, compress);
907
+ sessionCache.nodeMap.set(img, node);
908
+ inlineAllStyles(node, img, sessionCache, options);
560
909
  return img;
561
910
  }
562
911
  let clone;
563
912
  try {
564
913
  clone = node.cloneNode(false);
565
- cache.preNodeMap.set(clone, node);
914
+ sessionCache.nodeMap.set(clone, node);
915
+ if (node.tagName === "IMG") {
916
+ freezeImgSrcset(node, clone);
917
+ }
566
918
  } catch (err) {
567
919
  console.error("[Snapdom] Failed to clone node:", node, err);
568
920
  throw err;
@@ -587,49 +939,56 @@
587
939
  if (node instanceof HTMLSelectElement) {
588
940
  pendingSelectValue = node.value;
589
941
  }
590
- inlineAllStyles(node, clone, compress);
942
+ inlineAllStyles(node, clone, sessionCache, options);
591
943
  if (node.shadowRoot) {
592
- const hasSlot = Array.from(node.shadowRoot.querySelectorAll("slot")).length > 0;
593
- if (hasSlot) {
594
- for (const child of node.shadowRoot.childNodes) {
595
- if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
596
- const cssText = child.textContent || "";
597
- if (cssText.trim() && compress) {
598
- if (!cache.preStyle) cache.preStyle = /* @__PURE__ */ new WeakMap();
599
- cache.preStyle.set(child, cssText);
600
- }
601
- }
602
- }
603
- } else {
604
- const shadowFrag = document.createDocumentFragment();
605
- for (const child of node.shadowRoot.childNodes) {
606
- if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
607
- const cssText = child.textContent || "";
608
- if (cssText.trim() && compress) {
609
- if (!cache.preStyle) cache.preStyle = /* @__PURE__ */ new WeakMap();
610
- cache.preStyle.set(child, cssText);
611
- }
612
- continue;
944
+ try {
945
+ const slots = node.shadowRoot.querySelectorAll("slot");
946
+ for (const s of slots) {
947
+ let assigned = [];
948
+ try {
949
+ assigned = ((_b = s.assignedNodes) == null ? void 0 : _b.call(s, { flatten: true })) || ((_c = s.assignedNodes) == null ? void 0 : _c.call(s)) || [];
950
+ } catch (e) {
951
+ assigned = ((_d = s.assignedNodes) == null ? void 0 : _d.call(s)) || [];
613
952
  }
614
- const clonedChild = deepClone(child, compress, options, originalRoot || node);
615
- if (clonedChild) shadowFrag.appendChild(clonedChild);
953
+ for (const an of assigned) clonedAssignedNodes.add(an);
616
954
  }
617
- clone.appendChild(shadowFrag);
955
+ } catch (e) {
956
+ }
957
+ const scopeId = nextShadowScopeId(sessionCache);
958
+ const scopeSelector = `[data-sd="${scopeId}"]`;
959
+ try {
960
+ clone.setAttribute("data-sd", scopeId);
961
+ } catch (e) {
618
962
  }
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);
619
975
  }
620
976
  if (node.tagName === "SLOT") {
621
- const assigned = ((_b = node.assignedNodes) == null ? void 0 : _b.call(node, { flatten: true })) || [];
977
+ const assigned = ((_e = node.assignedNodes) == null ? void 0 : _e.call(node, { flatten: true })) || [];
622
978
  const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
623
979
  const fragment = document.createDocumentFragment();
624
980
  for (const child of nodesToClone) {
625
- const clonedChild = deepClone(child, compress, options, originalRoot || node);
626
- if (clonedChild) fragment.appendChild(clonedChild);
981
+ const clonedChild = deepClone(child, sessionCache, options);
982
+ if (clonedChild) {
983
+ markSlottedSubtree(clonedChild);
984
+ fragment.appendChild(clonedChild);
985
+ }
627
986
  }
628
987
  return fragment;
629
988
  }
630
989
  for (const child of node.childNodes) {
631
990
  if (clonedAssignedNodes.has(child)) continue;
632
- const clonedChild = deepClone(child, compress, options, originalRoot || node);
991
+ const clonedChild = deepClone(child, sessionCache, options);
633
992
  if (clonedChild) clone.appendChild(clonedChild);
634
993
  }
635
994
  if (pendingSelectValue !== null && clone instanceof HTMLSelectElement) {
@@ -672,7 +1031,7 @@
672
1031
  }
673
1032
  }
674
1033
  }
675
- function isIconFont(input) {
1034
+ function isIconFont2(input) {
676
1035
  const text = typeof input === "string" ? input : "";
677
1036
  const candidates = [...defaultIconFonts, ...userIconFonts];
678
1037
  for (const rx of candidates) {
@@ -721,192 +1080,630 @@
721
1080
  };
722
1081
  });
723
1082
  }
724
- function isStylesheetLoaded(href) {
725
- return Array.from(document.styleSheets).some((sheet) => sheet.href === href);
726
- }
727
- function injectLinkIfMissing(href) {
728
- return new Promise((resolve) => {
729
- if (isStylesheetLoaded(href)) return resolve(null);
730
- const link = document.createElement("link");
731
- link.rel = "stylesheet";
732
- link.href = href;
733
- link.setAttribute("data-snapdom", "injected-import");
734
- link.onload = () => resolve(link);
735
- link.onerror = () => resolve(null);
736
- document.head.appendChild(link);
737
- });
1083
+ var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
1084
+ "serif",
1085
+ "sans-serif",
1086
+ "monospace",
1087
+ "cursive",
1088
+ "fantasy",
1089
+ "system-ui",
1090
+ "emoji",
1091
+ "math",
1092
+ "fangsong",
1093
+ "ui-serif",
1094
+ "ui-sans-serif",
1095
+ "ui-monospace",
1096
+ "ui-rounded"
1097
+ ]);
1098
+ function pickPrimaryFamily(familyList) {
1099
+ if (!familyList) return "";
1100
+ for (let raw of familyList.split(",")) {
1101
+ let f = raw.trim().replace(/^['"]+|['"]+$/g, "");
1102
+ if (!f) continue;
1103
+ if (!GENERIC_FAMILIES.has(f.toLowerCase())) return f;
1104
+ }
1105
+ return "";
1106
+ }
1107
+ function normWeight(w) {
1108
+ const t = String(w != null ? w : "400").trim().toLowerCase();
1109
+ if (t === "normal") return 400;
1110
+ if (t === "bold") return 700;
1111
+ const n = parseInt(t, 10);
1112
+ return Number.isFinite(n) ? Math.min(900, Math.max(100, n)) : 400;
1113
+ }
1114
+ function normStyle(s) {
1115
+ const t = String(s != null ? s : "normal").trim().toLowerCase();
1116
+ if (t.startsWith("italic")) return "italic";
1117
+ if (t.startsWith("oblique")) return "oblique";
1118
+ return "normal";
1119
+ }
1120
+ function normStretchPct(st) {
1121
+ const m = String(st != null ? st : "100%").match(/(\d+(?:\.\d+)?)\s*%/);
1122
+ return m ? Math.max(50, Math.min(200, parseFloat(m[1]))) : 100;
1123
+ }
1124
+ function parseWeightSpec(spec) {
1125
+ const s = String(spec || "400").trim();
1126
+ const m = s.match(/^(\d{2,3})\s+(\d{2,3})$/);
1127
+ if (m) {
1128
+ const a = normWeight(m[1]), b = normWeight(m[2]);
1129
+ return { min: Math.min(a, b), max: Math.max(a, b) };
1130
+ }
1131
+ const v = normWeight(s);
1132
+ return { min: v, max: v };
1133
+ }
1134
+ function parseStyleSpec(spec) {
1135
+ const t = String(spec || "normal").trim().toLowerCase();
1136
+ if (t === "italic") return { kind: "italic" };
1137
+ if (t.startsWith("oblique")) return { kind: "oblique" };
1138
+ return { kind: "normal" };
1139
+ }
1140
+ function parseStretchSpec(spec) {
1141
+ const s = String(spec || "100%").trim();
1142
+ const mm = s.match(/(\d+(?:\.\d+)?)\s*%\s+(\d+(?:\.\d+)?)\s*%/);
1143
+ if (mm) {
1144
+ const a = parseFloat(mm[1]), b = parseFloat(mm[2]);
1145
+ return { min: Math.min(a, b), max: Math.max(a, b) };
1146
+ }
1147
+ const m = s.match(/(\d+(?:\.\d+)?)\s*%/);
1148
+ const v = m ? parseFloat(m[1]) : 100;
1149
+ return { min: v, max: v };
1150
+ }
1151
+ function isLikelyFontStylesheet(href, requiredFamilies) {
1152
+ if (!href) return false;
1153
+ try {
1154
+ const u = new URL(href, location.href);
1155
+ const sameOrigin = u.origin === location.origin;
1156
+ if (sameOrigin) return true;
1157
+ const host = u.host.toLowerCase();
1158
+ const FONT_HOSTS = [
1159
+ "fonts.googleapis.com",
1160
+ "fonts.gstatic.com",
1161
+ "use.typekit.net",
1162
+ "p.typekit.net",
1163
+ "kit.fontawesome.com",
1164
+ "use.fontawesome.com"
1165
+ ];
1166
+ if (FONT_HOSTS.some((h) => host.endsWith(h))) return true;
1167
+ const path = (u.pathname + u.search).toLowerCase();
1168
+ if (/\bfont(s)?\b/.test(path) || /\.woff2?(\b|$)/.test(path)) return true;
1169
+ for (const fam of requiredFamilies) {
1170
+ const tokenA = fam.toLowerCase().replace(/\s+/g, "+");
1171
+ const tokenB = fam.toLowerCase().replace(/\s+/g, "-");
1172
+ if (path.includes(tokenA) || path.includes(tokenB)) return true;
1173
+ }
1174
+ return false;
1175
+ } catch (e) {
1176
+ return false;
1177
+ }
1178
+ }
1179
+ function familiesFromRequired(required) {
1180
+ var _a;
1181
+ const out = /* @__PURE__ */ new Set();
1182
+ for (const k of required || []) {
1183
+ const fam = (_a = String(k).split("__")[0]) == null ? void 0 : _a.trim();
1184
+ if (fam) out.add(fam);
1185
+ }
1186
+ return out;
738
1187
  }
739
1188
  function embedCustomFonts() {
740
- return __async(this, arguments, function* ({ preCached = false } = {}) {
741
- if (cache.resource.has("fonts-embed-css")) {
742
- if (preCached) {
743
- const style = document.createElement("style");
744
- style.setAttribute("data-snapdom", "embedFonts");
745
- style.textContent = cache.resource.get("fonts-embed-css");
746
- document.head.appendChild(style);
1189
+ return __async(this, arguments, function* ({
1190
+ required,
1191
+ usedCodepoints,
1192
+ exclude = void 0,
1193
+ localFonts = [],
1194
+ useProxy = ""
1195
+ } = {}) {
1196
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
1197
+ if (!(required instanceof Set)) required = /* @__PURE__ */ new Set();
1198
+ if (!(usedCodepoints instanceof Set)) usedCodepoints = /* @__PURE__ */ new Set();
1199
+ const requiredIndex = /* @__PURE__ */ new Map();
1200
+ for (const key of required) {
1201
+ const [fam, w, s, st] = String(key).split("__");
1202
+ if (!fam) continue;
1203
+ const arr = requiredIndex.get(fam) || [];
1204
+ arr.push({ w: parseInt(w, 10), s, st: parseInt(st, 10) });
1205
+ requiredIndex.set(fam, arr);
1206
+ }
1207
+ const IMPORT_RE = /@import\s+url\(["']?([^"')]+)["']?\)/g;
1208
+ const URL_RE = /url\((["']?)([^"')]+)\1\)/g;
1209
+ const FACE_RE = /@font-face[^{}]*\{[^}]*\}/g;
1210
+ function faceMatchesRequired(fam, styleSpec, weightSpec, stretchSpec) {
1211
+ if (!requiredIndex.has(fam)) return false;
1212
+ const need = requiredIndex.get(fam);
1213
+ const ws = parseWeightSpec(weightSpec);
1214
+ const ss = parseStyleSpec(styleSpec);
1215
+ const ts = parseStretchSpec(stretchSpec);
1216
+ const faceIsRange = ws.min !== ws.max;
1217
+ const faceSingleW = ws.min;
1218
+ const styleOK = (reqKind) => ss.kind === "normal" && reqKind === "normal" || ss.kind !== "normal" && (reqKind === "italic" || reqKind === "oblique");
1219
+ let exactMatched = false;
1220
+ for (const r of need) {
1221
+ const wOk = faceIsRange ? r.w >= ws.min && r.w <= ws.max : r.w === faceSingleW;
1222
+ const sOk = styleOK(normStyle(r.s));
1223
+ const tOk = r.st >= ts.min && r.st <= ts.max;
1224
+ if (wOk && sOk && tOk) {
1225
+ exactMatched = true;
1226
+ break;
1227
+ }
1228
+ }
1229
+ if (exactMatched) return true;
1230
+ if (!faceIsRange) {
1231
+ for (const r of need) {
1232
+ const sOk = styleOK(normStyle(r.s));
1233
+ const tOk = r.st >= ts.min && r.st <= ts.max;
1234
+ const nearWeight = Math.abs(faceSingleW - r.w) <= 300;
1235
+ if (nearWeight && sOk && tOk) {
1236
+ return true;
1237
+ }
1238
+ }
1239
+ }
1240
+ return false;
1241
+ }
1242
+ function parseUnicodeRange(ur) {
1243
+ if (!ur) return [];
1244
+ const ranges = [];
1245
+ const parts = ur.split(",").map((s) => s.trim()).filter(Boolean);
1246
+ for (const p of parts) {
1247
+ const m = p.match(/^U\+([0-9A-Fa-f?]+)(?:-([0-9A-Fa-f?]+))?$/);
1248
+ if (!m) continue;
1249
+ const a = m[1], b = m[2];
1250
+ const expand = (hex) => {
1251
+ if (!hex.includes("?")) return parseInt(hex, 16);
1252
+ const min = parseInt(hex.replace(/\?/g, "0"), 16);
1253
+ const max = parseInt(hex.replace(/\?/g, "F"), 16);
1254
+ return [min, max];
1255
+ };
1256
+ if (b) {
1257
+ const A = expand(a), B = expand(b);
1258
+ const min = Array.isArray(A) ? A[0] : A;
1259
+ const max = Array.isArray(B) ? B[1] : B;
1260
+ ranges.push([Math.min(min, max), Math.max(min, max)]);
1261
+ } else {
1262
+ const X = expand(a);
1263
+ if (Array.isArray(X)) ranges.push([X[0], X[1]]);
1264
+ else ranges.push([X, X]);
1265
+ }
1266
+ }
1267
+ return ranges;
1268
+ }
1269
+ function unicodeIntersects(used, ranges) {
1270
+ if (!ranges.length) return true;
1271
+ if (!used || used.size === 0) return true;
1272
+ for (const cp of used) {
1273
+ for (const [a, b] of ranges) if (cp >= a && cp <= b) return true;
1274
+ }
1275
+ return false;
1276
+ }
1277
+ function extractSrcUrls(srcValue, baseHref) {
1278
+ const urls = [];
1279
+ if (!srcValue) return urls;
1280
+ for (const m of srcValue.matchAll(URL_RE)) {
1281
+ let u = (m[2] || "").trim();
1282
+ if (!u || u.startsWith("data:")) continue;
1283
+ if (!/^https?:/i.test(u)) {
1284
+ try {
1285
+ u = new URL(u, baseHref || location.href).href;
1286
+ } catch (e) {
1287
+ }
1288
+ }
1289
+ urls.push(u);
1290
+ }
1291
+ return urls;
1292
+ }
1293
+ function inlineUrlsInCssBlock(cssBlock, baseHref) {
1294
+ return __async(this, null, function* () {
1295
+ var _a2, _b2, _c2, _d2, _e2;
1296
+ let out = cssBlock;
1297
+ for (const m of cssBlock.matchAll(URL_RE)) {
1298
+ const raw = extractURL(m[0]);
1299
+ if (!raw) continue;
1300
+ let abs = raw;
1301
+ if (!abs.startsWith("http") && !abs.startsWith("data:")) {
1302
+ try {
1303
+ abs = new URL(abs, baseHref || location.href).href;
1304
+ } catch (e) {
1305
+ }
1306
+ }
1307
+ if (isIconFont2(abs)) continue;
1308
+ if ((_a2 = cache.resource) == null ? void 0 : _a2.has(abs)) {
1309
+ (_b2 = cache.font) == null ? void 0 : _b2.add(abs);
1310
+ out = out.replace(m[0], `url(${cache.resource.get(abs)})`);
1311
+ continue;
1312
+ }
1313
+ if ((_c2 = cache.font) == null ? void 0 : _c2.has(abs)) continue;
1314
+ try {
1315
+ const r = yield snapFetch(abs, { as: "dataURL", useProxy, silent: true });
1316
+ if (r.ok && typeof r.data === "string") {
1317
+ const b64 = r.data;
1318
+ (_d2 = cache.resource) == null ? void 0 : _d2.set(abs, b64);
1319
+ (_e2 = cache.font) == null ? void 0 : _e2.add(abs);
1320
+ out = out.replace(m[0], `url(${b64})`);
1321
+ }
1322
+ } catch (e) {
1323
+ console.warn("[snapDOM] Failed to fetch font resource:", abs);
1324
+ }
1325
+ }
1326
+ return out;
1327
+ });
1328
+ }
1329
+ function subsetFromRanges(ranges) {
1330
+ if (!ranges.length) return null;
1331
+ const hit = (a, b) => ranges.some(([x, y]) => !(y < a || x > b));
1332
+ const latin = hit(0, 255) || hit(305, 305);
1333
+ const latinExt = hit(256, 591) || hit(7680, 7935);
1334
+ const greek = hit(880, 1023);
1335
+ const cyr = hit(1024, 1279);
1336
+ const viet = hit(7840, 7929) || hit(258, 259) || hit(416, 417) || hit(431, 432);
1337
+ if (viet) return "vietnamese";
1338
+ if (cyr) return "cyrillic";
1339
+ if (greek) return "greek";
1340
+ if (latinExt) return "latin-ext";
1341
+ if (latin) return "latin";
1342
+ return null;
1343
+ }
1344
+ function buildSimpleExcluder(ex = {}) {
1345
+ const famSet = new Set((ex.families || []).map((s) => String(s).toLowerCase()));
1346
+ const domSet = new Set((ex.domains || []).map((s) => String(s).toLowerCase()));
1347
+ const subSet = new Set((ex.subsets || []).map((s) => String(s).toLowerCase()));
1348
+ return (meta, parsedRanges) => {
1349
+ if (famSet.size && famSet.has(meta.family.toLowerCase())) return true;
1350
+ if (domSet.size) {
1351
+ for (const u of meta.srcUrls) {
1352
+ try {
1353
+ if (domSet.has(new URL(u).host.toLowerCase())) return true;
1354
+ } catch (e) {
1355
+ }
1356
+ }
1357
+ }
1358
+ if (subSet.size) {
1359
+ const label = subsetFromRanges(parsedRanges);
1360
+ if (label && subSet.has(label)) return true;
1361
+ }
1362
+ return false;
1363
+ };
1364
+ }
1365
+ const simpleExcluder = buildSimpleExcluder(exclude);
1366
+ function dedupeFontFaces(cssText) {
1367
+ var _a2, _b2, _c2, _d2, _e2, _f2;
1368
+ if (!cssText) return cssText;
1369
+ const FACE_RE2 = /@font-face[^{}]*\{[^}]*\}/gi;
1370
+ const seen = /* @__PURE__ */ new Set();
1371
+ const out = [];
1372
+ for (const block of cssText.match(FACE_RE2) || []) {
1373
+ const familyRaw = ((_a2 = block.match(/font-family:\s*([^;]+);/i)) == null ? void 0 : _a2[1]) || "";
1374
+ const family = pickPrimaryFamily(familyRaw);
1375
+ const weightSpec = (((_b2 = block.match(/font-weight:\s*([^;]+);/i)) == null ? void 0 : _b2[1]) || "400").trim();
1376
+ const styleSpec = (((_c2 = block.match(/font-style:\s*([^;]+);/i)) == null ? void 0 : _c2[1]) || "normal").trim();
1377
+ const stretchSpec = (((_d2 = block.match(/font-stretch:\s*([^;]+);/i)) == null ? void 0 : _d2[1]) || "100%").trim();
1378
+ const urange = (((_e2 = block.match(/unicode-range:\s*([^;]+);/i)) == null ? void 0 : _e2[1]) || "").trim();
1379
+ const srcRaw = (((_f2 = block.match(/src\s*:\s*([^;]+);/i)) == null ? void 0 : _f2[1]) || "").trim();
1380
+ const urls = extractSrcUrls(srcRaw, location.href);
1381
+ const srcPart = urls.length ? urls.map((u) => String(u).toLowerCase()).sort().join("|") : srcRaw.toLowerCase();
1382
+ const key = [
1383
+ String(family || "").toLowerCase(),
1384
+ weightSpec,
1385
+ styleSpec,
1386
+ stretchSpec,
1387
+ urange.toLowerCase(),
1388
+ srcPart
1389
+ ].join("|");
1390
+ if (!seen.has(key)) {
1391
+ seen.add(key);
1392
+ out.push(block);
1393
+ }
747
1394
  }
748
- return cache.resource.get("fonts-embed-css");
1395
+ if (out.length === 0) return cssText;
1396
+ let i = 0;
1397
+ return cssText.replace(FACE_RE2, () => out[i++] || "");
1398
+ }
1399
+ function buildFontsCacheKey() {
1400
+ const req = Array.from(required || []).sort().join("|");
1401
+ const ex = exclude ? JSON.stringify({
1402
+ families: (exclude.families || []).map((s) => String(s).toLowerCase()).sort(),
1403
+ domains: (exclude.domains || []).map((s) => String(s).toLowerCase()).sort(),
1404
+ subsets: (exclude.subsets || []).map((s) => String(s).toLowerCase()).sort()
1405
+ }) : "";
1406
+ const lf = (localFonts || []).map((f) => `${(f.family || "").toLowerCase()}::${f.weight || "normal"}::${f.style || "normal"}::${f.src || ""}`).sort().join("|");
1407
+ const px = useProxy || "";
1408
+ return `fonts-embed-css::req=${req}::ex=${ex}::lf=${lf}::px=${px}`;
1409
+ }
1410
+ const cacheKey = buildFontsCacheKey();
1411
+ if ((_a = cache.resource) == null ? void 0 : _a.has(cacheKey)) {
1412
+ const css = cache.resource.get(cacheKey);
1413
+ return css;
749
1414
  }
750
- const importRegex = /@import\s+url\(["']?([^"')]+)["']?\)/g;
751
- const styleImports = [];
1415
+ const requiredFamilies = familiesFromRequired(required);
1416
+ const importUrls = [];
752
1417
  for (const styleTag of document.querySelectorAll("style")) {
753
1418
  const cssText = styleTag.textContent || "";
754
- const matches = Array.from(cssText.matchAll(importRegex));
755
- for (const match of matches) {
756
- const importUrl = match[1];
757
- if (isIconFont(importUrl)) continue;
758
- if (!isStylesheetLoaded(importUrl)) {
759
- styleImports.push(importUrl);
760
- }
1419
+ for (const m of cssText.matchAll(IMPORT_RE)) {
1420
+ const u = m[1];
1421
+ if (!u || isIconFont2(u)) continue;
1422
+ const hasLink = !!document.querySelector(`link[rel="stylesheet"][href="${u}"]`);
1423
+ if (!hasLink) importUrls.push(u);
761
1424
  }
762
1425
  }
763
- yield Promise.all(styleImports.map(injectLinkIfMissing));
764
- const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter((link) => link.href);
1426
+ if (importUrls.length) {
1427
+ yield Promise.all(importUrls.map((u) => new Promise((resolve) => {
1428
+ if (document.querySelector(`link[rel="stylesheet"][href="${u}"]`)) return resolve(null);
1429
+ const link = document.createElement("link");
1430
+ link.rel = "stylesheet";
1431
+ link.href = u;
1432
+ link.setAttribute("data-snapdom", "injected-import");
1433
+ link.onload = () => resolve(link);
1434
+ link.onerror = () => resolve(null);
1435
+ document.head.appendChild(link);
1436
+ })));
1437
+ }
765
1438
  let finalCSS = "";
766
- for (const link of links) {
1439
+ const linkNodes = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter((l) => !!l.href);
1440
+ for (const link of linkNodes) {
767
1441
  try {
768
- const res = yield fetch(link.href);
769
- const cssText = yield res.text();
770
- if (isIconFont(link.href) || isIconFont(cssText)) continue;
771
- const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
772
- const inlinedCSS = yield Promise.all(
773
- Array.from(cssText.matchAll(urlRegex)).map((match) => __async(null, null, function* () {
774
- let rawUrl = extractURL(match[0]);
775
- if (!rawUrl) return null;
776
- let url = rawUrl;
777
- if (!url.startsWith("http") && !url.startsWith("data:")) {
778
- url = new URL(url, link.href).href;
779
- }
780
- if (isIconFont(url)) return null;
781
- if (cache.resource.has(url)) {
782
- cache.font.add(url);
783
- return { original: match[0], inlined: `url(${cache.resource.get(url)})` };
784
- }
785
- if (cache.font.has(url)) return null;
1442
+ if (isIconFont2(link.href)) continue;
1443
+ let cssText = "";
1444
+ let sameOrigin = false;
1445
+ try {
1446
+ sameOrigin = new URL(link.href, location.href).origin === location.origin;
1447
+ } catch (e) {
1448
+ }
1449
+ if (!sameOrigin) {
1450
+ if (!isLikelyFontStylesheet(link.href, requiredFamilies)) continue;
1451
+ }
1452
+ if (sameOrigin) {
1453
+ const sheet = Array.from(document.styleSheets).find((s) => s.href === link.href);
1454
+ if (sheet) {
786
1455
  try {
787
- const fontRes = yield fetch(url);
788
- const blob = yield fontRes.blob();
789
- const b64 = yield new Promise((resolve) => {
790
- const reader = new FileReader();
791
- reader.onload = () => resolve(reader.result);
792
- reader.readAsDataURL(blob);
793
- });
794
- cache.resource.set(url, b64);
795
- cache.font.add(url);
796
- return { original: match[0], inlined: `url(${b64})` };
1456
+ const rules = sheet.cssRules || [];
1457
+ cssText = Array.from(rules).map((r) => r.cssText).join("");
797
1458
  } catch (e) {
798
- console.warn("[snapdom] Failed to fetch font resource:", url);
799
- return null;
800
1459
  }
801
- }))
802
- );
803
- let cssFinal = cssText;
804
- for (const r of inlinedCSS) {
805
- if (r) cssFinal = cssFinal.replace(r.original, r.inlined);
1460
+ }
1461
+ }
1462
+ if (!cssText) {
1463
+ const res = yield snapFetch(link.href, { as: "text", useProxy });
1464
+ cssText = res.data;
1465
+ if (isIconFont2(link.href)) continue;
1466
+ }
1467
+ let facesOut = "";
1468
+ for (const face of cssText.match(FACE_RE) || []) {
1469
+ const famRaw = (((_b = face.match(/font-family:\s*([^;]+);/i)) == null ? void 0 : _b[1]) || "").trim();
1470
+ const family = pickPrimaryFamily(famRaw);
1471
+ if (!family || isIconFont2(family)) {
1472
+ continue;
1473
+ }
1474
+ const weightSpec = (((_c = face.match(/font-weight:\s*([^;]+);/i)) == null ? void 0 : _c[1]) || "400").trim();
1475
+ const styleSpec = (((_d = face.match(/font-style:\s*([^;]+);/i)) == null ? void 0 : _d[1]) || "normal").trim();
1476
+ const stretchSpec = (((_e = face.match(/font-stretch:\s*([^;]+);/i)) == null ? void 0 : _e[1]) || "100%").trim();
1477
+ const urange = (((_f = face.match(/unicode-range:\s*([^;]+);/i)) == null ? void 0 : _f[1]) || "").trim();
1478
+ const srcRaw = (((_g = face.match(/src\s*:\s*([^;]+);/i)) == null ? void 0 : _g[1]) || "").trim();
1479
+ const srcUrls = extractSrcUrls(srcRaw, link.href);
1480
+ if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) {
1481
+ continue;
1482
+ }
1483
+ const ranges = parseUnicodeRange(urange);
1484
+ if (!unicodeIntersects(usedCodepoints, ranges)) {
1485
+ continue;
1486
+ }
1487
+ const meta = { family, weightSpec, styleSpec, stretchSpec, unicodeRange: urange, srcRaw, srcUrls, href: link.href };
1488
+ if (exclude && simpleExcluder(meta, ranges)) {
1489
+ continue;
1490
+ }
1491
+ const newFace = /url\(/i.test(srcRaw) ? yield inlineUrlsInCssBlock(face, link.href) : face;
1492
+ facesOut += newFace + "";
806
1493
  }
807
- finalCSS += cssFinal + "\n";
1494
+ if (facesOut.trim()) finalCSS += facesOut + "";
808
1495
  } catch (e) {
809
- console.warn("[snapdom] Failed to fetch CSS:", link.href);
1496
+ console.warn("[snapdom] Failed to process stylesheet:", link.href);
810
1497
  }
811
1498
  }
812
1499
  for (const sheet of document.styleSheets) {
813
1500
  try {
814
- if (!sheet.href || links.every((link) => link.href !== sheet.href)) {
815
- for (const rule of sheet.cssRules) {
816
- if (rule.type === CSSRule.FONT_FACE_RULE) {
817
- const src = rule.style.getPropertyValue("src");
818
- const family = rule.style.getPropertyValue("font-family");
819
- if (!src || isIconFont(family)) continue;
820
- const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
821
- const localRegex = /local\((["']?)[^)]+?\1\)/g;
822
- const hasURL = !!src.match(urlRegex);
823
- const hasLocal = !!src.match(localRegex);
824
- if (!hasURL && hasLocal) {
825
- finalCSS += `@font-face{font-family:${family};src:${src};font-style:${rule.style.getPropertyValue("font-style") || "normal"};font-weight:${rule.style.getPropertyValue("font-weight") || "normal"};}`;
826
- continue;
827
- }
828
- let inlinedSrc = src;
829
- const matches = Array.from(src.matchAll(urlRegex));
830
- for (const match of matches) {
831
- let rawUrl = match[2].trim();
832
- if (!rawUrl) continue;
833
- let url = rawUrl;
834
- if (!url.startsWith("http") && !url.startsWith("data:")) {
835
- url = new URL(url, sheet.href || location.href).href;
836
- }
837
- if (isIconFont(url)) continue;
838
- if (cache.resource.has(url)) {
839
- cache.font.add(url);
840
- inlinedSrc = inlinedSrc.replace(match[0], `url(${cache.resource.get(url)})`);
841
- continue;
842
- }
843
- if (cache.font.has(url)) continue;
844
- try {
845
- const res = yield fetch(url);
846
- const blob = yield res.blob();
847
- const b64 = yield new Promise((resolve) => {
848
- const reader = new FileReader();
849
- reader.onload = () => resolve(reader.result);
850
- reader.readAsDataURL(blob);
851
- });
852
- cache.resource.set(url, b64);
853
- cache.font.add(url);
854
- inlinedSrc = inlinedSrc.replace(match[0], `url(${b64})`);
855
- } catch (e) {
856
- console.warn("[snapdom] Failed to fetch font URL:", url);
857
- }
858
- }
859
- finalCSS += `@font-face{font-family:${family};src:${inlinedSrc};font-style:${rule.style.getPropertyValue("font-style") || "normal"};font-weight:${rule.style.getPropertyValue("font-weight") || "normal"};}`;
860
- }
1501
+ if (sheet.href && linkNodes.some((l) => l.href === sheet.href)) continue;
1502
+ const rules = sheet.cssRules || [];
1503
+ for (const rule of rules) {
1504
+ if (rule.type !== CSSRule.FONT_FACE_RULE) continue;
1505
+ const famRaw = (rule.style.getPropertyValue("font-family") || "").trim();
1506
+ const family = pickPrimaryFamily(famRaw);
1507
+ if (!family || isIconFont2(family)) continue;
1508
+ const weightSpec = (rule.style.getPropertyValue("font-weight") || "400").trim();
1509
+ const styleSpec = (rule.style.getPropertyValue("font-style") || "normal").trim();
1510
+ const stretchSpec = (rule.style.getPropertyValue("font-stretch") || "100%").trim();
1511
+ const srcRaw = (rule.style.getPropertyValue("src") || "").trim();
1512
+ const urange = (rule.style.getPropertyValue("unicode-range") || "").trim();
1513
+ const srcUrls = extractSrcUrls(srcRaw, sheet.href || location.href);
1514
+ if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
1515
+ const ranges = parseUnicodeRange(urange);
1516
+ if (!unicodeIntersects(usedCodepoints, ranges)) continue;
1517
+ const meta = { family, weightSpec, styleSpec, stretchSpec, unicodeRange: urange, srcRaw, srcUrls, href: sheet.href || location.href };
1518
+ if (exclude && simpleExcluder(meta, ranges)) continue;
1519
+ if (/url\(/i.test(srcRaw)) {
1520
+ const inlinedSrc = yield inlineUrlsInCssBlock(srcRaw, sheet.href || location.href);
1521
+ finalCSS += `@font-face{font-family:${family};src:${inlinedSrc};font-style:${styleSpec};font-weight:${weightSpec};font-stretch:${stretchSpec};${urange ? `unicode-range:${urange};` : ""}}`;
1522
+ } else {
1523
+ finalCSS += `@font-face{font-family:${family};src:${srcRaw};font-style:${styleSpec};font-weight:${weightSpec};font-stretch:${stretchSpec};${urange ? `unicode-range:${urange};` : ""}}`;
861
1524
  }
862
1525
  }
863
1526
  } catch (e) {
864
- console.warn("[snapdom] Cannot access stylesheet", sheet.href, e);
865
1527
  }
866
1528
  }
867
- for (const font of document.fonts) {
868
- if (font.family && font.status === "loaded" && font._snapdomSrc) {
869
- if (isIconFont(font.family)) continue;
870
- let b64 = font._snapdomSrc;
871
- if (!b64.startsWith("data:")) {
872
- if (cache.resource.has(font._snapdomSrc)) {
873
- b64 = cache.resource.get(font._snapdomSrc);
874
- cache.font.add(font._snapdomSrc);
875
- } else if (!cache.font.has(font._snapdomSrc)) {
1529
+ try {
1530
+ for (const f of document.fonts || []) {
1531
+ if (!f || !f.family || f.status !== "loaded" || !f._snapdomSrc) continue;
1532
+ const fam = String(f.family).replace(/^['"]+|['"]+$/g, "");
1533
+ if (isIconFont2(fam)) continue;
1534
+ if (!requiredIndex.has(fam)) continue;
1535
+ if ((exclude == null ? void 0 : exclude.families) && exclude.families.some((n) => String(n).toLowerCase() === fam.toLowerCase())) {
1536
+ continue;
1537
+ }
1538
+ let b64 = f._snapdomSrc;
1539
+ if (!String(b64).startsWith("data:")) {
1540
+ if ((_h = cache.resource) == null ? void 0 : _h.has(f._snapdomSrc)) {
1541
+ b64 = cache.resource.get(f._snapdomSrc);
1542
+ (_i = cache.font) == null ? void 0 : _i.add(f._snapdomSrc);
1543
+ } else if (!((_j = cache.font) == null ? void 0 : _j.has(f._snapdomSrc))) {
876
1544
  try {
877
- const res = yield fetch(font._snapdomSrc);
878
- const blob = yield res.blob();
879
- b64 = yield new Promise((resolve) => {
880
- const reader = new FileReader();
881
- reader.onload = () => resolve(reader.result);
882
- reader.readAsDataURL(blob);
883
- });
884
- cache.resource.set(font._snapdomSrc, b64);
885
- cache.font.add(font._snapdomSrc);
1545
+ const r = yield snapFetch(f._snapdomSrc, { as: "dataURL", useProxy, silent: true });
1546
+ if (r.ok && typeof r.data === "string") {
1547
+ b64 = r.data;
1548
+ (_k = cache.resource) == null ? void 0 : _k.set(f._snapdomSrc, b64);
1549
+ (_l = cache.font) == null ? void 0 : _l.add(f._snapdomSrc);
1550
+ } else {
1551
+ continue;
1552
+ }
886
1553
  } catch (e) {
887
- console.warn("[snapdom] Failed to fetch dynamic font src:", font._snapdomSrc);
1554
+ console.warn("[snapdom] Failed to fetch dynamic font src:", f._snapdomSrc);
888
1555
  continue;
889
1556
  }
890
1557
  }
891
1558
  }
892
- finalCSS += `@font-face{font-family:'${font.family}';src:url(${b64});font-style:${font.style || "normal"};font-weight:${font.weight || "normal"};}`;
1559
+ finalCSS += `@font-face{font-family:'${fam}';src:url(${b64});font-style:${f.style || "normal"};font-weight:${f.weight || "normal"};}`;
893
1560
  }
1561
+ } catch (e) {
894
1562
  }
895
- if (finalCSS) {
896
- cache.resource.set("fonts-embed-css", finalCSS);
897
- if (preCached) {
898
- const style = document.createElement("style");
899
- style.setAttribute("data-snapdom", "embedFonts");
900
- style.textContent = finalCSS;
901
- document.head.appendChild(style);
1563
+ for (const font of localFonts) {
1564
+ if (!font || typeof font !== "object") continue;
1565
+ const family = String(font.family || "").replace(/^['"]+|['"]+$/g, "");
1566
+ if (!family || isIconFont2(family)) continue;
1567
+ if (!requiredIndex.has(family)) continue;
1568
+ if ((exclude == null ? void 0 : exclude.families) && exclude.families.some((n) => String(n).toLowerCase() === family.toLowerCase())) continue;
1569
+ const weight = font.weight != null ? String(font.weight) : "normal";
1570
+ const style = font.style != null ? String(font.style) : "normal";
1571
+ const stretch = font.stretchPct != null ? `${font.stretchPct}%` : "100%";
1572
+ const src = String(font.src || "");
1573
+ let b64 = src;
1574
+ if (!b64.startsWith("data:")) {
1575
+ if ((_m = cache.resource) == null ? void 0 : _m.has(src)) {
1576
+ b64 = cache.resource.get(src);
1577
+ (_n = cache.font) == null ? void 0 : _n.add(src);
1578
+ } else if (!((_o = cache.font) == null ? void 0 : _o.has(src))) {
1579
+ try {
1580
+ const r = yield snapFetch(src, { as: "dataURL", useProxy, silent: true });
1581
+ if (r.ok && typeof r.data === "string") {
1582
+ b64 = r.data;
1583
+ (_p = cache.resource) == null ? void 0 : _p.set(src, b64);
1584
+ (_q = cache.font) == null ? void 0 : _q.add(src);
1585
+ } else {
1586
+ continue;
1587
+ }
1588
+ } catch (e) {
1589
+ console.warn("[snapdom] Failed to fetch localFonts src:", src);
1590
+ continue;
1591
+ }
1592
+ }
902
1593
  }
1594
+ finalCSS += `@font-face{font-family:'${family}';src:url(${b64});font-style:${style};font-weight:${weight};font-stretch:${stretch};}`;
1595
+ }
1596
+ if (finalCSS) {
1597
+ finalCSS = dedupeFontFaces(finalCSS);
1598
+ (_r = cache.resource) == null ? void 0 : _r.set(cacheKey, finalCSS);
903
1599
  }
904
1600
  return finalCSS;
905
1601
  });
906
1602
  }
1603
+ function collectUsedFontVariants(root) {
1604
+ const req = /* @__PURE__ */ new Set();
1605
+ if (!root) return req;
1606
+ const tw = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null);
1607
+ const addFromStyle = (cs) => {
1608
+ const family = pickPrimaryFamily(cs.fontFamily);
1609
+ if (!family) return;
1610
+ const key = (w, s, st) => `${family}__${normWeight(w)}__${normStyle(s)}__${normStretchPct(st)}`;
1611
+ req.add(key(cs.fontWeight, cs.fontStyle, cs.fontStretch));
1612
+ };
1613
+ addFromStyle(getComputedStyle(root));
1614
+ const csBeforeRoot = getComputedStyle(root, "::before");
1615
+ if (csBeforeRoot && csBeforeRoot.content && csBeforeRoot.content !== "none") addFromStyle(csBeforeRoot);
1616
+ const csAfterRoot = getComputedStyle(root, "::after");
1617
+ if (csAfterRoot && csAfterRoot.content && csAfterRoot.content !== "none") addFromStyle(csAfterRoot);
1618
+ while (tw.nextNode()) {
1619
+ const el = (
1620
+ /** @type {Element} */
1621
+ tw.currentNode
1622
+ );
1623
+ const cs = getComputedStyle(el);
1624
+ addFromStyle(cs);
1625
+ const b = getComputedStyle(el, "::before");
1626
+ if (b && b.content && b.content !== "none") addFromStyle(b);
1627
+ const a = getComputedStyle(el, "::after");
1628
+ if (a && a.content && a.content !== "none") addFromStyle(a);
1629
+ }
1630
+ return req;
1631
+ }
1632
+ function collectUsedCodepoints(root) {
1633
+ const used = /* @__PURE__ */ new Set();
1634
+ const pushText = (txt) => {
1635
+ if (!txt) return;
1636
+ for (const ch of txt) used.add(ch.codePointAt(0));
1637
+ };
1638
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null);
1639
+ while (walker.nextNode()) {
1640
+ const n = walker.currentNode;
1641
+ if (n.nodeType === Node.TEXT_NODE) {
1642
+ pushText(n.nodeValue || "");
1643
+ } else if (n.nodeType === Node.ELEMENT_NODE) {
1644
+ const el = (
1645
+ /** @type {Element} */
1646
+ n
1647
+ );
1648
+ for (const pseudo of ["::before", "::after"]) {
1649
+ const cs = getComputedStyle(el, pseudo);
1650
+ const c = cs == null ? void 0 : cs.getPropertyValue("content");
1651
+ if (!c || c === "none") continue;
1652
+ if (/^"/.test(c) || /^'/.test(c)) {
1653
+ pushText(c.slice(1, -1));
1654
+ } else {
1655
+ const matches = c.match(/\\[0-9A-Fa-f]{1,6}/g);
1656
+ if (matches) {
1657
+ for (const m of matches) {
1658
+ try {
1659
+ used.add(parseInt(m.slice(1), 16));
1660
+ } catch (e) {
1661
+ }
1662
+ }
1663
+ }
1664
+ }
1665
+ }
1666
+ }
1667
+ }
1668
+ return used;
1669
+ }
1670
+ function ensureFontsReady(families, warmupRepetitions = 1) {
1671
+ return __async(this, null, function* () {
1672
+ try {
1673
+ yield document.fonts.ready;
1674
+ } catch (e) {
1675
+ }
1676
+ const fams = Array.from(families || []).filter(Boolean);
1677
+ if (fams.length === 0) return;
1678
+ const warmupOnce = () => {
1679
+ const container = document.createElement("div");
1680
+ container.style.cssText = `position: absolute !important;left: -9999px !important;top: 0 !important;opacity: 0 !important;pointer-events: none !important;contain: layout size style;`;
1681
+ for (const fam of fams) {
1682
+ const span = document.createElement("span");
1683
+ span.textContent = "AaBbGg1234\xC1\xC9\xCD\xD3\xDA\xE7\xF1\u2014\u221E";
1684
+ span.style.fontFamily = `"${fam}"`;
1685
+ span.style.fontWeight = "700";
1686
+ span.style.fontStyle = "italic";
1687
+ span.style.fontSize = "32px";
1688
+ span.style.lineHeight = "1";
1689
+ span.style.whiteSpace = "nowrap";
1690
+ span.style.margin = "0";
1691
+ span.style.padding = "0";
1692
+ container.appendChild(span);
1693
+ }
1694
+ document.body.appendChild(container);
1695
+ container.offsetWidth;
1696
+ document.body.removeChild(container);
1697
+ };
1698
+ for (let i = 0; i < Math.max(1, warmupRepetitions); i++) {
1699
+ warmupOnce();
1700
+ yield new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
1701
+ }
1702
+ });
1703
+ }
907
1704
 
908
1705
  // src/modules/pseudo.js
909
- function inlinePseudoElements(source, clone, options) {
1706
+ function inlinePseudoElements(source, clone, sessionCache, options) {
910
1707
  return __async(this, null, function* () {
911
1708
  var _a;
912
1709
  if (!(source instanceof Element) || !(clone instanceof Element)) return;
@@ -936,8 +1733,8 @@
936
1733
  span.textContent = first;
937
1734
  span.dataset.snapdomPseudo = "::first-letter";
938
1735
  const snapshot2 = snapshotComputedStyle(style);
939
- const key2 = getStyleKey(snapshot2, "span", options);
940
- cache.preStyleMap.set(span, key2);
1736
+ const key2 = getStyleKey(snapshot2, "span");
1737
+ sessionCache.styleMap.set(span, key2);
941
1738
  const restNode = document.createTextNode(rest);
942
1739
  clone.replaceChild(restNode, textNode);
943
1740
  clone.insertBefore(span, restNode);
@@ -957,7 +1754,7 @@
957
1754
  const borderStyle = style.borderStyle;
958
1755
  const borderWidth = parseFloat(style.borderWidth);
959
1756
  const transform = style.transform;
960
- const isIconFont2 = isIconFont(fontFamily);
1757
+ const isIconFont22 = isIconFont2(fontFamily);
961
1758
  const hasExplicitContent = content !== "none" && cleanContent !== "";
962
1759
  const hasBg = bg && bg !== "none";
963
1760
  const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
@@ -970,9 +1767,9 @@
970
1767
  pseudoEl.dataset.snapdomPseudo = pseudo;
971
1768
  pseudoEl.style.verticalAlign = "middle";
972
1769
  const snapshot = snapshotComputedStyle(style);
973
- const key = getStyleKey(snapshot, "span", options);
974
- cache.preStyleMap.set(pseudoEl, key);
975
- if (isIconFont2 && cleanContent.length === 1) {
1770
+ const key = getStyleKey(snapshot, "span");
1771
+ sessionCache.styleMap.set(pseudoEl, key);
1772
+ if (isIconFont22 && cleanContent.length === 1) {
976
1773
  const { dataUrl, width: width2, height: height2 } = yield iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
977
1774
  const imgEl = document.createElement("img");
978
1775
  imgEl.src = dataUrl;
@@ -984,15 +1781,15 @@
984
1781
  if (rawUrl == null ? void 0 : rawUrl.trim()) {
985
1782
  try {
986
1783
  const imgEl = document.createElement("img");
987
- const dataUrl = yield fetchImage(safeEncodeURI(rawUrl), options);
988
- imgEl.src = dataUrl;
1784
+ const dataUrl = yield snapFetch(safeEncodeURI(rawUrl), { as: "dataURL", useProxy: options.useProxy });
1785
+ imgEl.src = dataUrl.data;
989
1786
  imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
990
1787
  pseudoEl.appendChild(imgEl);
991
1788
  } catch (e) {
992
1789
  console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
993
1790
  }
994
1791
  }
995
- } else if (!isIconFont2 && hasExplicitContent) {
1792
+ } else if (!isIconFont22 && hasExplicitContent) {
996
1793
  pseudoEl.textContent = cleanContent;
997
1794
  }
998
1795
  if (hasBg) {
@@ -1020,7 +1817,7 @@
1020
1817
  const sChildren = Array.from(source.children);
1021
1818
  const cChildren = Array.from(clone.children).filter((child) => !child.dataset.snapdomPseudo);
1022
1819
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
1023
- yield inlinePseudoElements(sChildren[i], cChildren[i], options);
1820
+ yield inlinePseudoElements(sChildren[i], cChildren[i], sessionCache, options);
1024
1821
  }
1025
1822
  });
1026
1823
  }
@@ -1077,10 +1874,16 @@
1077
1874
 
1078
1875
  // src/core/prepare.js
1079
1876
  function prepareClone(_0) {
1080
- return __async(this, arguments, function* (element, compress = false, embedFonts = false, options = {}) {
1877
+ return __async(this, arguments, function* (element, options = {}) {
1081
1878
  var _a, _b;
1879
+ const sessionCache = {
1880
+ styleMap: cache.session.styleMap,
1881
+ styleCache: cache.session.styleCache,
1882
+ nodeMap: cache.session.nodeMap
1883
+ };
1082
1884
  let clone;
1083
1885
  let classCSS = "";
1886
+ let shadowScopedCSS = "";
1084
1887
  stabilizeLayout(element);
1085
1888
  try {
1086
1889
  inlineExternalDefsAndSymbols(element);
@@ -1088,42 +1891,45 @@
1088
1891
  console.warn("inlineExternal defs or symbol failed:", e);
1089
1892
  }
1090
1893
  try {
1091
- clone = deepClone(element, compress, options, element);
1894
+ clone = deepClone(element, sessionCache, options, element);
1092
1895
  } catch (e) {
1093
1896
  console.warn("deepClone failed:", e);
1094
1897
  throw e;
1095
1898
  }
1096
1899
  try {
1097
- yield inlinePseudoElements(element, clone, compress, embedFonts, options.useProxy);
1900
+ yield inlinePseudoElements(element, clone, sessionCache, options);
1098
1901
  } catch (e) {
1099
1902
  console.warn("inlinePseudoElements failed:", e);
1100
1903
  }
1101
- if (compress) {
1102
- const keyToClass = generateCSSClasses();
1103
- classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
1104
- for (const [node, key] of cache.preStyleMap.entries()) {
1105
- if (node.tagName === "STYLE") continue;
1106
- if (node.getRootNode && node.getRootNode() instanceof ShadowRoot) {
1107
- node.setAttribute("style", key.replace(/;/g, "; "));
1108
- continue;
1109
- }
1110
- const className = keyToClass.get(key);
1111
- if (className) node.classList.add(className);
1112
- const bgImage = (_a = node.style) == null ? void 0 : _a.backgroundImage;
1113
- const hasIcon = (_b = node.dataset) == null ? void 0 : _b.snapdomHasIcon;
1114
- if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
1115
- if (hasIcon) {
1116
- node.style.verticalAlign = "middle";
1117
- node.style.display = "inline";
1118
- }
1904
+ yield resolveBlobUrlsInTree(clone);
1905
+ try {
1906
+ const styleNodes = clone.querySelectorAll("style[data-sd]");
1907
+ for (const s of styleNodes) {
1908
+ shadowScopedCSS += s.textContent || "";
1909
+ s.remove();
1119
1910
  }
1120
- } else {
1121
- for (const [node, key] of cache.preStyleMap.entries()) {
1122
- if (node.tagName === "STYLE") continue;
1911
+ } catch (e) {
1912
+ }
1913
+ const keyToClass = generateCSSClasses(sessionCache.styleMap);
1914
+ classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
1915
+ classCSS = shadowScopedCSS + classCSS;
1916
+ for (const [node, key] of sessionCache.styleMap.entries()) {
1917
+ if (node.tagName === "STYLE") continue;
1918
+ if (node.getRootNode && node.getRootNode() instanceof ShadowRoot) {
1123
1919
  node.setAttribute("style", key.replace(/;/g, "; "));
1920
+ continue;
1921
+ }
1922
+ const className = keyToClass.get(key);
1923
+ if (className) node.classList.add(className);
1924
+ const bgImage = (_a = node.style) == null ? void 0 : _a.backgroundImage;
1925
+ const hasIcon = (_b = node.dataset) == null ? void 0 : _b.snapdomHasIcon;
1926
+ if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
1927
+ if (hasIcon) {
1928
+ node.style.verticalAlign = "middle";
1929
+ node.style.display = "inline";
1124
1930
  }
1125
1931
  }
1126
- for (const [cloneNode, originalNode] of cache.preNodeMap.entries()) {
1932
+ for (const [cloneNode, originalNode] of sessionCache.nodeMap.entries()) {
1127
1933
  const scrollX = originalNode.scrollLeft;
1128
1934
  const scrollY = originalNode.scrollTop;
1129
1935
  const hasScroll = scrollX || scrollY;
@@ -1142,9 +1948,9 @@
1142
1948
  cloneNode.appendChild(inner);
1143
1949
  }
1144
1950
  }
1145
- if (element === cache.preNodeMap.get(clone)) {
1146
- const computed = cache.preStyle.get(element) || window.getComputedStyle(element);
1147
- cache.preStyle.set(element, computed);
1951
+ if (element === sessionCache.nodeMap.get(clone)) {
1952
+ const computed = sessionCache.styleCache.get(element) || window.getComputedStyle(element);
1953
+ sessionCache.styleCache.set(element, computed);
1148
1954
  const transform = stripTranslate(computed.transform);
1149
1955
  clone.style.margin = "0";
1150
1956
  clone.style.position = "static";
@@ -1157,13 +1963,13 @@
1157
1963
  clone.style.clear = "none";
1158
1964
  clone.style.transform = transform || "";
1159
1965
  }
1160
- for (const [cloneNode, originalNode] of cache.preNodeMap.entries()) {
1966
+ for (const [cloneNode, originalNode] of sessionCache.nodeMap.entries()) {
1161
1967
  if (originalNode.tagName === "PRE") {
1162
1968
  cloneNode.style.marginTop = "0";
1163
1969
  cloneNode.style.marginBlockStart = "0";
1164
1970
  }
1165
1971
  }
1166
- return { clone, classCSS };
1972
+ return { clone, classCSS, styleCache: sessionCache.styleCache };
1167
1973
  });
1168
1974
  }
1169
1975
  function stabilizeLayout(element) {
@@ -1178,24 +1984,178 @@
1178
1984
  element.style.border = `${outlineWidth} solid transparent`;
1179
1985
  }
1180
1986
  }
1987
+ var _blobToDataUrlCache = /* @__PURE__ */ new Map();
1988
+ function blobUrlToDataUrl(blobUrl) {
1989
+ return __async(this, null, function* () {
1990
+ var _a;
1991
+ if ((_a = cache.resource) == null ? void 0 : _a.has(blobUrl)) return cache.resource.get(blobUrl);
1992
+ if (_blobToDataUrlCache.has(blobUrl)) return _blobToDataUrlCache.get(blobUrl);
1993
+ const p = (() => __async(null, null, function* () {
1994
+ var _a2;
1995
+ const r = yield snapFetch(blobUrl, { as: "dataURL", silent: true });
1996
+ if (!r.ok || typeof r.data !== "string") {
1997
+ throw new Error(`[snapDOM] Failed to read blob URL: ${blobUrl}`);
1998
+ }
1999
+ (_a2 = cache.resource) == null ? void 0 : _a2.set(blobUrl, r.data);
2000
+ return r.data;
2001
+ }))();
2002
+ _blobToDataUrlCache.set(blobUrl, p);
2003
+ try {
2004
+ const data = yield p;
2005
+ _blobToDataUrlCache.set(blobUrl, data);
2006
+ return data;
2007
+ } catch (e) {
2008
+ _blobToDataUrlCache.delete(blobUrl);
2009
+ throw e;
2010
+ }
2011
+ });
2012
+ }
2013
+ var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
2014
+ function replaceBlobUrlsInCssText(cssText) {
2015
+ return __async(this, null, function* () {
2016
+ if (!cssText || cssText.indexOf("blob:") === -1) return cssText;
2017
+ const uniques = Array.from(new Set(cssText.match(BLOB_URL_RE) || []));
2018
+ if (uniques.length === 0) return cssText;
2019
+ let out = cssText;
2020
+ for (const u of uniques) {
2021
+ try {
2022
+ const d = yield blobUrlToDataUrl(u);
2023
+ out = out.split(u).join(d);
2024
+ } catch (e) {
2025
+ }
2026
+ }
2027
+ return out;
2028
+ });
2029
+ }
2030
+ function isBlobUrl(u) {
2031
+ return typeof u === "string" && u.startsWith("blob:");
2032
+ }
2033
+ function parseSrcset(srcset) {
2034
+ return (srcset || "").split(",").map((s) => s.trim()).filter(Boolean).map((item) => {
2035
+ const m = item.match(/^(\S+)(\s+.+)?$/);
2036
+ return m ? { url: m[1], desc: m[2] || "" } : null;
2037
+ }).filter(Boolean);
2038
+ }
2039
+ function stringifySrcset(parts) {
2040
+ return parts.map((p) => p.desc ? `${p.url} ${p.desc.trim()}` : p.url).join(", ");
2041
+ }
2042
+ function resolveBlobUrlsInTree(root) {
2043
+ return __async(this, null, function* () {
2044
+ var _a, _b;
2045
+ if (!root) return;
2046
+ const imgs = root.querySelectorAll ? root.querySelectorAll("img") : [];
2047
+ for (const img of imgs) {
2048
+ try {
2049
+ const srcAttr = img.getAttribute("src");
2050
+ const effective = srcAttr || img.currentSrc || "";
2051
+ if (isBlobUrl(effective)) {
2052
+ const data = yield blobUrlToDataUrl(effective);
2053
+ img.setAttribute("src", data);
2054
+ }
2055
+ const srcset = img.getAttribute("srcset");
2056
+ if (srcset && srcset.includes("blob:")) {
2057
+ const parts = parseSrcset(srcset);
2058
+ let changed = false;
2059
+ for (const p of parts) {
2060
+ if (isBlobUrl(p.url)) {
2061
+ try {
2062
+ p.url = yield blobUrlToDataUrl(p.url);
2063
+ changed = true;
2064
+ } catch (e) {
2065
+ }
2066
+ }
2067
+ }
2068
+ if (changed) img.setAttribute("srcset", stringifySrcset(parts));
2069
+ }
2070
+ } catch (e) {
2071
+ }
2072
+ }
2073
+ const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
2074
+ for (const node of svgImages) {
2075
+ try {
2076
+ const XLINK_NS = "http://www.w3.org/1999/xlink";
2077
+ const href = node.getAttribute("href") || ((_a = node.getAttributeNS) == null ? void 0 : _a.call(node, XLINK_NS, "href"));
2078
+ if (isBlobUrl(href)) {
2079
+ const d = yield blobUrlToDataUrl(href);
2080
+ node.setAttribute("href", d);
2081
+ (_b = node.removeAttributeNS) == null ? void 0 : _b.call(node, XLINK_NS, "href");
2082
+ }
2083
+ } catch (e) {
2084
+ }
2085
+ }
2086
+ const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
2087
+ for (const el of styled) {
2088
+ try {
2089
+ const styleText = el.getAttribute("style");
2090
+ if (styleText && styleText.includes("blob:")) {
2091
+ const replaced = yield replaceBlobUrlsInCssText(styleText);
2092
+ el.setAttribute("style", replaced);
2093
+ }
2094
+ } catch (e) {
2095
+ }
2096
+ }
2097
+ const styleTags = root.querySelectorAll ? root.querySelectorAll("style") : [];
2098
+ for (const s of styleTags) {
2099
+ try {
2100
+ const css = s.textContent || "";
2101
+ if (css.includes("blob:")) {
2102
+ s.textContent = yield replaceBlobUrlsInCssText(css);
2103
+ }
2104
+ } catch (e) {
2105
+ }
2106
+ }
2107
+ const urlAttrs = ["poster"];
2108
+ for (const attr of urlAttrs) {
2109
+ const nodes = root.querySelectorAll ? root.querySelectorAll(`[${attr}^='blob:']`) : [];
2110
+ for (const n of nodes) {
2111
+ try {
2112
+ const u = n.getAttribute(attr);
2113
+ if (isBlobUrl(u)) {
2114
+ n.setAttribute(attr, yield blobUrlToDataUrl(u));
2115
+ }
2116
+ } catch (e) {
2117
+ }
2118
+ }
2119
+ }
2120
+ });
2121
+ }
1181
2122
 
1182
2123
  // src/modules/images.js
1183
2124
  function inlineImages(_0) {
1184
2125
  return __async(this, arguments, function* (clone, options = {}) {
1185
2126
  const imgs = Array.from(clone.querySelectorAll("img"));
1186
2127
  const processImg = (img) => __async(null, null, function* () {
1187
- const src = img.src;
1188
- try {
1189
- const dataUrl = yield fetchImage(src, { useProxy: options.useProxy });
1190
- img.src = dataUrl;
2128
+ if (!img.getAttribute("src")) {
2129
+ const eff = img.currentSrc || img.src || "";
2130
+ if (eff) img.setAttribute("src", eff);
2131
+ }
2132
+ img.removeAttribute("srcset");
2133
+ img.removeAttribute("sizes");
2134
+ const src = img.src || "";
2135
+ if (!src) return;
2136
+ const r = yield snapFetch(src, { as: "dataURL", useProxy: options.useProxy });
2137
+ if (r.ok && typeof r.data === "string" && r.data.startsWith("data:")) {
2138
+ img.src = r.data;
1191
2139
  if (!img.width) img.width = img.naturalWidth || 100;
1192
2140
  if (!img.height) img.height = img.naturalHeight || 100;
1193
- } catch (e) {
1194
- const fallback = document.createElement("div");
1195
- fallback.style = `width: ${img.width || 100}px; height: ${img.height || 100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${img.height || 100}px; color: #666; font-size: 12px;`;
1196
- fallback.innerText = "img";
1197
- img.replaceWith(fallback);
2141
+ return;
1198
2142
  }
2143
+ const w = img.width || img.naturalWidth || 100;
2144
+ 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);
1199
2159
  });
1200
2160
  for (let i = 0; i < imgs.length; i += 4) {
1201
2161
  const group = imgs.slice(i, i + 4).map(processImg);
@@ -1205,8 +2165,8 @@
1205
2165
  }
1206
2166
 
1207
2167
  // src/modules/background.js
1208
- function inlineBackgroundImages(_0, _1) {
1209
- return __async(this, arguments, function* (source, clone, options = {}) {
2168
+ function inlineBackgroundImages(_0, _1, _2) {
2169
+ return __async(this, arguments, function* (source, clone, styleCache, options = {}) {
1210
2170
  const queue = [[source, clone]];
1211
2171
  const imageProps = [
1212
2172
  "background-image",
@@ -1226,8 +2186,8 @@
1226
2186
  ];
1227
2187
  while (queue.length) {
1228
2188
  const [srcNode, cloneNode] = queue.shift();
1229
- const style = cache.preStyle.get(srcNode) || getStyle(srcNode);
1230
- if (!cache.preStyle.has(srcNode)) cache.preStyle.set(srcNode, style);
2189
+ const style = styleCache.get(srcNode) || getStyle(srcNode);
2190
+ if (!styleCache.has(srcNode)) styleCache.set(srcNode, style);
1231
2191
  const hasBorderImage = (() => {
1232
2192
  const bi = style.getPropertyValue("border-image");
1233
2193
  const bis = style.getPropertyValue("border-image-source");
@@ -1247,10 +2207,6 @@
1247
2207
  cloneNode.style.setProperty(prop, inlined.join(", "));
1248
2208
  }
1249
2209
  }
1250
- const bgColor = style.getPropertyValue("background-color");
1251
- if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") {
1252
- cloneNode.style.backgroundColor = bgColor;
1253
- }
1254
2210
  const sChildren = Array.from(srcNode.children);
1255
2211
  const cChildren = Array.from(cloneNode.children);
1256
2212
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
@@ -1261,17 +2217,17 @@
1261
2217
  }
1262
2218
 
1263
2219
  // src/core/capture.js
1264
- function captureDOM(_0) {
1265
- return __async(this, arguments, function* (element, options = {}) {
2220
+ function captureDOM(element, options) {
2221
+ return __async(this, null, function* () {
1266
2222
  if (!element) throw new Error("Element cannot be null or undefined");
1267
- cache.reset();
1268
- const { compress = true, embedFonts = false, fast = true, scale = 1, useProxy = "" } = options;
1269
- let clone, classCSS;
2223
+ applyCachePolicy(options.cache);
2224
+ const fast = options.fast;
2225
+ let clone, classCSS, styleCache;
1270
2226
  let fontsCSS = "";
1271
2227
  let baseCSS = "";
1272
2228
  let dataURL;
1273
2229
  let svgString;
1274
- ({ clone, classCSS } = yield prepareClone(element, compress, embedFonts, options));
2230
+ ({ clone, classCSS, styleCache } = yield prepareClone(element, options));
1275
2231
  yield new Promise((resolve) => {
1276
2232
  idle(() => __async(null, null, function* () {
1277
2233
  yield inlineImages(clone, options);
@@ -1280,32 +2236,38 @@
1280
2236
  });
1281
2237
  yield new Promise((resolve) => {
1282
2238
  idle(() => __async(null, null, function* () {
1283
- yield inlineBackgroundImages(element, clone, options);
2239
+ yield inlineBackgroundImages(element, clone, styleCache, options);
1284
2240
  resolve();
1285
2241
  }), { fast });
1286
2242
  });
1287
- if (embedFonts) {
2243
+ if (options.embedFonts) {
1288
2244
  yield new Promise((resolve) => {
1289
2245
  idle(() => __async(null, null, function* () {
1290
- fontsCSS = yield embedCustomFonts();
2246
+ const required = collectUsedFontVariants(element);
2247
+ const usedCodepoints = collectUsedCodepoints(element);
2248
+ if (isSafari) {
2249
+ const families = new Set(
2250
+ Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
2251
+ );
2252
+ yield ensureFontsReady(families, 2);
2253
+ }
2254
+ fontsCSS = yield embedCustomFonts({ required, usedCodepoints, preCached: false, exclude: options.excludeFonts, useProxy: options.useProxy });
1291
2255
  resolve();
1292
2256
  }), { fast });
1293
2257
  });
1294
2258
  }
1295
- if (compress) {
1296
- const usedTags = collectUsedTagNames(clone).sort();
1297
- const tagKey = usedTags.join(",");
1298
- if (cache.baseStyle.has(tagKey)) {
1299
- baseCSS = cache.baseStyle.get(tagKey);
1300
- } else {
1301
- yield new Promise((resolve) => {
1302
- idle(() => {
1303
- baseCSS = generateDedupedBaseCSS(usedTags);
1304
- cache.baseStyle.set(tagKey, baseCSS);
1305
- resolve();
1306
- }, { fast });
1307
- });
1308
- }
2259
+ const usedTags = collectUsedTagNames(clone).sort();
2260
+ const tagKey = usedTags.join(",");
2261
+ if (cache.baseStyle.has(tagKey)) {
2262
+ baseCSS = cache.baseStyle.get(tagKey);
2263
+ } else {
2264
+ yield new Promise((resolve) => {
2265
+ idle(() => {
2266
+ baseCSS = generateDedupedBaseCSS(usedTags);
2267
+ cache.baseStyle.set(tagKey, baseCSS);
2268
+ resolve();
2269
+ }, { fast });
2270
+ });
1309
2271
  }
1310
2272
  yield new Promise((resolve) => {
1311
2273
  idle(() => {
@@ -1364,26 +2326,77 @@
1364
2326
  });
1365
2327
  }
1366
2328
 
1367
- // src/api/snapdom.js
1368
- function toImg(_0) {
1369
- return __async(this, arguments, function* (url, { scale = 1 } = {}) {
2329
+ // src/core/context.js
2330
+ function normalizeCachePolicy(v) {
2331
+ if (v === true) return "soft";
2332
+ if (v === false) return "disabled";
2333
+ if (typeof v === "string") {
2334
+ const s = v.toLowerCase().trim();
2335
+ if (s === "disabled" || s === "full" || s === "auto" || s === "soft") return (
2336
+ /** @type {CachePolicy} */
2337
+ s
2338
+ );
2339
+ }
2340
+ return "soft";
2341
+ }
2342
+ function createContext(options = {}) {
2343
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
2344
+ const resolvedFormat = (_a = options.format) != null ? _a : "png";
2345
+ const cachePolicy = normalizeCachePolicy(options.cache);
2346
+ return {
2347
+ // Debug & perf
2348
+ debug: (_b = options.debug) != null ? _b : false,
2349
+ fast: (_c = options.fast) != null ? _c : true,
2350
+ scale: (_d = options.scale) != null ? _d : 1,
2351
+ // DOM filters
2352
+ exclude: (_e = options.exclude) != null ? _e : [],
2353
+ filter: (_f = options.filter) != null ? _f : null,
2354
+ // Fuentes
2355
+ embedFonts: (_g = options.embedFonts) != null ? _g : false,
2356
+ iconFonts: Array.isArray(options.iconFonts) ? options.iconFonts : options.iconFonts ? [options.iconFonts] : [],
2357
+ localFonts: Array.isArray(options.localFonts) ? options.localFonts : [],
2358
+ excludeFonts: (_h = options.excludeFonts) != null ? _h : void 0,
2359
+ /** Cache policy (disabled|full|auto|soft). Default: "soft" */
2360
+ /** @type {CachePolicy} */
2361
+ cache: cachePolicy,
2362
+ // Red
2363
+ useProxy: typeof options.useProxy === "string" ? options.useProxy : "",
2364
+ // Salida
2365
+ width: (_i = options.width) != null ? _i : null,
2366
+ height: (_j = options.height) != null ? _j : null,
2367
+ 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"
2373
+ // Plugins (reservado para futura activación)
2374
+ // plugins: normalizePlugins(...),
2375
+ };
2376
+ }
2377
+
2378
+ // src/exporters/toImg.js
2379
+ function toImg(url, options) {
2380
+ return __async(this, null, function* () {
1370
2381
  const img = new Image();
1371
2382
  img.src = url;
1372
2383
  yield img.decode();
1373
- if (scale !== 1) {
1374
- img.style.width = `${img.naturalWidth * scale}px`;
1375
- img.style.height = `${img.naturalHeight * scale}px`;
2384
+ if (options.scale !== 1) {
2385
+ img.style.width = `${img.naturalWidth * options.scale}px`;
2386
+ img.style.height = `${img.naturalHeight * options.scale}px`;
1376
2387
  }
1377
2388
  return img;
1378
2389
  });
1379
2390
  }
1380
- function toCanvas(_0) {
1381
- return __async(this, arguments, function* (url, { dpr = 1, scale = 1 } = {}) {
2391
+
2392
+ // src/exporters/toCanvas.js
2393
+ function toCanvas(url, options) {
2394
+ return __async(this, null, function* () {
1382
2395
  const img = new Image();
1383
- img.src = url;
1384
2396
  img.crossOrigin = "anonymous";
1385
2397
  img.loading = "eager";
1386
2398
  img.decoding = "sync";
2399
+ img.src = url;
1387
2400
  const isSafariBrowser = isSafari();
1388
2401
  let appended = false;
1389
2402
  if (isSafariBrowser) {
@@ -1391,16 +2404,11 @@
1391
2404
  appended = true;
1392
2405
  }
1393
2406
  yield img.decode();
1394
- if (isSafariBrowser) {
1395
- yield new Promise((resolve) => setTimeout(resolve, 100));
1396
- }
1397
- if (img.width === 0 || img.height === 0) {
1398
- if (appended) img.remove();
1399
- throw new Error("Image failed to load or has no dimensions");
1400
- }
1401
- const width = img.naturalWidth * scale;
1402
- const height = img.naturalHeight * scale;
2407
+ if (isSafariBrowser) yield new Promise((resolve) => setTimeout(resolve, 100));
2408
+ const width = img.naturalWidth * options.scale;
2409
+ const height = img.naturalHeight * options.scale;
1403
2410
  const canvas = document.createElement("canvas");
2411
+ const dpr = options.dpr;
1404
2412
  canvas.width = Math.ceil(width * dpr);
1405
2413
  canvas.height = Math.ceil(height * dpr);
1406
2414
  canvas.style.width = `${width}px`;
@@ -1412,194 +2420,188 @@
1412
2420
  return canvas;
1413
2421
  });
1414
2422
  }
1415
- function toBlob(_0) {
1416
- return __async(this, arguments, function* (url, {
1417
- type = "svg",
1418
- scale = 1,
1419
- backgroundColor = "#fff",
1420
- quality
1421
- } = {}) {
1422
- const mime = {
1423
- jpg: "image/jpeg",
1424
- jpeg: "image/jpeg",
1425
- png: "image/png",
1426
- webp: "image/webp"
1427
- }[type] || "image/png";
2423
+
2424
+ // src/exporters/toBlob.js
2425
+ function toBlob(url, options) {
2426
+ return __async(this, null, function* () {
2427
+ const type = options.type;
1428
2428
  if (type === "svg") {
1429
2429
  const svgText = decodeURIComponent(url.split(",")[1]);
1430
2430
  return new Blob([svgText], { type: "image/svg+xml" });
1431
2431
  }
1432
- const canvas = yield createBackground(url, { dpr: 1, scale }, backgroundColor);
1433
- return new Promise((resolve) => {
1434
- canvas.toBlob((blob) => resolve(blob), `${mime}`, quality);
1435
- });
1436
- });
1437
- }
1438
- function createBackground(_0, _1, _2) {
1439
- return __async(this, arguments, function* (url, { dpr = 1, scale = 1 }, backgroundColor) {
1440
- const baseCanvas = yield toCanvas(url, { dpr, scale });
1441
- if (!backgroundColor) return baseCanvas;
1442
- const temp = document.createElement("canvas");
1443
- temp.width = baseCanvas.width;
1444
- temp.height = baseCanvas.height;
1445
- const ctx = temp.getContext("2d");
1446
- ctx.fillStyle = backgroundColor;
1447
- ctx.fillRect(0, 0, temp.width, temp.height);
1448
- ctx.drawImage(baseCanvas, 0, 0);
1449
- return temp;
2432
+ const canvas = yield toCanvas(url, options);
2433
+ const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
2434
+ return new Promise(
2435
+ (resolve) => finalCanvas.toBlob(
2436
+ (blob) => resolve(blob),
2437
+ `image/${type}`,
2438
+ options.quality
2439
+ )
2440
+ );
1450
2441
  });
1451
2442
  }
1452
- function toRasterImg(_0, _1) {
1453
- return __async(this, arguments, function* (url, { dpr = 1, scale = 1, backgroundColor, quality }, format = "png") {
1454
- const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
1455
- const finalBg = backgroundColor != null ? backgroundColor : defaultBg;
1456
- const canvas = yield createBackground(url, { dpr, scale }, finalBg);
2443
+
2444
+ // src/modules/rasterize.js
2445
+ function rasterize(url, options) {
2446
+ return __async(this, null, function* () {
2447
+ const canvas = yield toCanvas(url, options);
2448
+ const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
1457
2449
  const img = new Image();
1458
- img.src = canvas.toDataURL(`image/${format}`, quality);
2450
+ img.src = finalCanvas.toDataURL(`image/${options.format}`, options.quality);
1459
2451
  yield img.decode();
1460
- img.style.width = `${canvas.width / dpr}px`;
1461
- img.style.height = `${canvas.height / dpr}px`;
2452
+ img.style.width = `${finalCanvas.width / options.dpr}px`;
2453
+ img.style.height = `${finalCanvas.height / options.dpr}px`;
1462
2454
  return img;
1463
2455
  });
1464
2456
  }
1465
- function download(_0) {
1466
- return __async(this, arguments, function* (url, { dpr = 1, scale = 1, backgroundColor, format = "png", filename = "snapDOM" } = {}) {
1467
- if (format === "svg") {
1468
- const blob = yield toBlob(url);
2457
+
2458
+ // src/exporters/download.js
2459
+ function download(url, options) {
2460
+ return __async(this, null, function* () {
2461
+ if (options.format === "svg") {
2462
+ const blob = yield toBlob(url, __spreadProps(__spreadValues({}, options), { type: "svg" }));
1469
2463
  const objectURL = URL.createObjectURL(blob);
1470
2464
  const a2 = document.createElement("a");
1471
2465
  a2.href = objectURL;
1472
- a2.download = `${filename}.svg`;
2466
+ a2.download = options.filename;
1473
2467
  a2.click();
1474
2468
  URL.revokeObjectURL(objectURL);
1475
2469
  return;
1476
2470
  }
1477
- const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
1478
- const finalBg = backgroundColor != null ? backgroundColor : defaultBg;
1479
- const canvas = yield createBackground(url, { dpr, scale }, finalBg);
1480
- const mime = {
1481
- jpg: "image/jpeg",
1482
- jpeg: "image/jpeg",
1483
- png: "image/png",
1484
- webp: "image/webp"
1485
- }[format] || "image/png";
1486
- const dataURL = canvas.toDataURL(mime);
2471
+ const canvas = yield toCanvas(url, options);
2472
+ const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
1487
2473
  const a = document.createElement("a");
1488
- a.href = dataURL;
1489
- a.download = `${filename}.${format}`;
2474
+ a.href = finalCanvas.toDataURL(`image/${options.format}`, options.quality);
2475
+ a.download = options.filename;
1490
2476
  a.click();
1491
2477
  });
1492
2478
  }
1493
- function snapdom(_0) {
1494
- return __async(this, arguments, function* (element, options = {}) {
1495
- options = __spreadValues({ scale: 1 }, options);
2479
+
2480
+ // src/api/snapdom.js
2481
+ var INTERNAL_TOKEN = Symbol("snapdom.internal");
2482
+ function snapdom(element, userOptions) {
2483
+ return __async(this, null, function* () {
1496
2484
  if (!element) throw new Error("Element cannot be null or undefined");
1497
- if (options.iconFonts) {
1498
- extendIconFonts(options.iconFonts);
1499
- }
1500
- return yield snapdom.capture(element, options);
2485
+ const context = createContext(userOptions);
2486
+ if (context.iconFonts && context.iconFonts.length > 0) extendIconFonts(context.iconFonts);
2487
+ return snapdom.capture(element, context, INTERNAL_TOKEN);
1501
2488
  });
1502
2489
  }
1503
- snapdom.capture = (_0, ..._1) => __async(null, [_0, ..._1], function* (el, options = {}) {
1504
- var _a;
1505
- const url = yield captureDOM(el, options);
1506
- const dpr = (_a = options.dpr) != null ? _a : window.devicePixelRatio || 1;
1507
- const scale = options.scale || 1;
2490
+ snapdom.capture = (el, context, _token) => __async(null, null, function* () {
2491
+ if (_token !== INTERNAL_TOKEN) {
2492
+ throw new Error("[snapdom.capture] is internal. Use snapdom(...) instead.");
2493
+ }
2494
+ const url = yield captureDOM(el, context);
2495
+ const ensureContext = (opts) => __spreadValues(__spreadValues({}, context), opts || {});
2496
+ const withFormat = (format) => (opts) => rasterize(url, ensureContext(__spreadProps(__spreadValues({}, opts || {}), { format })));
1508
2497
  return {
1509
2498
  url,
1510
- options,
1511
2499
  toRaw: () => url,
1512
- toImg: (opts = {}) => toImg(url, __spreadValues({ dpr, scale }, opts)),
1513
- toCanvas: (opts = {}) => toCanvas(url, __spreadValues({ dpr, scale }, opts)),
1514
- toBlob: (opts = {}) => toBlob(url, __spreadValues({ dpr, scale }, opts)),
1515
- toPng: (opts = {}) => toRasterImg(url, __spreadValues({ dpr, scale }, opts), "png"),
1516
- toJpg: (opts = {}) => toRasterImg(url, __spreadValues({ dpr, scale }, opts), "jpeg"),
1517
- toWebp: (opts = {}) => toRasterImg(url, __spreadValues({ dpr, scale }, opts), "webp"),
1518
- download: (_b = {}) => {
1519
- var _c = _b, { format = "png", filename = "snapDOM", backgroundColor } = _c, opts = __objRest(_c, ["format", "filename", "backgroundColor"]);
1520
- return download(url, __spreadValues({ dpr, scale, format, filename, backgroundColor }, opts));
1521
- }
2500
+ toImg: (opts) => toImg(url, ensureContext(opts)),
2501
+ toCanvas: (opts) => toCanvas(url, ensureContext(opts)),
2502
+ toBlob: (opts) => toBlob(url, ensureContext(opts)),
2503
+ toPng: withFormat("png"),
2504
+ toJpg: withFormat("jpeg"),
2505
+ toWebp: withFormat("webp"),
2506
+ download: (opts) => download(url, ensureContext(opts))
1522
2507
  };
1523
2508
  });
1524
- snapdom.toRaw = (el, options) => __async(null, null, function* () {
1525
- return (yield snapdom.capture(el, options)).toRaw();
1526
- });
1527
- snapdom.toImg = (el, options) => __async(null, null, function* () {
1528
- return (yield snapdom.capture(el, options)).toImg();
1529
- });
1530
- snapdom.toCanvas = (el, options) => __async(null, null, function* () {
1531
- return (yield snapdom.capture(el, options)).toCanvas();
1532
- });
1533
- snapdom.toBlob = (el, options) => __async(null, null, function* () {
1534
- return (yield snapdom.capture(el, options)).toBlob(options);
1535
- });
1536
- snapdom.toPng = (el, options) => __async(null, null, function* () {
1537
- return (yield snapdom.capture(el, options)).toPng(options);
1538
- });
1539
- snapdom.toJpg = (el, options) => __async(null, null, function* () {
1540
- return (yield snapdom.capture(el, options)).toJpg(options);
1541
- });
1542
- snapdom.toWebp = (el, options) => __async(null, null, function* () {
1543
- return (yield snapdom.capture(el, options)).toWebp(options);
1544
- });
1545
- snapdom.download = (_0, ..._1) => __async(null, [_0, ..._1], function* (el, options = {}) {
1546
- const _a = options, {
1547
- format = "png",
1548
- filename = "capture",
1549
- backgroundColor
1550
- } = _a, rest = __objRest(_a, [
1551
- "format",
1552
- "filename",
1553
- "backgroundColor"
1554
- ]);
1555
- const capture = yield snapdom.capture(el, rest);
1556
- return yield capture.download({ format, filename, backgroundColor });
1557
- });
2509
+ snapdom.toRaw = (el, options) => snapdom(el, options).then((result) => result.toRaw());
2510
+ snapdom.toImg = (el, options) => snapdom(el, options).then((result) => result.toImg());
2511
+ snapdom.toCanvas = (el, options) => snapdom(el, options).then((result) => result.toCanvas());
2512
+ snapdom.toBlob = (el, options) => snapdom(el, options).then((result) => result.toBlob());
2513
+ snapdom.toPng = (el, options) => snapdom(el, __spreadProps(__spreadValues({}, options), { format: "png" })).then((result) => result.toPng());
2514
+ snapdom.toJpg = (el, options) => snapdom(el, __spreadProps(__spreadValues({}, options), { format: "jpeg" })).then((result) => result.toJpg());
2515
+ snapdom.toWebp = (el, options) => snapdom(el, __spreadProps(__spreadValues({}, options), { format: "webp" })).then((result) => result.toWebp());
2516
+ snapdom.download = (el, options) => snapdom(el, options).then((result) => result.download());
1558
2517
 
1559
2518
  // src/api/preCache.js
1560
2519
  function preCache() {
1561
2520
  return __async(this, arguments, function* (root = document, options = {}) {
1562
- const { embedFonts = true, reset = false } = options;
1563
- if (reset) {
1564
- cache.reset();
1565
- return;
2521
+ var _a;
2522
+ const {
2523
+ embedFonts = true,
2524
+ cacheOpt = "full",
2525
+ useProxy = ""
2526
+ } = options;
2527
+ applyCachePolicy(cacheOpt);
2528
+ try {
2529
+ yield document.fonts.ready;
2530
+ } catch (e) {
1566
2531
  }
1567
- yield document.fonts.ready;
1568
2532
  precacheCommonTags();
2533
+ cache.session = cache.session || {};
2534
+ if (!cache.session.styleCache) {
2535
+ cache.session.styleCache = /* @__PURE__ */ new WeakMap();
2536
+ }
2537
+ try {
2538
+ yield inlineBackgroundImages(
2539
+ root,
2540
+ /* mirror */
2541
+ void 0,
2542
+ cache.session.styleCache,
2543
+ { useProxy }
2544
+ );
2545
+ } catch (e) {
2546
+ }
1569
2547
  let imgEls = [], allEls = [];
1570
- if (root == null ? void 0 : root.querySelectorAll) {
1571
- imgEls = Array.from(root.querySelectorAll("img[src]"));
1572
- allEls = Array.from(root.querySelectorAll("*"));
2548
+ try {
2549
+ if (root == null ? void 0 : root.querySelectorAll) {
2550
+ imgEls = Array.from(root.querySelectorAll("img[src]"));
2551
+ allEls = Array.from(root.querySelectorAll("*"));
2552
+ }
2553
+ } catch (e) {
1573
2554
  }
1574
2555
  const promises = [];
1575
2556
  for (const img of imgEls) {
1576
- const src = img.src;
2557
+ const src = img == null ? void 0 : img.src;
2558
+ if (!src) continue;
1577
2559
  if (!cache.image.has(src)) {
1578
- promises.push(
1579
- fetchImage(src, { useProxy: options.useProxy }).then((dataURL) => cache.image.set(src, dataURL)).catch(() => {
1580
- })
1581
- );
2560
+ const p = Promise.resolve().then(() => snapFetch(src, { as: "dataURL", useProxy })).then((dataURL) => {
2561
+ cache.image.set(src, dataURL);
2562
+ }).catch(() => {
2563
+ });
2564
+ promises.push(p);
1582
2565
  }
1583
2566
  }
1584
2567
  for (const el of allEls) {
1585
- const bg = getStyle(el).backgroundImage;
2568
+ let bg = "";
2569
+ try {
2570
+ bg = getStyle(el).backgroundImage;
2571
+ } catch (e) {
2572
+ }
1586
2573
  if (bg && bg !== "none") {
1587
- const bgSplits = splitBackgroundImage(bg);
1588
- for (const entry of bgSplits) {
1589
- const isUrl = entry.startsWith("url(");
1590
- if (isUrl) {
1591
- promises.push(
1592
- inlineSingleBackgroundEntry(entry, options).catch(() => {
1593
- })
1594
- );
2574
+ const parts = splitBackgroundImage(bg);
2575
+ for (const entry of parts) {
2576
+ if (entry.startsWith("url(")) {
2577
+ const p = Promise.resolve().then(() => inlineSingleBackgroundEntry(entry, __spreadProps(__spreadValues({}, options), { useProxy }))).catch(() => {
2578
+ });
2579
+ promises.push(p);
1595
2580
  }
1596
2581
  }
1597
2582
  }
1598
2583
  }
1599
2584
  if (embedFonts) {
1600
- yield embedCustomFonts({ preCached: true });
2585
+ try {
2586
+ const required = collectUsedFontVariants(root);
2587
+ const usedCodepoints = collectUsedCodepoints(root);
2588
+ if (typeof isSafari === "function" ? isSafari() : !!isSafari) {
2589
+ const families = new Set(
2590
+ Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
2591
+ );
2592
+ yield ensureFontsReady(families, 2);
2593
+ }
2594
+ yield embedCustomFonts({
2595
+ required,
2596
+ usedCodepoints,
2597
+ exclude: options.excludeFonts,
2598
+ localFonts: options.localFonts,
2599
+ useProxy: (_a = options.useProxy) != null ? _a : useProxy
2600
+ });
2601
+ } catch (e) {
2602
+ }
1601
2603
  }
1602
- yield Promise.all(promises);
2604
+ yield Promise.allSettled(promises);
1603
2605
  });
1604
2606
  }
1605
2607