@zumer/snapdom 1.9.9 → 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,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.9.9
4
+ * v.1.9.10-dev.0
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -26,18 +26,6 @@
26
26
  return a;
27
27
  };
28
28
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
29
- var __objRest = (source, exclude) => {
30
- var target = {};
31
- for (var prop in source)
32
- if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
33
- target[prop] = source[prop];
34
- if (source != null && __getOwnPropSymbols)
35
- for (var prop of __getOwnPropSymbols(source)) {
36
- if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
37
- target[prop] = source[prop];
38
- }
39
- return target;
40
- };
41
29
  var __async = (__this, __arguments, generator) => {
42
30
  return new Promise((resolve, reject) => {
43
31
  var fulfilled = (value) => {
@@ -68,15 +56,314 @@
68
56
  baseStyle: /* @__PURE__ */ new Map(),
69
57
  computedStyle: /* @__PURE__ */ new WeakMap(),
70
58
  font: /* @__PURE__ */ new Set(),
71
- snapshot: /* @__PURE__ */ new WeakMap(),
72
- snapshotKey: /* @__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();
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
+ }
134
+ }
135
+
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
+ });
77
326
  }
78
327
 
79
- // src/utils/cssTools.js
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
+ ]);
80
367
  var commonTags = [
81
368
  "div",
82
369
  "span",
@@ -103,12 +390,6 @@
103
390
  "h4",
104
391
  "h5",
105
392
  "h6",
106
- "svg",
107
- "path",
108
- "circle",
109
- "rect",
110
- "line",
111
- "g",
112
393
  "table",
113
394
  "thead",
114
395
  "tbody",
@@ -118,28 +399,33 @@
118
399
  ];
119
400
  function precacheCommonTags() {
120
401
  for (let tag of commonTags) {
121
- 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);
122
406
  }
123
407
  }
124
408
  function getDefaultStyleForTag(tagName) {
125
- if (cache.defaultStyle.has(tagName)) {
126
- return cache.defaultStyle.get(tagName);
127
- }
128
- const skipTags = /* @__PURE__ */ new Set(["script", "style", "meta", "link", "noscript", "template", "defs", "symbol", "title", "metadata", "desc"]);
129
- if (skipTags.has(tagName)) {
409
+ tagName = String(tagName).toLowerCase();
410
+ if (NO_DEFAULTS_TAGS.has(tagName)) {
130
411
  const empty = {};
131
412
  cache.defaultStyle.set(tagName, empty);
132
413
  return empty;
133
414
  }
415
+ if (cache.defaultStyle.has(tagName)) {
416
+ return cache.defaultStyle.get(tagName);
417
+ }
134
418
  let sandbox = document.getElementById("snapdom-sandbox");
135
419
  if (!sandbox) {
136
420
  sandbox = document.createElement("div");
137
421
  sandbox.id = "snapdom-sandbox";
422
+ sandbox.setAttribute("data-snapdom-sandbox", "true");
423
+ sandbox.setAttribute("aria-hidden", "true");
138
424
  sandbox.style.position = "absolute";
139
425
  sandbox.style.left = "-9999px";
140
426
  sandbox.style.top = "-9999px";
141
- sandbox.style.width = "0";
142
- sandbox.style.height = "0";
427
+ sandbox.style.width = "0px";
428
+ sandbox.style.height = "0px";
143
429
  sandbox.style.overflow = "hidden";
144
430
  document.body.appendChild(sandbox);
145
431
  }
@@ -149,32 +435,55 @@
149
435
  const styles = getComputedStyle(el);
150
436
  const defaults = {};
151
437
  for (let prop of styles) {
152
- defaults[prop] = styles.getPropertyValue(prop);
438
+ if (shouldIgnoreProp(prop)) continue;
439
+ const value = styles.getPropertyValue(prop);
440
+ defaults[prop] = value;
153
441
  }
154
442
  sandbox.removeChild(el);
155
443
  cache.defaultStyle.set(tagName, defaults);
156
444
  return defaults;
157
445
  }
158
- var IGNORED_PROPS = /* @__PURE__ */ new Set([
159
- "-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"
160
465
  ]);
161
- 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
+ }
162
478
  const entries = [];
163
- const defaultStyles = getDefaultStyleForTag(tagName);
479
+ const defaults = getDefaultStyleForTag(tagName);
164
480
  for (let [prop, value] of Object.entries(snapshot)) {
165
- if (IGNORED_PROPS.has(prop)) continue;
166
- if (!compress) {
167
- if (value) {
168
- entries.push(`${prop}:${value}`);
169
- }
170
- } else {
171
- const defaultValue = defaultStyles[prop];
172
- if (value && value !== defaultValue) {
173
- entries.push(`${prop}:${value}`);
174
- }
175
- }
481
+ if (shouldIgnoreProp(prop)) continue;
482
+ const def = defaults[prop];
483
+ if (value && value !== def) entries.push(`${prop}:${value}`);
176
484
  }
177
- return entries.sort().join(";");
485
+ entries.sort();
486
+ return entries.join(";");
178
487
  }
179
488
  function collectUsedTagNames(root) {
180
489
  const tagSet = /* @__PURE__ */ new Set();
@@ -195,6 +504,7 @@
195
504
  const styles = cache.defaultStyle.get(tagName);
196
505
  if (!styles) continue;
197
506
  const key = Object.entries(styles).map(([k, v]) => `${k}:${v};`).sort().join("");
507
+ if (!key) continue;
198
508
  if (!groups.has(key)) {
199
509
  groups.set(key, []);
200
510
  }
@@ -208,45 +518,12 @@
208
518
  return css;
209
519
  }
210
520
  function generateCSSClasses(styleMap) {
211
- const keySet = new Set(styleMap.values());
521
+ const keys = Array.from(new Set(styleMap.values())).filter(Boolean).sort();
212
522
  const classMap = /* @__PURE__ */ new Map();
213
- let counter = 1;
214
- for (const key of keySet) {
215
- if (!key.trim()) continue;
216
- classMap.set(key, `c${counter++}`);
217
- }
523
+ let i = 1;
524
+ for (const k of keys) classMap.set(k, `c${i++}`);
218
525
  return classMap;
219
526
  }
220
-
221
- // src/utils/helpers.js
222
- function inlineSingleBackgroundEntry(_0) {
223
- return __async(this, arguments, function* (entry, options = {}) {
224
- const rawUrl = extractURL(entry);
225
- const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
226
- if (rawUrl) {
227
- const encodedUrl = safeEncodeURI(rawUrl);
228
- if (cache.background.has(encodedUrl)) {
229
- return options.skipInline ? void 0 : `url(${cache.background.get(encodedUrl)})`;
230
- } else {
231
- const dataUrl = yield fetchImage(encodedUrl, { useProxy: options.useProxy });
232
- cache.background.set(encodedUrl, dataUrl);
233
- return options.skipInline ? void 0 : `url("${dataUrl}")`;
234
- }
235
- }
236
- if (isGradient || entry === "none") {
237
- return entry;
238
- }
239
- return entry;
240
- });
241
- }
242
- function idle(fn, { fast = false } = {}) {
243
- if (fast) return fn();
244
- if ("requestIdleCallback" in window) {
245
- requestIdleCallback(fn, { timeout: 50 });
246
- } else {
247
- setTimeout(fn, 1);
248
- }
249
- }
250
527
  function getStyle(el, pseudo = null) {
251
528
  if (!(el instanceof Element)) {
252
529
  return window.getComputedStyle(el, pseudo);
@@ -273,196 +550,6 @@
273
550
  }
274
551
  return clean;
275
552
  }
276
- function extractURL(value) {
277
- const match = value.match(/url\((['"]?)(.*?)(\1)\)/);
278
- if (!match) return null;
279
- const url = match[2].trim();
280
- if (url.startsWith("#")) return null;
281
- return url;
282
- }
283
- function fetchResource(_0) {
284
- return __async(this, arguments, function* (url, { useProxy = "" } = {}) {
285
- function doFetch(u) {
286
- return __async(this, null, function* () {
287
- const res = yield fetch(u);
288
- if (!res.ok) throw new Error(`[snapdom] Failed to fetch resource: ${u}`);
289
- return res;
290
- });
291
- }
292
- try {
293
- return yield doFetch(url);
294
- } catch (e) {
295
- if (useProxy && typeof useProxy === "string") {
296
- const proxied = useProxy.replace(/\/$/, "") + safeEncodeURI(url);
297
- return doFetch(proxied);
298
- }
299
- throw e;
300
- }
301
- });
302
- }
303
- var _inflight = /* @__PURE__ */ new Map();
304
- var _errorCache = /* @__PURE__ */ new Map();
305
- function fetchImage(src, { timeout = 3e3, useProxy = "", errorTTL = 8e3 } = {}) {
306
- function getCrossOriginMode(url) {
307
- try {
308
- const parsed = new URL(url, window.location.href);
309
- return parsed.origin === window.location.origin ? "use-credentials" : "anonymous";
310
- } catch (e) {
311
- return "anonymous";
312
- }
313
- }
314
- const ok = (data) => ({ ok: true, data });
315
- const fail = (e) => ({ ok: false, error: e instanceof Error ? e : new Error(String(e)) });
316
- function fetchBlobAsDataURLSafe(fetchUrl) {
317
- try {
318
- return fetch(fetchUrl, {
319
- mode: "cors",
320
- credentials: getCrossOriginMode(fetchUrl) === "use-credentials" ? "include" : "omit"
321
- }).then((r) => {
322
- if (!r.ok) return fail(new Error("HTTP " + r.status));
323
- return r.blob().then((blob) => new Promise((resolve) => {
324
- const reader = new FileReader();
325
- reader.onloadend = () => {
326
- const base64 = reader.result;
327
- if (typeof base64 !== "string" || !base64.startsWith("data:image/")) {
328
- resolve(fail(new Error("Invalid image data URL")));
329
- } else {
330
- resolve(ok(base64));
331
- }
332
- };
333
- reader.onerror = () => resolve(fail(new Error("FileReader error")));
334
- reader.readAsDataURL(blob);
335
- }));
336
- }).catch((e) => fail(e));
337
- } catch (e) {
338
- return Promise.resolve(fail(e));
339
- }
340
- }
341
- function fetchWithFallbackOnceSafe(url) {
342
- return fetchBlobAsDataURLSafe(url).then((r) => {
343
- if (r.ok) return r;
344
- if (useProxy && typeof useProxy === "string") {
345
- const proxied = useProxy.replace(/\/$/, "") + safeEncodeURI(url);
346
- return fetchBlobAsDataURLSafe(proxied).then((r2) => {
347
- if (r2.ok) return r2;
348
- return fail(new Error("[SnapDOM - fetchImage] Fetch failed and no proxy provided"));
349
- });
350
- }
351
- return fail(new Error("[SnapDOM - fetchImage] Fetch failed and no proxy provided"));
352
- });
353
- }
354
- const now = Date.now();
355
- const until = _errorCache.get(src);
356
- if (until && until > now) {
357
- const pr = Promise.reject(new Error("[SnapDOM - fetchImage] Recently failed (cooldown)."));
358
- pr.catch(() => {
359
- });
360
- return pr;
361
- }
362
- if (_inflight.has(src)) return _inflight.get(src);
363
- const crossOriginValue = getCrossOriginMode(src);
364
- if (cache.image.has(src)) return Promise.resolve(cache.image.get(src));
365
- if (src.startsWith("data:image/")) {
366
- cache.image.set(src, src);
367
- return Promise.resolve(src);
368
- }
369
- if (/\.svg(\?.*)?$/i.test(src)) {
370
- const p2 = (() => __async(null, null, function* () {
371
- const direct = yield (() => __async(null, null, function* () {
372
- try {
373
- const res = yield fetch(src, {
374
- mode: "cors",
375
- credentials: crossOriginValue === "use-credentials" ? "include" : "omit"
376
- });
377
- if (!res.ok) return fail(new Error("HTTP " + res.status));
378
- const svgText = yield res.text();
379
- return ok(`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`);
380
- } catch (e) {
381
- return fail(e);
382
- }
383
- }))();
384
- if (direct.ok) {
385
- cache.image.set(src, direct.data);
386
- return direct.data;
387
- }
388
- const via = yield fetchWithFallbackOnceSafe(src);
389
- if (via.ok) {
390
- cache.image.set(src, via.data);
391
- return via.data;
392
- }
393
- _errorCache.set(src, now + errorTTL);
394
- return Promise.reject(via.error);
395
- }))();
396
- _inflight.set(src, p2);
397
- p2.finally(() => _inflight.delete(src));
398
- p2.catch(() => {
399
- });
400
- return p2;
401
- }
402
- const p = new Promise((resolve, reject) => {
403
- let finished = false;
404
- const img = new Image();
405
- const finish = (fn) => (arg) => {
406
- if (finished) return;
407
- finished = true;
408
- clearTimeout(timeoutId);
409
- img.onload = img.onerror = null;
410
- fn(arg);
411
- };
412
- const onSuccess = (d) => {
413
- cache.image.set(src, d);
414
- resolve(d);
415
- };
416
- const onFinalError = (e) => {
417
- _errorCache.set(src, Date.now() + errorTTL);
418
- reject(e);
419
- };
420
- const timeoutId = setTimeout(
421
- finish(() => {
422
- fetchWithFallbackOnceSafe(src).then((r) => {
423
- if (r.ok) onSuccess(r.data);
424
- else onFinalError(new Error("Image load timed out"));
425
- });
426
- }),
427
- timeout
428
- );
429
- img.crossOrigin = crossOriginValue;
430
- img.onload = finish(() => {
431
- Promise.resolve(img.decode()).then(() => {
432
- try {
433
- const canvas = document.createElement("canvas");
434
- canvas.width = img.naturalWidth || img.width;
435
- canvas.height = img.naturalHeight || img.height;
436
- const ctx = canvas.getContext("2d");
437
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
438
- onSuccess(canvas.toDataURL("image/png"));
439
- } catch (e) {
440
- fetchWithFallbackOnceSafe(src).then((r) => {
441
- if (r.ok) onSuccess(r.data);
442
- else onFinalError(r.error);
443
- });
444
- }
445
- }).catch(() => {
446
- fetchWithFallbackOnceSafe(src).then((r) => {
447
- if (r.ok) onSuccess(r.data);
448
- else onFinalError(r.error);
449
- });
450
- });
451
- });
452
- img.onerror = finish(() => {
453
- fetchWithFallbackOnceSafe(src).then((r) => {
454
- if (r.ok) onSuccess(r.data);
455
- else onFinalError(r.error);
456
- });
457
- });
458
- img.src = src;
459
- });
460
- _inflight.set(src, p);
461
- p.finally(() => _inflight.delete(src));
462
- p.catch(() => {
463
- });
464
- return p;
465
- }
466
553
  function snapshotComputedStyle(style) {
467
554
  const snap = {};
468
555
  for (let prop of style) {
@@ -470,36 +557,6 @@
470
557
  }
471
558
  return snap;
472
559
  }
473
- function isSafari() {
474
- return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
475
- }
476
- function stripTranslate(transform) {
477
- if (!transform || transform === "none") return "";
478
- let cleaned = transform.replace(/translate[XY]?\([^)]*\)/g, "");
479
- cleaned = cleaned.replace(/matrix\(([^)]+)\)/g, (_, values) => {
480
- const parts = values.split(",").map((s) => s.trim());
481
- if (parts.length !== 6) return `matrix(${values})`;
482
- parts[4] = "0";
483
- parts[5] = "0";
484
- return `matrix(${parts.join(", ")})`;
485
- });
486
- cleaned = cleaned.replace(/matrix3d\(([^)]+)\)/g, (_, values) => {
487
- const parts = values.split(",").map((s) => s.trim());
488
- if (parts.length !== 16) return `matrix3d(${values})`;
489
- parts[12] = "0";
490
- parts[13] = "0";
491
- return `matrix3d(${parts.join(", ")})`;
492
- });
493
- return cleaned.trim().replace(/\s{2,}/g, " ");
494
- }
495
- function safeEncodeURI(uri) {
496
- if (/%[0-9A-Fa-f]{2}/.test(uri)) return uri;
497
- try {
498
- return encodeURI(uri);
499
- } catch (e) {
500
- return uri;
501
- }
502
- }
503
560
  function splitBackgroundImage(bg) {
504
561
  const parts = [];
505
562
  let depth = 0;
@@ -517,49 +574,209 @@
517
574
  return parts;
518
575
  }
519
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
+
520
590
  // src/modules/styles.js
521
591
  var snapshotCache = /* @__PURE__ */ new WeakMap();
522
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
+ }
523
622
  function snapshotComputedStyleFull(style) {
524
- const result = {};
525
- const computedVisibility = style.getPropertyValue("visibility");
623
+ const out = {};
624
+ const vis = style.getPropertyValue("visibility");
526
625
  for (let i = 0; i < style.length; i++) {
527
626
  const prop = style[i];
528
627
  let val = style.getPropertyValue(prop);
529
628
  if ((prop === "background-image" || prop === "content") && val.includes("url(") && !val.includes("data:")) {
530
629
  val = "none";
531
630
  }
532
- result[prop] = val;
631
+ out[prop] = val;
533
632
  }
534
- if (computedVisibility === "hidden") {
535
- 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
+ };
669
+ }
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
+ };
683
+ }
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
+ });
710
+ }
711
+
712
+ // src/core/clone.js
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) {
536
770
  }
537
- return result;
771
+ return css;
538
772
  }
539
- function inlineAllStyles(source, clone, styleMap, cache2, compress) {
540
- var _a;
541
- if (source.tagName === "STYLE") return;
542
- if (!cache2.has(source)) {
543
- cache2.set(source, getStyle(source));
544
- }
545
- const style = cache2.get(source);
546
- if (!snapshotCache.has(source)) {
547
- const snapshot2 = snapshotComputedStyleFull(style);
548
- snapshotCache.set(source, snapshot2);
549
- }
550
- const snapshot = snapshotCache.get(source);
551
- const hash = Object.entries(snapshot).sort(([a], [b]) => a.localeCompare(b)).map(([prop, val]) => `${prop}:${val}`).join(";");
552
- if (snapshotKeyCache.has(hash)) {
553
- styleMap.set(clone, snapshotKeyCache.get(hash));
554
- return;
555
- }
556
- const tagName = ((_a = source.tagName) == null ? void 0 : _a.toLowerCase()) || "div";
557
- const key = getStyleKey(snapshot, tagName, compress);
558
- snapshotKeyCache.set(hash, key);
559
- styleMap.set(clone, key);
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);
560
779
  }
561
-
562
- // src/core/clone.js
563
780
  function freezeImgSrcset(original, cloned) {
564
781
  try {
565
782
  const chosen = original.currentSrc || original.src || "";
@@ -572,11 +789,62 @@
572
789
  } catch (e) {
573
790
  }
574
791
  }
575
- function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {}, originalRoot) {
576
- var _a, _b;
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;
577
836
  if (!node) throw new Error("Invalid node");
578
837
  const clonedAssignedNodes = /* @__PURE__ */ new Set();
579
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
+ }
580
848
  if (node.nodeType === Node.TEXT_NODE) {
581
849
  return node.cloneNode(true);
582
850
  }
@@ -605,7 +873,7 @@
605
873
  }
606
874
  if (typeof options.filter === "function") {
607
875
  try {
608
- if (!options.filter(node, originalRoot || node)) {
876
+ if (!options.filter(node)) {
609
877
  const spacer = document.createElement("div");
610
878
  const rect = node.getBoundingClientRect();
611
879
  spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
@@ -622,8 +890,8 @@
622
890
  }
623
891
  if (node.getAttribute("data-capture") === "placeholder") {
624
892
  const clone2 = node.cloneNode(false);
625
- nodeMap.set(clone2, node);
626
- inlineAllStyles(node, clone2, styleMap, styleCache, compress);
893
+ sessionCache.nodeMap.set(clone2, node);
894
+ inlineAllStyles(node, clone2, sessionCache, options);
627
895
  const placeholder = document.createElement("div");
628
896
  placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
629
897
  placeholder.style.cssText = `color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;`;
@@ -636,14 +904,14 @@
636
904
  img.src = dataURL;
637
905
  img.width = node.width;
638
906
  img.height = node.height;
639
- nodeMap.set(img, node);
640
- inlineAllStyles(node, img, styleMap, styleCache, compress);
907
+ sessionCache.nodeMap.set(img, node);
908
+ inlineAllStyles(node, img, sessionCache, options);
641
909
  return img;
642
910
  }
643
911
  let clone;
644
912
  try {
645
913
  clone = node.cloneNode(false);
646
- nodeMap.set(clone, node);
914
+ sessionCache.nodeMap.set(clone, node);
647
915
  if (node.tagName === "IMG") {
648
916
  freezeImgSrcset(node, clone);
649
917
  }
@@ -671,47 +939,56 @@
671
939
  if (node instanceof HTMLSelectElement) {
672
940
  pendingSelectValue = node.value;
673
941
  }
674
- inlineAllStyles(node, clone, styleMap, styleCache, compress);
942
+ inlineAllStyles(node, clone, sessionCache, options);
675
943
  if (node.shadowRoot) {
676
- const hasSlot = Array.from(node.shadowRoot.querySelectorAll("slot")).length > 0;
677
- if (hasSlot) {
678
- for (const child of node.shadowRoot.childNodes) {
679
- if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
680
- const cssText = child.textContent || "";
681
- if (cssText.trim() && compress) {
682
- styleCache.set(child, cssText);
683
- }
684
- }
685
- }
686
- } else {
687
- const shadowFrag = document.createDocumentFragment();
688
- for (const child of node.shadowRoot.childNodes) {
689
- if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
690
- const cssText = child.textContent || "";
691
- if (cssText.trim() && compress) {
692
- styleCache.set(child, cssText);
693
- }
694
- 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)) || [];
695
952
  }
696
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
697
- if (clonedChild) shadowFrag.appendChild(clonedChild);
953
+ for (const an of assigned) clonedAssignedNodes.add(an);
698
954
  }
699
- 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) {
700
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);
701
975
  }
702
976
  if (node.tagName === "SLOT") {
703
- 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 })) || [];
704
978
  const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
705
979
  const fragment = document.createDocumentFragment();
706
980
  for (const child of nodesToClone) {
707
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
708
- if (clonedChild) fragment.appendChild(clonedChild);
981
+ const clonedChild = deepClone(child, sessionCache, options);
982
+ if (clonedChild) {
983
+ markSlottedSubtree(clonedChild);
984
+ fragment.appendChild(clonedChild);
985
+ }
709
986
  }
710
987
  return fragment;
711
988
  }
712
989
  for (const child of node.childNodes) {
713
990
  if (clonedAssignedNodes.has(child)) continue;
714
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
991
+ const clonedChild = deepClone(child, sessionCache, options);
715
992
  if (clonedChild) clone.appendChild(clonedChild);
716
993
  }
717
994
  if (pendingSelectValue !== null && clone instanceof HTMLSelectElement) {
@@ -754,7 +1031,7 @@
754
1031
  }
755
1032
  }
756
1033
  }
757
- function isIconFont(input) {
1034
+ function isIconFont2(input) {
758
1035
  const text = typeof input === "string" ? input : "";
759
1036
  const candidates = [...defaultIconFonts, ...userIconFonts];
760
1037
  for (const rx of candidates) {
@@ -803,249 +1080,630 @@
803
1080
  };
804
1081
  });
805
1082
  }
806
- function isStylesheetLoaded(href) {
807
- return Array.from(document.styleSheets).some((sheet) => sheet.href === href);
808
- }
809
- function injectLinkIfMissing(href) {
810
- return new Promise((resolve) => {
811
- if (isStylesheetLoaded(href)) return resolve(null);
812
- const link = document.createElement("link");
813
- link.rel = "stylesheet";
814
- link.href = href;
815
- link.setAttribute("data-snapdom", "injected-import");
816
- link.onload = () => resolve(link);
817
- link.onerror = () => resolve(null);
818
- document.head.appendChild(link);
819
- });
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;
820
1187
  }
821
1188
  function embedCustomFonts() {
822
- return __async(this, arguments, function* ({ preCached = false, localFonts = [], useProxy = "" } = {}) {
823
- if (cache.resource.has("fonts-embed-css")) {
824
- if (preCached) {
825
- const style = document.createElement("style");
826
- style.setAttribute("data-snapdom", "embedFonts");
827
- style.textContent = cache.resource.get("fonts-embed-css");
828
- 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
+ }
829
1228
  }
830
- return cache.resource.get("fonts-embed-css");
831
- }
832
- const loadedFonts = /* @__PURE__ */ new Set();
833
- try {
834
- for (const f of document.fonts) {
835
- if (f.status === "loaded") {
836
- loadedFonts.add(`${f.family}__${f.weight || "normal"}__${f.style || "normal"}`);
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
+ }
837
1238
  }
838
1239
  }
839
- } catch (e) {
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;
840
1268
  }
841
- const importRegex = /@import\s+url\(["']?([^"')]+)["']?\)/g;
842
- const styleImports = [];
843
- for (const styleTag of document.querySelectorAll("style")) {
844
- const cssText = styleTag.textContent || "";
845
- const matches = Array.from(cssText.matchAll(importRegex));
846
- for (const match of matches) {
847
- const importUrl = match[1];
848
- if (isIconFont(importUrl)) continue;
849
- if (!isStylesheetLoaded(importUrl)) {
850
- styleImports.push(importUrl);
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
+ }
851
1288
  }
1289
+ urls.push(u);
852
1290
  }
1291
+ return urls;
853
1292
  }
854
- yield Promise.all(styleImports.map(injectLinkIfMissing));
855
- const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter((link) => link.href);
856
- let finalCSS = "";
857
- for (const link of links) {
858
- try {
859
- const res = yield fetchResource(link.href, { useProxy });
860
- const cssText = yield res.text();
861
- if (isIconFont(link.href) || isIconFont(cssText)) continue;
862
- const faceRegex = /@font-face[^{}]*{[^}]*}/g;
863
- let cssFinal = cssText;
864
- for (const face of cssText.match(faceRegex) || []) {
865
- const famMatch = face.match(/font-family:\s*([^;]+);/i);
866
- if (!famMatch) continue;
867
- const family = famMatch[1].replace(/['"]/g, "").trim();
868
- const weightMatch = face.match(/font-weight:\s*([^;]+);/i);
869
- const styleMatch = face.match(/font-style:\s*([^;]+);/i);
870
- const weight = weightMatch ? weightMatch[1].trim() : "normal";
871
- const style = styleMatch ? styleMatch[1].trim() : "normal";
872
- const key = `${family}__${weight}__${style}`;
873
- const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
874
- const hasURL = /url\(/i.test(face);
875
- const hasLocal = /local\(/i.test(face);
876
- if (!hasURL && hasLocal) {
877
- continue;
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
+ }
878
1306
  }
879
- if (!loadedFonts.has(key)) {
880
- cssFinal = cssFinal.replace(face, "");
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)})`);
881
1311
  continue;
882
1312
  }
883
- let inlined = face;
884
- const matches = Array.from(face.matchAll(urlRegex));
885
- for (const match of matches) {
886
- let rawUrl = extractURL(match[0]);
887
- if (!rawUrl) continue;
888
- let url = rawUrl;
889
- if (!url.startsWith("http") && !url.startsWith("data:")) {
890
- url = new URL(url, link.href).href;
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})`);
891
1321
  }
892
- if (isIconFont(url)) continue;
893
- if (cache.resource.has(url)) {
894
- cache.font.add(url);
895
- inlined = inlined.replace(match[0], `url(${cache.resource.get(url)})`);
896
- continue;
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) {
897
1355
  }
898
- if (cache.font.has(url)) continue;
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
+ }
1394
+ }
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;
1414
+ }
1415
+ const requiredFamilies = familiesFromRequired(required);
1416
+ const importUrls = [];
1417
+ for (const styleTag of document.querySelectorAll("style")) {
1418
+ const cssText = styleTag.textContent || "";
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);
1424
+ }
1425
+ }
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
+ }
1438
+ let finalCSS = "";
1439
+ const linkNodes = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter((l) => !!l.href);
1440
+ for (const link of linkNodes) {
1441
+ try {
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) {
899
1455
  try {
900
- const fontRes = yield fetchResource(url, { useProxy });
901
- const blob = yield fontRes.blob();
902
- const b64 = yield new Promise((resolve) => {
903
- const reader = new FileReader();
904
- reader.onload = () => resolve(reader.result);
905
- reader.readAsDataURL(blob);
906
- });
907
- cache.resource.set(url, b64);
908
- cache.font.add(url);
909
- inlined = inlined.replace(match[0], `url(${b64})`);
1456
+ const rules = sheet.cssRules || [];
1457
+ cssText = Array.from(rules).map((r) => r.cssText).join("");
910
1458
  } catch (e) {
911
- console.warn("[snapdom] Failed to fetch font resource:", url);
912
1459
  }
913
1460
  }
914
- cssFinal = cssFinal.replace(face, inlined);
915
1461
  }
916
- finalCSS += cssFinal + "\n";
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 + "";
1493
+ }
1494
+ if (facesOut.trim()) finalCSS += facesOut + "";
917
1495
  } catch (e) {
918
- console.warn("[snapdom] Failed to fetch CSS:", link.href);
1496
+ console.warn("[snapdom] Failed to process stylesheet:", link.href);
919
1497
  }
920
1498
  }
921
1499
  for (const sheet of document.styleSheets) {
922
1500
  try {
923
- if (!sheet.href || links.every((link) => link.href !== sheet.href)) {
924
- for (const rule of sheet.cssRules) {
925
- if (rule.type === CSSRule.FONT_FACE_RULE) {
926
- const src = rule.style.getPropertyValue("src");
927
- const family = rule.style.getPropertyValue("font-family");
928
- if (!src || isIconFont(family)) continue;
929
- const weightVal = rule.style.getPropertyValue("font-weight") || "normal";
930
- const styleVal = rule.style.getPropertyValue("font-style") || "normal";
931
- const key = `${family}__${weightVal}__${styleVal}`;
932
- const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
933
- const localRegex = /local\((["']?)[^)]+?\1\)/g;
934
- const hasURL = !!src.match(urlRegex);
935
- const hasLocal = !!src.match(localRegex);
936
- if (!hasURL && hasLocal) {
937
- finalCSS += `@font-face{font-family:${family};src:${src};font-style:${styleVal};font-weight:${weightVal};}`;
938
- continue;
939
- }
940
- if (!loadedFonts.has(key)) continue;
941
- let inlinedSrc = src;
942
- const matches = Array.from(src.matchAll(urlRegex));
943
- for (const match of matches) {
944
- let rawUrl = match[2].trim();
945
- if (!rawUrl) continue;
946
- let url = rawUrl;
947
- if (!url.startsWith("http") && !url.startsWith("data:")) {
948
- url = new URL(url, sheet.href || location.href).href;
949
- }
950
- if (isIconFont(url)) continue;
951
- if (cache.resource.has(url)) {
952
- cache.font.add(url);
953
- inlinedSrc = inlinedSrc.replace(match[0], `url(${cache.resource.get(url)})`);
954
- continue;
955
- }
956
- if (cache.font.has(url)) continue;
957
- try {
958
- const res = yield fetchResource(url, { useProxy });
959
- const blob = yield res.blob();
960
- const b64 = yield new Promise((resolve) => {
961
- const reader = new FileReader();
962
- reader.onload = () => resolve(reader.result);
963
- reader.readAsDataURL(blob);
964
- });
965
- cache.resource.set(url, b64);
966
- cache.font.add(url);
967
- inlinedSrc = inlinedSrc.replace(match[0], `url(${b64})`);
968
- } catch (e) {
969
- console.warn("[snapdom] Failed to fetch font URL:", url);
970
- }
971
- }
972
- finalCSS += `@font-face{font-family:${family};src:${inlinedSrc};font-style:${styleVal};font-weight:${weightVal};}`;
973
- }
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};` : ""}}`;
974
1524
  }
975
1525
  }
976
1526
  } catch (e) {
977
- console.warn("[snapdom] Cannot access stylesheet", sheet.href, e);
978
1527
  }
979
1528
  }
980
- for (const font of document.fonts) {
981
- if (font.family && font.status === "loaded" && font._snapdomSrc) {
982
- if (isIconFont(font.family)) continue;
983
- let b64 = font._snapdomSrc;
984
- if (!b64.startsWith("data:")) {
985
- if (cache.resource.has(font._snapdomSrc)) {
986
- b64 = cache.resource.get(font._snapdomSrc);
987
- cache.font.add(font._snapdomSrc);
988
- } 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))) {
989
1544
  try {
990
- const res = yield fetchResource(font._snapdomSrc, { useProxy });
991
- const blob = yield res.blob();
992
- b64 = yield new Promise((resolve) => {
993
- const reader = new FileReader();
994
- reader.onload = () => resolve(reader.result);
995
- reader.readAsDataURL(blob);
996
- });
997
- cache.resource.set(font._snapdomSrc, b64);
998
- 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
+ }
999
1553
  } catch (e) {
1000
- console.warn("[snapdom] Failed to fetch dynamic font src:", font._snapdomSrc);
1554
+ console.warn("[snapdom] Failed to fetch dynamic font src:", f._snapdomSrc);
1001
1555
  continue;
1002
1556
  }
1003
1557
  }
1004
1558
  }
1005
- 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"};}`;
1006
1560
  }
1561
+ } catch (e) {
1007
1562
  }
1008
1563
  for (const font of localFonts) {
1009
1564
  if (!font || typeof font !== "object") continue;
1010
- const { family, src, weight = "normal", style = "normal" } = font;
1011
- if (!family || !src) 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 || "");
1012
1573
  let b64 = src;
1013
1574
  if (!b64.startsWith("data:")) {
1014
- try {
1015
- const res = yield fetchResource(src, { useProxy });
1016
- const blob = yield res.blob();
1017
- b64 = yield new Promise((resolve) => {
1018
- const reader = new FileReader();
1019
- reader.onload = () => resolve(reader.result);
1020
- reader.readAsDataURL(blob);
1021
- });
1022
- cache.resource.set(src, b64);
1023
- cache.font.add(src);
1024
- } catch (e) {
1025
- console.warn("[snapdom] Failed to load local font:", src);
1026
- continue;
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
+ }
1027
1592
  }
1028
- } else {
1029
- cache.resource.set(src, b64);
1030
- cache.font.add(src);
1031
1593
  }
1032
- finalCSS += `@font-face{font-family:'${family}';src:url(${b64});font-style:${style};font-weight:${weight};}`;
1594
+ finalCSS += `@font-face{font-family:'${family}';src:url(${b64});font-style:${style};font-weight:${weight};font-stretch:${stretch};}`;
1033
1595
  }
1034
1596
  if (finalCSS) {
1035
- cache.resource.set("fonts-embed-css", finalCSS);
1036
- if (preCached) {
1037
- const style = document.createElement("style");
1038
- style.setAttribute("data-snapdom", "embedFonts");
1039
- style.textContent = finalCSS;
1040
- document.head.appendChild(style);
1041
- }
1597
+ finalCSS = dedupeFontFaces(finalCSS);
1598
+ (_r = cache.resource) == null ? void 0 : _r.set(cacheKey, finalCSS);
1042
1599
  }
1043
1600
  return finalCSS;
1044
1601
  });
1045
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
+ }
1046
1704
 
1047
1705
  // src/modules/pseudo.js
1048
- function inlinePseudoElements(source, clone, styleMap, styleCache, options) {
1706
+ function inlinePseudoElements(source, clone, sessionCache, options) {
1049
1707
  return __async(this, null, function* () {
1050
1708
  var _a;
1051
1709
  if (!(source instanceof Element) || !(clone instanceof Element)) return;
@@ -1075,8 +1733,8 @@
1075
1733
  span.textContent = first;
1076
1734
  span.dataset.snapdomPseudo = "::first-letter";
1077
1735
  const snapshot2 = snapshotComputedStyle(style);
1078
- const key2 = getStyleKey(snapshot2, "span", options);
1079
- styleMap.set(span, key2);
1736
+ const key2 = getStyleKey(snapshot2, "span");
1737
+ sessionCache.styleMap.set(span, key2);
1080
1738
  const restNode = document.createTextNode(rest);
1081
1739
  clone.replaceChild(restNode, textNode);
1082
1740
  clone.insertBefore(span, restNode);
@@ -1096,7 +1754,7 @@
1096
1754
  const borderStyle = style.borderStyle;
1097
1755
  const borderWidth = parseFloat(style.borderWidth);
1098
1756
  const transform = style.transform;
1099
- const isIconFont2 = isIconFont(fontFamily);
1757
+ const isIconFont22 = isIconFont2(fontFamily);
1100
1758
  const hasExplicitContent = content !== "none" && cleanContent !== "";
1101
1759
  const hasBg = bg && bg !== "none";
1102
1760
  const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
@@ -1109,9 +1767,9 @@
1109
1767
  pseudoEl.dataset.snapdomPseudo = pseudo;
1110
1768
  pseudoEl.style.verticalAlign = "middle";
1111
1769
  const snapshot = snapshotComputedStyle(style);
1112
- const key = getStyleKey(snapshot, "span", options);
1113
- styleMap.set(pseudoEl, key);
1114
- if (isIconFont2 && cleanContent.length === 1) {
1770
+ const key = getStyleKey(snapshot, "span");
1771
+ sessionCache.styleMap.set(pseudoEl, key);
1772
+ if (isIconFont22 && cleanContent.length === 1) {
1115
1773
  const { dataUrl, width: width2, height: height2 } = yield iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
1116
1774
  const imgEl = document.createElement("img");
1117
1775
  imgEl.src = dataUrl;
@@ -1123,15 +1781,15 @@
1123
1781
  if (rawUrl == null ? void 0 : rawUrl.trim()) {
1124
1782
  try {
1125
1783
  const imgEl = document.createElement("img");
1126
- const dataUrl = yield fetchImage(safeEncodeURI(rawUrl), options);
1127
- imgEl.src = dataUrl;
1784
+ const dataUrl = yield snapFetch(safeEncodeURI(rawUrl), { as: "dataURL", useProxy: options.useProxy });
1785
+ imgEl.src = dataUrl.data;
1128
1786
  imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
1129
1787
  pseudoEl.appendChild(imgEl);
1130
1788
  } catch (e) {
1131
1789
  console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
1132
1790
  }
1133
1791
  }
1134
- } else if (!isIconFont2 && hasExplicitContent) {
1792
+ } else if (!isIconFont22 && hasExplicitContent) {
1135
1793
  pseudoEl.textContent = cleanContent;
1136
1794
  }
1137
1795
  if (hasBg) {
@@ -1159,7 +1817,7 @@
1159
1817
  const sChildren = Array.from(source.children);
1160
1818
  const cChildren = Array.from(clone.children).filter((child) => !child.dataset.snapdomPseudo);
1161
1819
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
1162
- yield inlinePseudoElements(sChildren[i], cChildren[i], styleMap, styleCache, options);
1820
+ yield inlinePseudoElements(sChildren[i], cChildren[i], sessionCache, options);
1163
1821
  }
1164
1822
  });
1165
1823
  }
@@ -1216,13 +1874,16 @@
1216
1874
 
1217
1875
  // src/core/prepare.js
1218
1876
  function prepareClone(_0) {
1219
- return __async(this, arguments, function* (element, compress = false, embedFonts = false, options = {}) {
1877
+ return __async(this, arguments, function* (element, options = {}) {
1220
1878
  var _a, _b;
1221
- const styleMap = /* @__PURE__ */ new Map();
1222
- const styleCache = /* @__PURE__ */ new WeakMap();
1223
- const nodeMap = /* @__PURE__ */ new Map();
1879
+ const sessionCache = {
1880
+ styleMap: cache.session.styleMap,
1881
+ styleCache: cache.session.styleCache,
1882
+ nodeMap: cache.session.nodeMap
1883
+ };
1224
1884
  let clone;
1225
1885
  let classCSS = "";
1886
+ let shadowScopedCSS = "";
1226
1887
  stabilizeLayout(element);
1227
1888
  try {
1228
1889
  inlineExternalDefsAndSymbols(element);
@@ -1230,43 +1891,45 @@
1230
1891
  console.warn("inlineExternal defs or symbol failed:", e);
1231
1892
  }
1232
1893
  try {
1233
- clone = deepClone(element, styleMap, styleCache, nodeMap, compress, options, element);
1894
+ clone = deepClone(element, sessionCache, options, element);
1234
1895
  } catch (e) {
1235
1896
  console.warn("deepClone failed:", e);
1236
1897
  throw e;
1237
1898
  }
1238
1899
  try {
1239
- yield inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts, options.useProxy);
1900
+ yield inlinePseudoElements(element, clone, sessionCache, options);
1240
1901
  } catch (e) {
1241
1902
  console.warn("inlinePseudoElements failed:", e);
1242
1903
  }
1243
1904
  yield resolveBlobUrlsInTree(clone);
1244
- if (compress) {
1245
- const keyToClass = generateCSSClasses(styleMap);
1246
- classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
1247
- for (const [node, key] of styleMap.entries()) {
1248
- if (node.tagName === "STYLE") continue;
1249
- if (node.getRootNode && node.getRootNode() instanceof ShadowRoot) {
1250
- node.setAttribute("style", key.replace(/;/g, "; "));
1251
- continue;
1252
- }
1253
- const className = keyToClass.get(key);
1254
- if (className) node.classList.add(className);
1255
- const bgImage = (_a = node.style) == null ? void 0 : _a.backgroundImage;
1256
- const hasIcon = (_b = node.dataset) == null ? void 0 : _b.snapdomHasIcon;
1257
- if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
1258
- if (hasIcon) {
1259
- node.style.verticalAlign = "middle";
1260
- node.style.display = "inline";
1261
- }
1905
+ try {
1906
+ const styleNodes = clone.querySelectorAll("style[data-sd]");
1907
+ for (const s of styleNodes) {
1908
+ shadowScopedCSS += s.textContent || "";
1909
+ s.remove();
1262
1910
  }
1263
- } else {
1264
- for (const [node, key] of styleMap.entries()) {
1265
- 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) {
1266
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";
1267
1930
  }
1268
1931
  }
1269
- for (const [cloneNode, originalNode] of nodeMap.entries()) {
1932
+ for (const [cloneNode, originalNode] of sessionCache.nodeMap.entries()) {
1270
1933
  const scrollX = originalNode.scrollLeft;
1271
1934
  const scrollY = originalNode.scrollTop;
1272
1935
  const hasScroll = scrollX || scrollY;
@@ -1285,9 +1948,9 @@
1285
1948
  cloneNode.appendChild(inner);
1286
1949
  }
1287
1950
  }
1288
- if (element === nodeMap.get(clone)) {
1289
- const computed = styleCache.get(element) || window.getComputedStyle(element);
1290
- styleCache.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);
1291
1954
  const transform = stripTranslate(computed.transform);
1292
1955
  clone.style.margin = "0";
1293
1956
  clone.style.position = "static";
@@ -1300,13 +1963,13 @@
1300
1963
  clone.style.clear = "none";
1301
1964
  clone.style.transform = transform || "";
1302
1965
  }
1303
- for (const [cloneNode, originalNode] of nodeMap.entries()) {
1966
+ for (const [cloneNode, originalNode] of sessionCache.nodeMap.entries()) {
1304
1967
  if (originalNode.tagName === "PRE") {
1305
1968
  cloneNode.style.marginTop = "0";
1306
1969
  cloneNode.style.marginBlockStart = "0";
1307
1970
  }
1308
1971
  }
1309
- return { clone, classCSS, styleCache };
1972
+ return { clone, classCSS, styleCache: sessionCache.styleCache };
1310
1973
  });
1311
1974
  }
1312
1975
  function stabilizeLayout(element) {
@@ -1324,22 +1987,27 @@
1324
1987
  var _blobToDataUrlCache = /* @__PURE__ */ new Map();
1325
1988
  function blobUrlToDataUrl(blobUrl) {
1326
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);
1327
1992
  if (_blobToDataUrlCache.has(blobUrl)) return _blobToDataUrlCache.get(blobUrl);
1328
- const res = yield fetch(blobUrl);
1329
- if (!res.ok) throw new Error(`[SnapDOM] HTTP ${res.status} on blob fetch (${blobUrl})`);
1330
- const blob = yield res.blob();
1331
- const dataUrl = yield new Promise((resolve, reject) => {
1332
- const fr = new FileReader();
1333
- fr.onloadend = () => {
1334
- const v = fr.result;
1335
- if (typeof v === "string" && v.startsWith("data:")) resolve(v);
1336
- else reject(new Error("[SnapDOM] Invalid data URL from blob"));
1337
- };
1338
- fr.onerror = () => reject(new Error("[SnapDOM] FileReader error"));
1339
- fr.readAsDataURL(blob);
1340
- });
1341
- _blobToDataUrlCache.set(blobUrl, dataUrl);
1342
- return dataUrl;
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
+ }
1343
2011
  });
1344
2012
  }
1345
2013
  var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
@@ -1463,18 +2131,31 @@
1463
2131
  }
1464
2132
  img.removeAttribute("srcset");
1465
2133
  img.removeAttribute("sizes");
1466
- const src = img.src;
1467
- try {
1468
- const dataUrl = yield fetchImage(src, { useProxy: options.useProxy });
1469
- img.src = dataUrl;
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;
1470
2139
  if (!img.width) img.width = img.naturalWidth || 100;
1471
2140
  if (!img.height) img.height = img.naturalHeight || 100;
1472
- } catch (e) {
1473
- const fallback = document.createElement("div");
1474
- 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;`;
1475
- fallback.innerText = "img";
1476
- img.replaceWith(fallback);
2141
+ return;
1477
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);
1478
2159
  });
1479
2160
  for (let i = 0; i < imgs.length; i += 4) {
1480
2161
  const group = imgs.slice(i, i + 4).map(processImg);
@@ -1526,10 +2207,6 @@
1526
2207
  cloneNode.style.setProperty(prop, inlined.join(", "));
1527
2208
  }
1528
2209
  }
1529
- const bgColor = style.getPropertyValue("background-color");
1530
- if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") {
1531
- cloneNode.style.backgroundColor = bgColor;
1532
- }
1533
2210
  const sChildren = Array.from(srcNode.children);
1534
2211
  const cChildren = Array.from(cloneNode.children);
1535
2212
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
@@ -1540,17 +2217,17 @@
1540
2217
  }
1541
2218
 
1542
2219
  // src/core/capture.js
1543
- function captureDOM(_0) {
1544
- return __async(this, arguments, function* (element, options = {}) {
2220
+ function captureDOM(element, options) {
2221
+ return __async(this, null, function* () {
1545
2222
  if (!element) throw new Error("Element cannot be null or undefined");
1546
- cache.reset();
1547
- const { compress = true, embedFonts = false, fast = true, scale = 1, useProxy = "", localFonts = [] } = options;
2223
+ applyCachePolicy(options.cache);
2224
+ const fast = options.fast;
1548
2225
  let clone, classCSS, styleCache;
1549
2226
  let fontsCSS = "";
1550
2227
  let baseCSS = "";
1551
2228
  let dataURL;
1552
2229
  let svgString;
1553
- ({ clone, classCSS, styleCache } = yield prepareClone(element, compress, embedFonts, options));
2230
+ ({ clone, classCSS, styleCache } = yield prepareClone(element, options));
1554
2231
  yield new Promise((resolve) => {
1555
2232
  idle(() => __async(null, null, function* () {
1556
2233
  yield inlineImages(clone, options);
@@ -1563,28 +2240,34 @@
1563
2240
  resolve();
1564
2241
  }), { fast });
1565
2242
  });
1566
- if (embedFonts) {
2243
+ if (options.embedFonts) {
1567
2244
  yield new Promise((resolve) => {
1568
2245
  idle(() => __async(null, null, function* () {
1569
- fontsCSS = yield embedCustomFonts({ localFonts, useProxy });
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 });
1570
2255
  resolve();
1571
2256
  }), { fast });
1572
2257
  });
1573
2258
  }
1574
- if (compress) {
1575
- const usedTags = collectUsedTagNames(clone).sort();
1576
- const tagKey = usedTags.join(",");
1577
- if (cache.baseStyle.has(tagKey)) {
1578
- baseCSS = cache.baseStyle.get(tagKey);
1579
- } else {
1580
- yield new Promise((resolve) => {
1581
- idle(() => {
1582
- baseCSS = generateDedupedBaseCSS(usedTags);
1583
- cache.baseStyle.set(tagKey, baseCSS);
1584
- resolve();
1585
- }, { fast });
1586
- });
1587
- }
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
+ });
1588
2271
  }
1589
2272
  yield new Promise((resolve) => {
1590
2273
  idle(() => {
@@ -1643,26 +2326,77 @@
1643
2326
  });
1644
2327
  }
1645
2328
 
1646
- // src/api/snapdom.js
1647
- function toImg(_0) {
1648
- 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* () {
1649
2381
  const img = new Image();
1650
2382
  img.src = url;
1651
2383
  yield img.decode();
1652
- if (scale !== 1) {
1653
- img.style.width = `${img.naturalWidth * scale}px`;
1654
- 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`;
1655
2387
  }
1656
2388
  return img;
1657
2389
  });
1658
2390
  }
1659
- function toCanvas(_0) {
1660
- 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* () {
1661
2395
  const img = new Image();
1662
- img.src = url;
1663
2396
  img.crossOrigin = "anonymous";
1664
2397
  img.loading = "eager";
1665
2398
  img.decoding = "sync";
2399
+ img.src = url;
1666
2400
  const isSafariBrowser = isSafari();
1667
2401
  let appended = false;
1668
2402
  if (isSafariBrowser) {
@@ -1670,16 +2404,11 @@
1670
2404
  appended = true;
1671
2405
  }
1672
2406
  yield img.decode();
1673
- if (isSafariBrowser) {
1674
- yield new Promise((resolve) => setTimeout(resolve, 100));
1675
- }
1676
- if (img.width === 0 || img.height === 0) {
1677
- if (appended) img.remove();
1678
- throw new Error("Image failed to load or has no dimensions");
1679
- }
1680
- const width = img.naturalWidth * scale;
1681
- 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;
1682
2410
  const canvas = document.createElement("canvas");
2411
+ const dpr = options.dpr;
1683
2412
  canvas.width = Math.ceil(width * dpr);
1684
2413
  canvas.height = Math.ceil(height * dpr);
1685
2414
  canvas.style.width = `${width}px`;
@@ -1691,180 +2420,144 @@
1691
2420
  return canvas;
1692
2421
  });
1693
2422
  }
1694
- function toBlob(_0) {
1695
- return __async(this, arguments, function* (url, {
1696
- type = "svg",
1697
- scale = 1,
1698
- backgroundColor = "#fff",
1699
- quality
1700
- } = {}) {
1701
- const mime = {
1702
- jpg: "image/jpeg",
1703
- jpeg: "image/jpeg",
1704
- png: "image/png",
1705
- webp: "image/webp"
1706
- }[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;
1707
2428
  if (type === "svg") {
1708
2429
  const svgText = decodeURIComponent(url.split(",")[1]);
1709
2430
  return new Blob([svgText], { type: "image/svg+xml" });
1710
2431
  }
1711
- const canvas = yield createBackground(url, { dpr: 1, scale }, backgroundColor);
1712
- return new Promise((resolve) => {
1713
- canvas.toBlob((blob) => resolve(blob), `${mime}`, quality);
1714
- });
1715
- });
1716
- }
1717
- function createBackground(_0, _1, _2) {
1718
- return __async(this, arguments, function* (url, { dpr = 1, scale = 1 }, backgroundColor) {
1719
- const baseCanvas = yield toCanvas(url, { dpr, scale });
1720
- if (!backgroundColor) return baseCanvas;
1721
- const temp = document.createElement("canvas");
1722
- temp.width = baseCanvas.width;
1723
- temp.height = baseCanvas.height;
1724
- const ctx = temp.getContext("2d");
1725
- ctx.fillStyle = backgroundColor;
1726
- ctx.fillRect(0, 0, temp.width, temp.height);
1727
- ctx.drawImage(baseCanvas, 0, 0);
1728
- 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
+ );
1729
2441
  });
1730
2442
  }
1731
- function toRasterImg(_0, _1) {
1732
- return __async(this, arguments, function* (url, { dpr = 1, scale = 1, backgroundColor, quality }, format = "png") {
1733
- const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
1734
- const finalBg = backgroundColor != null ? backgroundColor : defaultBg;
1735
- 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;
1736
2449
  const img = new Image();
1737
- img.src = canvas.toDataURL(`image/${format}`, quality);
2450
+ img.src = finalCanvas.toDataURL(`image/${options.format}`, options.quality);
1738
2451
  yield img.decode();
1739
- img.style.width = `${canvas.width / dpr}px`;
1740
- 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`;
1741
2454
  return img;
1742
2455
  });
1743
2456
  }
1744
- function download(_0) {
1745
- return __async(this, arguments, function* (url, { dpr = 1, scale = 1, backgroundColor, format = "png", filename = "snapDOM" } = {}) {
1746
- if (format === "svg") {
1747
- 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" }));
1748
2463
  const objectURL = URL.createObjectURL(blob);
1749
2464
  const a2 = document.createElement("a");
1750
2465
  a2.href = objectURL;
1751
- a2.download = `${filename}.svg`;
2466
+ a2.download = options.filename;
1752
2467
  a2.click();
1753
2468
  URL.revokeObjectURL(objectURL);
1754
2469
  return;
1755
2470
  }
1756
- const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
1757
- const finalBg = backgroundColor != null ? backgroundColor : defaultBg;
1758
- const canvas = yield createBackground(url, { dpr, scale }, finalBg);
1759
- const mime = {
1760
- jpg: "image/jpeg",
1761
- jpeg: "image/jpeg",
1762
- png: "image/png",
1763
- webp: "image/webp"
1764
- }[format] || "image/png";
1765
- const dataURL = canvas.toDataURL(mime);
2471
+ const canvas = yield toCanvas(url, options);
2472
+ const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
1766
2473
  const a = document.createElement("a");
1767
- a.href = dataURL;
1768
- a.download = `${filename}.${format}`;
2474
+ a.href = finalCanvas.toDataURL(`image/${options.format}`, options.quality);
2475
+ a.download = options.filename;
1769
2476
  a.click();
1770
2477
  });
1771
2478
  }
1772
- function snapdom(_0) {
1773
- return __async(this, arguments, function* (element, options = {}) {
1774
- 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* () {
1775
2484
  if (!element) throw new Error("Element cannot be null or undefined");
1776
- if (options.iconFonts) {
1777
- extendIconFonts(options.iconFonts);
1778
- }
1779
- 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);
1780
2488
  });
1781
2489
  }
1782
- snapdom.capture = (_0, ..._1) => __async(null, [_0, ..._1], function* (el, options = {}) {
1783
- var _a;
1784
- const url = yield captureDOM(el, options);
1785
- const dpr = (_a = options.dpr) != null ? _a : window.devicePixelRatio || 1;
1786
- 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 })));
1787
2497
  return {
1788
2498
  url,
1789
- options,
1790
2499
  toRaw: () => url,
1791
- toImg: (opts = {}) => toImg(url, __spreadValues({ dpr, scale }, opts)),
1792
- toCanvas: (opts = {}) => toCanvas(url, __spreadValues({ dpr, scale }, opts)),
1793
- toBlob: (opts = {}) => toBlob(url, __spreadValues({ dpr, scale }, opts)),
1794
- toPng: (opts = {}) => toRasterImg(url, __spreadValues({ dpr, scale }, opts), "png"),
1795
- toJpg: (opts = {}) => toRasterImg(url, __spreadValues({ dpr, scale }, opts), "jpeg"),
1796
- toWebp: (opts = {}) => toRasterImg(url, __spreadValues({ dpr, scale }, opts), "webp"),
1797
- download: (_b = {}) => {
1798
- var _c = _b, { format = "png", filename = "snapDOM", backgroundColor } = _c, opts = __objRest(_c, ["format", "filename", "backgroundColor"]);
1799
- return download(url, __spreadValues({ dpr, scale, format, filename, backgroundColor }, opts));
1800
- }
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))
1801
2507
  };
1802
2508
  });
1803
- snapdom.toRaw = (el, options) => __async(null, null, function* () {
1804
- return (yield snapdom.capture(el, options)).toRaw();
1805
- });
1806
- snapdom.toImg = (el, options) => __async(null, null, function* () {
1807
- return (yield snapdom.capture(el, options)).toImg();
1808
- });
1809
- snapdom.toCanvas = (el, options) => __async(null, null, function* () {
1810
- return (yield snapdom.capture(el, options)).toCanvas();
1811
- });
1812
- snapdom.toBlob = (el, options) => __async(null, null, function* () {
1813
- return (yield snapdom.capture(el, options)).toBlob(options);
1814
- });
1815
- snapdom.toPng = (el, options) => __async(null, null, function* () {
1816
- return (yield snapdom.capture(el, options)).toPng(options);
1817
- });
1818
- snapdom.toJpg = (el, options) => __async(null, null, function* () {
1819
- return (yield snapdom.capture(el, options)).toJpg(options);
1820
- });
1821
- snapdom.toWebp = (el, options) => __async(null, null, function* () {
1822
- return (yield snapdom.capture(el, options)).toWebp(options);
1823
- });
1824
- snapdom.download = (_0, ..._1) => __async(null, [_0, ..._1], function* (el, options = {}) {
1825
- const _a = options, {
1826
- format = "png",
1827
- filename = "capture",
1828
- backgroundColor
1829
- } = _a, rest = __objRest(_a, [
1830
- "format",
1831
- "filename",
1832
- "backgroundColor"
1833
- ]);
1834
- const capture = yield snapdom.capture(el, rest);
1835
- return yield capture.download({ format, filename, backgroundColor });
1836
- });
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());
1837
2517
 
1838
2518
  // src/api/preCache.js
1839
2519
  function preCache() {
1840
2520
  return __async(this, arguments, function* (root = document, options = {}) {
1841
- const { embedFonts = true, reset = false, useProxy } = options;
1842
- if (reset) {
1843
- cache.image.clear();
1844
- cache.background.clear();
1845
- cache.resource.clear();
1846
- cache.defaultStyle.clear();
1847
- cache.baseStyle.clear();
1848
- cache.font.clear();
1849
- cache.computedStyle = /* @__PURE__ */ new WeakMap();
1850
- return;
1851
- }
2521
+ var _a;
2522
+ const {
2523
+ embedFonts = true,
2524
+ cacheOpt = "full",
2525
+ useProxy = ""
2526
+ } = options;
2527
+ applyCachePolicy(cacheOpt);
1852
2528
  try {
1853
2529
  yield document.fonts.ready;
1854
2530
  } catch (e) {
1855
2531
  }
1856
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
+ }
1857
2547
  let imgEls = [], allEls = [];
1858
- if (root == null ? void 0 : root.querySelectorAll) {
1859
- imgEls = Array.from(root.querySelectorAll("img[src]"));
1860
- 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) {
1861
2554
  }
1862
2555
  const promises = [];
1863
2556
  for (const img of imgEls) {
1864
2557
  const src = img == null ? void 0 : img.src;
1865
2558
  if (!src) continue;
1866
2559
  if (!cache.image.has(src)) {
1867
- const p = Promise.resolve().then(() => fetchImage(src, { useProxy })).then((dataURL) => {
2560
+ const p = Promise.resolve().then(() => snapFetch(src, { as: "dataURL", useProxy })).then((dataURL) => {
1868
2561
  cache.image.set(src, dataURL);
1869
2562
  }).catch(() => {
1870
2563
  });
@@ -1878,8 +2571,8 @@
1878
2571
  } catch (e) {
1879
2572
  }
1880
2573
  if (bg && bg !== "none") {
1881
- const bgSplits = splitBackgroundImage(bg);
1882
- for (const entry of bgSplits) {
2574
+ const parts = splitBackgroundImage(bg);
2575
+ for (const entry of parts) {
1883
2576
  if (entry.startsWith("url(")) {
1884
2577
  const p = Promise.resolve().then(() => inlineSingleBackgroundEntry(entry, __spreadProps(__spreadValues({}, options), { useProxy }))).catch(() => {
1885
2578
  });
@@ -1890,10 +2583,23 @@
1890
2583
  }
1891
2584
  if (embedFonts) {
1892
2585
  try {
1893
- yield embedCustomFonts({ preCached: true, localFonts: options.localFonts, useProxy: options.useProxy });
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
+ });
1894
2601
  } catch (e) {
1895
2602
  }
1896
- ;
1897
2603
  }
1898
2604
  yield Promise.allSettled(promises);
1899
2605
  });