@zumer/snapdom 1.9.9 → 1.9.10-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/snapdom.js CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.9.9
4
+ * v.1.9.10-dev.1
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,313 @@
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
+ }
77
134
  }
78
135
 
79
- // src/utils/cssTools.js
136
+ // src/modules/snapFetch.js
137
+ function createSnapLogger(prefix = "[snapDOM]", { ttlMs = 5 * 6e4, maxEntries = 12 } = {}) {
138
+ const seen = /* @__PURE__ */ new Map();
139
+ let emitted = 0;
140
+ function log(level, key, msg) {
141
+ if (emitted >= maxEntries) return;
142
+ const now = Date.now();
143
+ const until = seen.get(key) || 0;
144
+ if (until > now) return;
145
+ seen.set(key, now + ttlMs);
146
+ emitted++;
147
+ if (level === "warn" && console && console.warn) console.warn(`${prefix} ${msg}`);
148
+ else if (console && console.error) console.error(`${prefix} ${msg}`);
149
+ }
150
+ return {
151
+ warnOnce(key, msg) {
152
+ log("warn", key, msg);
153
+ },
154
+ errorOnce(key, msg) {
155
+ log("error", key, msg);
156
+ },
157
+ reset() {
158
+ seen.clear();
159
+ emitted = 0;
160
+ }
161
+ };
162
+ }
163
+ var snapLogger = createSnapLogger("[snapDOM]", { ttlMs: 3 * 6e4, maxEntries: 10 });
164
+ var _inflight = /* @__PURE__ */ new Map();
165
+ var _errorCache = /* @__PURE__ */ new Map();
166
+ function isSpecialURL(url) {
167
+ return /^data:|^blob:|^about:blank$/.test(url);
168
+ }
169
+ function shouldProxy(url, useProxy) {
170
+ if (!useProxy || isSpecialURL(url)) return false;
171
+ try {
172
+ const base = typeof location !== "undefined" && location.href ? location.href : "http://localhost/";
173
+ const u = new URL(url, base);
174
+ return typeof location !== "undefined" ? u.origin !== location.origin : true;
175
+ } catch (e) {
176
+ return !!useProxy;
177
+ }
178
+ }
179
+ function applyProxy(url, useProxy) {
180
+ if (!useProxy) return url;
181
+ if (useProxy.includes("{url}")) return useProxy.replace("{url}", encodeURIComponent(url));
182
+ const sep = useProxy.endsWith("?") ? "" : useProxy.includes("?") ? "&" : "?";
183
+ return `${useProxy}${sep}url=${encodeURIComponent(url)}`;
184
+ }
185
+ function blobToDataURL(blob) {
186
+ return new Promise((res, rej) => {
187
+ const fr = new FileReader();
188
+ fr.onload = () => res(String(fr.result || ""));
189
+ fr.onerror = () => rej(new Error("read_failed"));
190
+ fr.readAsDataURL(blob);
191
+ });
192
+ }
193
+ function makeKey(url, o) {
194
+ var _a, _b;
195
+ return [
196
+ o.as || "blob",
197
+ (_a = o.timeout) != null ? _a : 3e3,
198
+ o.useProxy || "",
199
+ (_b = o.errorTTL) != null ? _b : 8e3,
200
+ url
201
+ ].join("|");
202
+ }
203
+ function snapFetch(_0) {
204
+ return __async(this, arguments, function* (url, options = {}) {
205
+ var _a, _b, _c;
206
+ const as = (_a = options.as) != null ? _a : "blob";
207
+ const timeout = (_b = options.timeout) != null ? _b : 3e3;
208
+ const useProxy = options.useProxy || "";
209
+ const errorTTL = (_c = options.errorTTL) != null ? _c : 8e3;
210
+ const headers = options.headers || {};
211
+ const silent = !!options.silent;
212
+ const key = makeKey(url, { as, timeout, useProxy, errorTTL });
213
+ const e = _errorCache.get(key);
214
+ if (e && e.until > Date.now()) {
215
+ return __spreadProps(__spreadValues({}, e.result), { fromCache: true });
216
+ } else if (e) {
217
+ _errorCache.delete(key);
218
+ }
219
+ const inflight = _inflight.get(key);
220
+ if (inflight) return inflight;
221
+ const finalURL = shouldProxy(url, useProxy) ? applyProxy(url, useProxy) : url;
222
+ let cred = options.credentials;
223
+ if (!cred) {
224
+ try {
225
+ const base = typeof location !== "undefined" && location.href ? location.href : "http://localhost/";
226
+ const u = new URL(url, base);
227
+ const sameOrigin = typeof location !== "undefined" && u.origin === location.origin;
228
+ cred = sameOrigin ? "include" : "omit";
229
+ } catch (e2) {
230
+ cred = "omit";
231
+ }
232
+ }
233
+ const ctrl = new AbortController();
234
+ const timer = setTimeout(() => ctrl.abort("timeout"), timeout);
235
+ const p = (() => __async(null, null, function* () {
236
+ var _a2, _b2;
237
+ try {
238
+ const resp = yield fetch(finalURL, { signal: ctrl.signal, credentials: cred, headers });
239
+ if (!resp.ok) {
240
+ const result = { ok: false, data: null, status: resp.status, url: finalURL, fromCache: false, reason: "http_error" };
241
+ _errorCache.set(key, { until: Date.now() + errorTTL, result });
242
+ if (!silent) {
243
+ const short = `${resp.status} ${resp.statusText || ""}`.trim();
244
+ snapLogger.warnOnce(
245
+ `http:${resp.status}:${as}:${new URL(url, (_a2 = location == null ? void 0 : location.href) != null ? _a2 : "http://localhost/").origin}`,
246
+ `HTTP error ${short} while fetching ${as} ${url}`
247
+ );
248
+ }
249
+ options.onError && options.onError(result);
250
+ return result;
251
+ }
252
+ if (as === "text") {
253
+ const text = yield resp.text();
254
+ return { ok: true, data: text, status: resp.status, url: finalURL, fromCache: false };
255
+ }
256
+ const blob = yield resp.blob();
257
+ const mime = blob.type || resp.headers.get("content-type") || "";
258
+ if (as === "dataURL") {
259
+ const dataURL = yield blobToDataURL(blob);
260
+ return { ok: true, data: dataURL, status: resp.status, url: finalURL, fromCache: false, mime };
261
+ }
262
+ return { ok: true, data: blob, status: resp.status, url: finalURL, fromCache: false, mime };
263
+ } catch (err) {
264
+ const reason = err && typeof err === "object" && "name" in err && err.name === "AbortError" ? String(err.message || "").includes("timeout") ? "timeout" : "abort" : "network";
265
+ const result = { ok: false, data: null, status: 0, url: finalURL, fromCache: false, reason };
266
+ _errorCache.set(key, { until: Date.now() + errorTTL, result });
267
+ if (!silent) {
268
+ const k = `${reason}:${as}:${new URL(url, (_b2 = location == null ? void 0 : location.href) != null ? _b2 : "http://localhost/").origin}`;
269
+ const tips = reason === "timeout" ? `Timeout after ${timeout}ms. Consider increasing timeout or using a proxy for ${url}` : reason === "abort" ? `Request aborted while fetching ${as} ${url}` : `Network/CORS issue while fetching ${as} ${url}. A proxy may be required`;
270
+ snapLogger.errorOnce(k, tips);
271
+ }
272
+ options.onError && options.onError(result);
273
+ return result;
274
+ } finally {
275
+ clearTimeout(timer);
276
+ _inflight.delete(key);
277
+ }
278
+ }))();
279
+ _inflight.set(key, p);
280
+ return p;
281
+ });
282
+ }
283
+
284
+ // src/utils/image.js
285
+ function createBackground(baseCanvas, backgroundColor) {
286
+ if (!backgroundColor || !baseCanvas.width || !baseCanvas.height) {
287
+ return baseCanvas;
288
+ }
289
+ const temp = document.createElement("canvas");
290
+ temp.width = baseCanvas.width;
291
+ temp.height = baseCanvas.height;
292
+ const ctx = temp.getContext("2d");
293
+ ctx.fillStyle = backgroundColor;
294
+ ctx.fillRect(0, 0, temp.width, temp.height);
295
+ ctx.drawImage(baseCanvas, 0, 0);
296
+ return temp;
297
+ }
298
+ function inlineSingleBackgroundEntry(_0) {
299
+ return __async(this, arguments, function* (entry, options = {}) {
300
+ const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
301
+ if (isGradient || entry.trim() === "none") {
302
+ return entry;
303
+ }
304
+ const rawUrl = extractURL(entry);
305
+ if (!rawUrl) {
306
+ return entry;
307
+ }
308
+ const encodedUrl = safeEncodeURI(rawUrl);
309
+ if (cache.background.has(encodedUrl)) {
310
+ const dataUrl = cache.background.get(encodedUrl);
311
+ return dataUrl ? `url("${dataUrl}")` : "none";
312
+ }
313
+ try {
314
+ const dataUrl = yield snapFetch(encodedUrl, { as: "dataURL", useProxy: options.useProxy });
315
+ if (dataUrl.ok) {
316
+ cache.background.set(encodedUrl, dataUrl.data);
317
+ return `url("${dataUrl.data}")`;
318
+ }
319
+ cache.background.set(encodedUrl, null);
320
+ return "none";
321
+ } catch (e) {
322
+ cache.background.set(encodedUrl, null);
323
+ return "none";
324
+ }
325
+ });
326
+ }
327
+
328
+ // src/utils/css.js
329
+ var NO_CAPTURE_TAGS = /* @__PURE__ */ new Set([
330
+ "meta",
331
+ "script",
332
+ "noscript",
333
+ "title",
334
+ "link",
335
+ "template"
336
+ ]);
337
+ var NO_DEFAULTS_TAGS = /* @__PURE__ */ new Set([
338
+ // non-painting / head stuff
339
+ "meta",
340
+ "link",
341
+ "style",
342
+ "title",
343
+ "noscript",
344
+ "script",
345
+ "template",
346
+ // SVG whole namespace (safe for LeaderLine/presentation attrs)
347
+ "g",
348
+ "defs",
349
+ "use",
350
+ "marker",
351
+ "mask",
352
+ "clipPath",
353
+ "pattern",
354
+ "path",
355
+ "polygon",
356
+ "polyline",
357
+ "line",
358
+ "circle",
359
+ "ellipse",
360
+ "rect",
361
+ "filter",
362
+ "lineargradient",
363
+ "radialgradient",
364
+ "stop"
365
+ ]);
80
366
  var commonTags = [
81
367
  "div",
82
368
  "span",
@@ -103,12 +389,6 @@
103
389
  "h4",
104
390
  "h5",
105
391
  "h6",
106
- "svg",
107
- "path",
108
- "circle",
109
- "rect",
110
- "line",
111
- "g",
112
392
  "table",
113
393
  "thead",
114
394
  "tbody",
@@ -118,28 +398,33 @@
118
398
  ];
119
399
  function precacheCommonTags() {
120
400
  for (let tag of commonTags) {
121
- getDefaultStyleForTag(tag);
401
+ const t = String(tag).toLowerCase();
402
+ if (NO_CAPTURE_TAGS.has(t)) continue;
403
+ if (NO_DEFAULTS_TAGS.has(t)) continue;
404
+ getDefaultStyleForTag(t);
122
405
  }
123
406
  }
124
407
  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)) {
408
+ tagName = String(tagName).toLowerCase();
409
+ if (NO_DEFAULTS_TAGS.has(tagName)) {
130
410
  const empty = {};
131
411
  cache.defaultStyle.set(tagName, empty);
132
412
  return empty;
133
413
  }
414
+ if (cache.defaultStyle.has(tagName)) {
415
+ return cache.defaultStyle.get(tagName);
416
+ }
134
417
  let sandbox = document.getElementById("snapdom-sandbox");
135
418
  if (!sandbox) {
136
419
  sandbox = document.createElement("div");
137
420
  sandbox.id = "snapdom-sandbox";
421
+ sandbox.setAttribute("data-snapdom-sandbox", "true");
422
+ sandbox.setAttribute("aria-hidden", "true");
138
423
  sandbox.style.position = "absolute";
139
424
  sandbox.style.left = "-9999px";
140
425
  sandbox.style.top = "-9999px";
141
- sandbox.style.width = "0";
142
- sandbox.style.height = "0";
426
+ sandbox.style.width = "0px";
427
+ sandbox.style.height = "0px";
143
428
  sandbox.style.overflow = "hidden";
144
429
  document.body.appendChild(sandbox);
145
430
  }
@@ -149,32 +434,55 @@
149
434
  const styles = getComputedStyle(el);
150
435
  const defaults = {};
151
436
  for (let prop of styles) {
152
- defaults[prop] = styles.getPropertyValue(prop);
437
+ if (shouldIgnoreProp(prop)) continue;
438
+ const value = styles.getPropertyValue(prop);
439
+ defaults[prop] = value;
153
440
  }
154
441
  sandbox.removeChild(el);
155
442
  cache.defaultStyle.set(tagName, defaults);
156
443
  return defaults;
157
444
  }
158
- var IGNORED_PROPS = /* @__PURE__ */ new Set([
159
- "-webkit-locale"
445
+ var NO_PAINT_TOKEN = /(?:^|-)(animation|transition)(?:-|$)/i;
446
+ 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;
447
+ var NO_PAINT_EXACT = /* @__PURE__ */ new Set([
448
+ // Interaction hints
449
+ "cursor",
450
+ "pointer-events",
451
+ "touch-action",
452
+ "user-select",
453
+ // Printing/speech/reading-mode hints
454
+ "print-color-adjust",
455
+ "speak",
456
+ "reading-flow",
457
+ "reading-order",
458
+ // Anchoring/container/timeline scopes (metadata for layout queries)
459
+ "anchor-name",
460
+ "anchor-scope",
461
+ "container-name",
462
+ "container-type",
463
+ "timeline-scope"
160
464
  ]);
161
- function getStyleKey(snapshot, tagName, compress = false) {
465
+ function shouldIgnoreProp(prop) {
466
+ const p = String(prop).toLowerCase();
467
+ if (NO_PAINT_EXACT.has(p)) return true;
468
+ if (NO_PAINT_PREFIX.test(p)) return true;
469
+ if (NO_PAINT_TOKEN.test(p)) return true;
470
+ return false;
471
+ }
472
+ function getStyleKey(snapshot, tagName) {
473
+ tagName = String(tagName || "").toLowerCase();
474
+ if (NO_DEFAULTS_TAGS.has(tagName)) {
475
+ return "";
476
+ }
162
477
  const entries = [];
163
- const defaultStyles = getDefaultStyleForTag(tagName);
478
+ const defaults = getDefaultStyleForTag(tagName);
164
479
  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
- }
480
+ if (shouldIgnoreProp(prop)) continue;
481
+ const def = defaults[prop];
482
+ if (value && value !== def) entries.push(`${prop}:${value}`);
176
483
  }
177
- return entries.sort().join(";");
484
+ entries.sort();
485
+ return entries.join(";");
178
486
  }
179
487
  function collectUsedTagNames(root) {
180
488
  const tagSet = /* @__PURE__ */ new Set();
@@ -195,6 +503,7 @@
195
503
  const styles = cache.defaultStyle.get(tagName);
196
504
  if (!styles) continue;
197
505
  const key = Object.entries(styles).map(([k, v]) => `${k}:${v};`).sort().join("");
506
+ if (!key) continue;
198
507
  if (!groups.has(key)) {
199
508
  groups.set(key, []);
200
509
  }
@@ -208,45 +517,12 @@
208
517
  return css;
209
518
  }
210
519
  function generateCSSClasses(styleMap) {
211
- const keySet = new Set(styleMap.values());
520
+ const keys = Array.from(new Set(styleMap.values())).filter(Boolean).sort();
212
521
  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
- }
522
+ let i = 1;
523
+ for (const k of keys) classMap.set(k, `c${i++}`);
218
524
  return classMap;
219
525
  }
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
526
  function getStyle(el, pseudo = null) {
251
527
  if (!(el instanceof Element)) {
252
528
  return window.getComputedStyle(el, pseudo);
@@ -273,196 +549,6 @@
273
549
  }
274
550
  return clean;
275
551
  }
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
552
  function snapshotComputedStyle(style) {
467
553
  const snap = {};
468
554
  for (let prop of style) {
@@ -470,36 +556,6 @@
470
556
  }
471
557
  return snap;
472
558
  }
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
559
  function splitBackgroundImage(bg) {
504
560
  const parts = [];
505
561
  let depth = 0;
@@ -517,49 +573,209 @@
517
573
  return parts;
518
574
  }
519
575
 
576
+ // src/utils/browser.js
577
+ function idle(fn, { fast = false } = {}) {
578
+ if (fast) return fn();
579
+ if ("requestIdleCallback" in window) {
580
+ requestIdleCallback(fn, { timeout: 50 });
581
+ } else {
582
+ setTimeout(fn, 1);
583
+ }
584
+ }
585
+ function isSafari() {
586
+ return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
587
+ }
588
+
520
589
  // src/modules/styles.js
521
590
  var snapshotCache = /* @__PURE__ */ new WeakMap();
522
591
  var snapshotKeyCache = /* @__PURE__ */ new Map();
592
+ var __epoch = 0;
593
+ function bumpEpoch() {
594
+ __epoch++;
595
+ }
596
+ var __wired = false;
597
+ function setupInvalidationOnce(root = document.documentElement) {
598
+ var _a, _b;
599
+ if (__wired) return;
600
+ __wired = true;
601
+ try {
602
+ const domObs = new MutationObserver(() => bumpEpoch());
603
+ domObs.observe(root, { subtree: true, childList: true, characterData: true, attributes: true });
604
+ } catch (e) {
605
+ }
606
+ try {
607
+ const headObs = new MutationObserver(() => bumpEpoch());
608
+ headObs.observe(document.head, { subtree: true, childList: true, characterData: true, attributes: true });
609
+ } catch (e) {
610
+ }
611
+ try {
612
+ const f = document.fonts;
613
+ if (f) {
614
+ (_a = f.addEventListener) == null ? void 0 : _a.call(f, "loadingdone", bumpEpoch);
615
+ (_b = f.ready) == null ? void 0 : _b.then(() => bumpEpoch()).catch(() => {
616
+ });
617
+ }
618
+ } catch (e) {
619
+ }
620
+ }
523
621
  function snapshotComputedStyleFull(style) {
524
- const result = {};
525
- const computedVisibility = style.getPropertyValue("visibility");
622
+ const out = {};
623
+ const vis = style.getPropertyValue("visibility");
526
624
  for (let i = 0; i < style.length; i++) {
527
625
  const prop = style[i];
528
626
  let val = style.getPropertyValue(prop);
529
627
  if ((prop === "background-image" || prop === "content") && val.includes("url(") && !val.includes("data:")) {
530
628
  val = "none";
531
629
  }
532
- result[prop] = val;
630
+ out[prop] = val;
533
631
  }
534
- if (computedVisibility === "hidden") {
535
- result.opacity = "0";
632
+ if (vis === "hidden") out.opacity = "0";
633
+ return out;
634
+ }
635
+ var __snapshotSig = /* @__PURE__ */ new WeakMap();
636
+ function styleSignature(snap) {
637
+ let sig = __snapshotSig.get(snap);
638
+ if (sig) return sig;
639
+ const entries = Object.entries(snap).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
640
+ sig = entries.map(([k, v]) => `${k}:${v}`).join(";");
641
+ __snapshotSig.set(snap, sig);
642
+ return sig;
643
+ }
644
+ function getSnapshot(el, preStyle = null) {
645
+ const rec = snapshotCache.get(el);
646
+ if (rec && rec.epoch === __epoch) return rec.snapshot;
647
+ const style = preStyle || getComputedStyle(el);
648
+ const snap = snapshotComputedStyleFull(style);
649
+ snapshotCache.set(el, { epoch: __epoch, snapshot: snap });
650
+ return snap;
651
+ }
652
+ function _resolveCtx(sessionOrCtx, opts) {
653
+ if (sessionOrCtx && sessionOrCtx.session && sessionOrCtx.persist) return sessionOrCtx;
654
+ if (sessionOrCtx && (sessionOrCtx.styleMap || sessionOrCtx.styleCache || sessionOrCtx.nodeMap)) {
655
+ return {
656
+ session: sessionOrCtx,
657
+ persist: {
658
+ snapshotKeyCache,
659
+ defaultStyle: cache.defaultStyle,
660
+ baseStyle: cache.baseStyle,
661
+ image: cache.image,
662
+ resource: cache.resource,
663
+ background: cache.background,
664
+ font: cache.font
665
+ },
666
+ options: opts || {}
667
+ };
536
668
  }
537
- return result;
669
+ return {
670
+ session: cache.session,
671
+ persist: {
672
+ snapshotKeyCache,
673
+ defaultStyle: cache.defaultStyle,
674
+ baseStyle: cache.baseStyle,
675
+ image: cache.image,
676
+ resource: cache.resource,
677
+ background: cache.background,
678
+ font: cache.font
679
+ },
680
+ options: sessionOrCtx || opts || {}
681
+ };
538
682
  }
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);
683
+ function inlineAllStyles(source, clone, sessionOrCtx, opts) {
684
+ return __async(this, null, function* () {
685
+ var _a, _b, _c;
686
+ if (source.tagName === "STYLE") return;
687
+ const ctx = _resolveCtx(sessionOrCtx, opts);
688
+ const resetMode = ctx.options && ctx.options.cache || "auto";
689
+ if (resetMode !== "disabled") setupInvalidationOnce(document.documentElement);
690
+ if (NO_DEFAULTS_TAGS.has((_a = source.tagName) == null ? void 0 : _a.toLowerCase())) {
691
+ const author = (_b = source.getAttribute) == null ? void 0 : _b.call(source, "style");
692
+ if (author) clone.setAttribute("style", author);
693
+ }
694
+ const { session, persist } = ctx;
695
+ if (!session.styleCache.has(source)) {
696
+ session.styleCache.set(source, getComputedStyle(source));
697
+ }
698
+ const pre = session.styleCache.get(source);
699
+ const snap = getSnapshot(source, pre);
700
+ const sig = styleSignature(snap);
701
+ let key = persist.snapshotKeyCache.get(sig);
702
+ if (!key) {
703
+ const tag = ((_c = source.tagName) == null ? void 0 : _c.toLowerCase()) || "div";
704
+ key = getStyleKey(snap, tag);
705
+ persist.snapshotKeyCache.set(sig, key);
706
+ }
707
+ session.styleMap.set(clone, key);
708
+ });
560
709
  }
561
710
 
562
711
  // src/core/clone.js
712
+ function addNotSlottedRightmost(sel) {
713
+ sel = sel.trim();
714
+ if (!sel) return sel;
715
+ if (/\:not\(\s*\[data-sd-slotted\]\s*\)\s*$/.test(sel)) return sel;
716
+ return `${sel}:not([data-sd-slotted])`;
717
+ }
718
+ function wrapWithScope(selectorList, scopeSelector, excludeSlotted = true) {
719
+ return selectorList.split(",").map((s) => s.trim()).filter(Boolean).map((s) => {
720
+ if (s.startsWith(":where(")) return s;
721
+ if (s.startsWith("@")) return s;
722
+ const body = excludeSlotted ? addNotSlottedRightmost(s) : s;
723
+ return `:where(${scopeSelector} ${body})`;
724
+ }).join(", ");
725
+ }
726
+ function rewriteShadowCSS(cssText, scopeSelector) {
727
+ if (!cssText) return "";
728
+ cssText = cssText.replace(/:host\(([^)]+)\)/g, (_, sel) => {
729
+ return `:where(${scopeSelector}:is(${sel.trim()}))`;
730
+ });
731
+ cssText = cssText.replace(/:host\b/g, `:where(${scopeSelector})`);
732
+ cssText = cssText.replace(/:host-context\(([^)]+)\)/g, (_, sel) => {
733
+ return `:where(:where(${sel.trim()}) ${scopeSelector})`;
734
+ });
735
+ cssText = cssText.replace(/::slotted\(([^)]+)\)/g, (_, sel) => {
736
+ return `:where(${scopeSelector} ${sel.trim()})`;
737
+ });
738
+ cssText = cssText.replace(/(^|})(\s*)([^@}{]+){/g, (_, brace, ws, selectorList) => {
739
+ const wrapped = wrapWithScope(
740
+ selectorList,
741
+ scopeSelector,
742
+ /*excludeSlotted*/
743
+ true
744
+ );
745
+ return `${brace}${ws}${wrapped}{`;
746
+ });
747
+ return cssText;
748
+ }
749
+ function nextShadowScopeId(sessionCache) {
750
+ sessionCache.shadowScopeSeq = (sessionCache.shadowScopeSeq || 0) + 1;
751
+ return `s${sessionCache.shadowScopeSeq}`;
752
+ }
753
+ function extractShadowCSS(sr) {
754
+ let css = "";
755
+ try {
756
+ sr.querySelectorAll("style").forEach((s) => {
757
+ css += (s.textContent || "") + "\n";
758
+ });
759
+ const sheets = sr.adoptedStyleSheets || [];
760
+ for (const sh of sheets) {
761
+ try {
762
+ if (sh && sh.cssRules) {
763
+ for (const rule of sh.cssRules) css += rule.cssText + "\n";
764
+ }
765
+ } catch (e) {
766
+ }
767
+ }
768
+ } catch (e) {
769
+ }
770
+ return css;
771
+ }
772
+ function injectScopedStyle(hostClone, cssText, scopeId) {
773
+ if (!cssText) return;
774
+ const style = document.createElement("style");
775
+ style.setAttribute("data-sd", scopeId);
776
+ style.textContent = cssText;
777
+ hostClone.insertBefore(style, hostClone.firstChild || null);
778
+ }
563
779
  function freezeImgSrcset(original, cloned) {
564
780
  try {
565
781
  const chosen = original.currentSrc || original.src || "";
@@ -572,11 +788,62 @@
572
788
  } catch (e) {
573
789
  }
574
790
  }
575
- function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {}, originalRoot) {
576
- var _a, _b;
791
+ function collectCustomPropsFromCSS(cssText) {
792
+ const out = /* @__PURE__ */ new Set();
793
+ if (!cssText) return out;
794
+ const re = /var\(\s*(--[A-Za-z0-9_-]+)\b/g;
795
+ let m;
796
+ while (m = re.exec(cssText)) out.add(m[1]);
797
+ return out;
798
+ }
799
+ function resolveCustomProp(el, name) {
800
+ try {
801
+ const cs = getComputedStyle(el);
802
+ let v = cs.getPropertyValue(name).trim();
803
+ if (v) return v;
804
+ } catch (e) {
805
+ }
806
+ try {
807
+ const rootCS = getComputedStyle(document.documentElement);
808
+ let v = rootCS.getPropertyValue(name).trim();
809
+ if (v) return v;
810
+ } catch (e) {
811
+ }
812
+ return "";
813
+ }
814
+ function buildSeedCustomPropsRule(hostEl, names, scopeSelector) {
815
+ const decls = [];
816
+ for (const name of names) {
817
+ const val = resolveCustomProp(hostEl, name);
818
+ if (val) decls.push(`${name}: ${val};`);
819
+ }
820
+ if (!decls.length) return "";
821
+ return `${scopeSelector}{${decls.join("")}}
822
+ `;
823
+ }
824
+ function markSlottedSubtree(root) {
825
+ if (!root) return;
826
+ if (root.nodeType === Node.ELEMENT_NODE) {
827
+ root.setAttribute("data-sd-slotted", "");
828
+ }
829
+ if (root.querySelectorAll) {
830
+ root.querySelectorAll("*").forEach((el) => el.setAttribute("data-sd-slotted", ""));
831
+ }
832
+ }
833
+ function deepClone(node, sessionCache, options) {
834
+ var _a, _b, _c, _d, _e;
577
835
  if (!node) throw new Error("Invalid node");
578
836
  const clonedAssignedNodes = /* @__PURE__ */ new Set();
579
837
  let pendingSelectValue = null;
838
+ if (node.nodeType === Node.ELEMENT_NODE) {
839
+ const tag = (node.localName || node.tagName || "").toLowerCase();
840
+ if (node.id === "snapdom-sandbox" || node.hasAttribute("data-snapdom-sandbox")) {
841
+ return null;
842
+ }
843
+ if (NO_CAPTURE_TAGS.has(tag)) {
844
+ return null;
845
+ }
846
+ }
580
847
  if (node.nodeType === Node.TEXT_NODE) {
581
848
  return node.cloneNode(true);
582
849
  }
@@ -605,7 +872,7 @@
605
872
  }
606
873
  if (typeof options.filter === "function") {
607
874
  try {
608
- if (!options.filter(node, originalRoot || node)) {
875
+ if (!options.filter(node)) {
609
876
  const spacer = document.createElement("div");
610
877
  const rect = node.getBoundingClientRect();
611
878
  spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
@@ -616,14 +883,23 @@
616
883
  }
617
884
  }
618
885
  if (node.tagName === "IFRAME") {
619
- const fallback = document.createElement("div");
620
- fallback.style.cssText = `width:${node.offsetWidth}px;height:${node.offsetHeight}px;background-image:repeating-linear-gradient(45deg,#ddd,#ddd 5px,#f9f9f9 5px,#f9f9f9 10px);display:flex;align-items:center;justify-content:center;font-size:12px;color:#555;border:1px solid #aaa;`;
621
- return fallback;
886
+ if (options.placeholders) {
887
+ const fallback = document.createElement("div");
888
+ fallback.style.cssText = `width:${node.offsetWidth}px;height:${node.offsetHeight}px;background-image:repeating-linear-gradient(45deg,#ddd,#ddd 5px,#f9f9f9 5px,#f9f9f9 10px);display:flex;align-items:center;justify-content:center;font-size:12px;color:#555;border:1px solid #aaa;`;
889
+ inlineAllStyles(node, fallback, sessionCache, options);
890
+ return fallback;
891
+ } else {
892
+ const rect = node.getBoundingClientRect();
893
+ const spacer = document.createElement("div");
894
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
895
+ inlineAllStyles(node, spacer, sessionCache, options);
896
+ return spacer;
897
+ }
622
898
  }
623
899
  if (node.getAttribute("data-capture") === "placeholder") {
624
900
  const clone2 = node.cloneNode(false);
625
- nodeMap.set(clone2, node);
626
- inlineAllStyles(node, clone2, styleMap, styleCache, compress);
901
+ sessionCache.nodeMap.set(clone2, node);
902
+ inlineAllStyles(node, clone2, sessionCache, options);
627
903
  const placeholder = document.createElement("div");
628
904
  placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
629
905
  placeholder.style.cssText = `color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;`;
@@ -636,14 +912,14 @@
636
912
  img.src = dataURL;
637
913
  img.width = node.width;
638
914
  img.height = node.height;
639
- nodeMap.set(img, node);
640
- inlineAllStyles(node, img, styleMap, styleCache, compress);
915
+ sessionCache.nodeMap.set(img, node);
916
+ inlineAllStyles(node, img, sessionCache, options);
641
917
  return img;
642
918
  }
643
919
  let clone;
644
920
  try {
645
921
  clone = node.cloneNode(false);
646
- nodeMap.set(clone, node);
922
+ sessionCache.nodeMap.set(clone, node);
647
923
  if (node.tagName === "IMG") {
648
924
  freezeImgSrcset(node, clone);
649
925
  }
@@ -657,7 +933,6 @@
657
933
  const rect = node.getBoundingClientRect();
658
934
  clone.style.width = `${rect.width}px`;
659
935
  clone.style.height = `${rect.height}px`;
660
- return clone;
661
936
  }
662
937
  if (node instanceof HTMLInputElement) {
663
938
  clone.value = node.value;
@@ -671,47 +946,56 @@
671
946
  if (node instanceof HTMLSelectElement) {
672
947
  pendingSelectValue = node.value;
673
948
  }
674
- inlineAllStyles(node, clone, styleMap, styleCache, compress);
949
+ inlineAllStyles(node, clone, sessionCache, options);
675
950
  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;
951
+ try {
952
+ const slots = node.shadowRoot.querySelectorAll("slot");
953
+ for (const s of slots) {
954
+ let assigned = [];
955
+ try {
956
+ assigned = ((_b = s.assignedNodes) == null ? void 0 : _b.call(s, { flatten: true })) || ((_c = s.assignedNodes) == null ? void 0 : _c.call(s)) || [];
957
+ } catch (e) {
958
+ assigned = ((_d = s.assignedNodes) == null ? void 0 : _d.call(s)) || [];
695
959
  }
696
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
697
- if (clonedChild) shadowFrag.appendChild(clonedChild);
960
+ for (const an of assigned) clonedAssignedNodes.add(an);
698
961
  }
699
- clone.appendChild(shadowFrag);
962
+ } catch (e) {
963
+ }
964
+ const scopeId = nextShadowScopeId(sessionCache);
965
+ const scopeSelector = `[data-sd="${scopeId}"]`;
966
+ try {
967
+ clone.setAttribute("data-sd", scopeId);
968
+ } catch (e) {
700
969
  }
970
+ const rawCSS = extractShadowCSS(node.shadowRoot);
971
+ const rewritten = rewriteShadowCSS(rawCSS, scopeSelector);
972
+ const neededVars = collectCustomPropsFromCSS(rawCSS);
973
+ const seed = buildSeedCustomPropsRule(node, neededVars, scopeSelector);
974
+ injectScopedStyle(clone, seed + rewritten, scopeId);
975
+ const shadowFrag = document.createDocumentFragment();
976
+ for (const child of node.shadowRoot.childNodes) {
977
+ if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") continue;
978
+ const clonedChild = deepClone(child, sessionCache, options);
979
+ if (clonedChild) shadowFrag.appendChild(clonedChild);
980
+ }
981
+ clone.appendChild(shadowFrag);
701
982
  }
702
983
  if (node.tagName === "SLOT") {
703
- const assigned = ((_b = node.assignedNodes) == null ? void 0 : _b.call(node, { flatten: true })) || [];
984
+ const assigned = ((_e = node.assignedNodes) == null ? void 0 : _e.call(node, { flatten: true })) || [];
704
985
  const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
705
986
  const fragment = document.createDocumentFragment();
706
987
  for (const child of nodesToClone) {
707
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
708
- if (clonedChild) fragment.appendChild(clonedChild);
988
+ const clonedChild = deepClone(child, sessionCache, options);
989
+ if (clonedChild) {
990
+ markSlottedSubtree(clonedChild);
991
+ fragment.appendChild(clonedChild);
992
+ }
709
993
  }
710
994
  return fragment;
711
995
  }
712
996
  for (const child of node.childNodes) {
713
997
  if (clonedAssignedNodes.has(child)) continue;
714
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
998
+ const clonedChild = deepClone(child, sessionCache, options);
715
999
  if (clonedChild) clone.appendChild(clonedChild);
716
1000
  }
717
1001
  if (pendingSelectValue !== null && clone instanceof HTMLSelectElement) {
@@ -754,7 +1038,7 @@
754
1038
  }
755
1039
  }
756
1040
  }
757
- function isIconFont(input) {
1041
+ function isIconFont2(input) {
758
1042
  const text = typeof input === "string" ? input : "";
759
1043
  const candidates = [...defaultIconFonts, ...userIconFonts];
760
1044
  for (const rx of candidates) {
@@ -803,249 +1087,630 @@
803
1087
  };
804
1088
  });
805
1089
  }
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
- });
1090
+ var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
1091
+ "serif",
1092
+ "sans-serif",
1093
+ "monospace",
1094
+ "cursive",
1095
+ "fantasy",
1096
+ "system-ui",
1097
+ "emoji",
1098
+ "math",
1099
+ "fangsong",
1100
+ "ui-serif",
1101
+ "ui-sans-serif",
1102
+ "ui-monospace",
1103
+ "ui-rounded"
1104
+ ]);
1105
+ function pickPrimaryFamily(familyList) {
1106
+ if (!familyList) return "";
1107
+ for (let raw of familyList.split(",")) {
1108
+ let f = raw.trim().replace(/^['"]+|['"]+$/g, "");
1109
+ if (!f) continue;
1110
+ if (!GENERIC_FAMILIES.has(f.toLowerCase())) return f;
1111
+ }
1112
+ return "";
1113
+ }
1114
+ function normWeight(w) {
1115
+ const t = String(w != null ? w : "400").trim().toLowerCase();
1116
+ if (t === "normal") return 400;
1117
+ if (t === "bold") return 700;
1118
+ const n = parseInt(t, 10);
1119
+ return Number.isFinite(n) ? Math.min(900, Math.max(100, n)) : 400;
1120
+ }
1121
+ function normStyle(s) {
1122
+ const t = String(s != null ? s : "normal").trim().toLowerCase();
1123
+ if (t.startsWith("italic")) return "italic";
1124
+ if (t.startsWith("oblique")) return "oblique";
1125
+ return "normal";
1126
+ }
1127
+ function normStretchPct(st) {
1128
+ const m = String(st != null ? st : "100%").match(/(\d+(?:\.\d+)?)\s*%/);
1129
+ return m ? Math.max(50, Math.min(200, parseFloat(m[1]))) : 100;
1130
+ }
1131
+ function parseWeightSpec(spec) {
1132
+ const s = String(spec || "400").trim();
1133
+ const m = s.match(/^(\d{2,3})\s+(\d{2,3})$/);
1134
+ if (m) {
1135
+ const a = normWeight(m[1]), b = normWeight(m[2]);
1136
+ return { min: Math.min(a, b), max: Math.max(a, b) };
1137
+ }
1138
+ const v = normWeight(s);
1139
+ return { min: v, max: v };
1140
+ }
1141
+ function parseStyleSpec(spec) {
1142
+ const t = String(spec || "normal").trim().toLowerCase();
1143
+ if (t === "italic") return { kind: "italic" };
1144
+ if (t.startsWith("oblique")) return { kind: "oblique" };
1145
+ return { kind: "normal" };
1146
+ }
1147
+ function parseStretchSpec(spec) {
1148
+ const s = String(spec || "100%").trim();
1149
+ const mm = s.match(/(\d+(?:\.\d+)?)\s*%\s+(\d+(?:\.\d+)?)\s*%/);
1150
+ if (mm) {
1151
+ const a = parseFloat(mm[1]), b = parseFloat(mm[2]);
1152
+ return { min: Math.min(a, b), max: Math.max(a, b) };
1153
+ }
1154
+ const m = s.match(/(\d+(?:\.\d+)?)\s*%/);
1155
+ const v = m ? parseFloat(m[1]) : 100;
1156
+ return { min: v, max: v };
1157
+ }
1158
+ function isLikelyFontStylesheet(href, requiredFamilies) {
1159
+ if (!href) return false;
1160
+ try {
1161
+ const u = new URL(href, location.href);
1162
+ const sameOrigin = u.origin === location.origin;
1163
+ if (sameOrigin) return true;
1164
+ const host = u.host.toLowerCase();
1165
+ const FONT_HOSTS = [
1166
+ "fonts.googleapis.com",
1167
+ "fonts.gstatic.com",
1168
+ "use.typekit.net",
1169
+ "p.typekit.net",
1170
+ "kit.fontawesome.com",
1171
+ "use.fontawesome.com"
1172
+ ];
1173
+ if (FONT_HOSTS.some((h) => host.endsWith(h))) return true;
1174
+ const path = (u.pathname + u.search).toLowerCase();
1175
+ if (/\bfont(s)?\b/.test(path) || /\.woff2?(\b|$)/.test(path)) return true;
1176
+ for (const fam of requiredFamilies) {
1177
+ const tokenA = fam.toLowerCase().replace(/\s+/g, "+");
1178
+ const tokenB = fam.toLowerCase().replace(/\s+/g, "-");
1179
+ if (path.includes(tokenA) || path.includes(tokenB)) return true;
1180
+ }
1181
+ return false;
1182
+ } catch (e) {
1183
+ return false;
1184
+ }
1185
+ }
1186
+ function familiesFromRequired(required) {
1187
+ var _a;
1188
+ const out = /* @__PURE__ */ new Set();
1189
+ for (const k of required || []) {
1190
+ const fam = (_a = String(k).split("__")[0]) == null ? void 0 : _a.trim();
1191
+ if (fam) out.add(fam);
1192
+ }
1193
+ return out;
820
1194
  }
821
1195
  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);
1196
+ return __async(this, arguments, function* ({
1197
+ required,
1198
+ usedCodepoints,
1199
+ exclude = void 0,
1200
+ localFonts = [],
1201
+ useProxy = ""
1202
+ } = {}) {
1203
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
1204
+ if (!(required instanceof Set)) required = /* @__PURE__ */ new Set();
1205
+ if (!(usedCodepoints instanceof Set)) usedCodepoints = /* @__PURE__ */ new Set();
1206
+ const requiredIndex = /* @__PURE__ */ new Map();
1207
+ for (const key of required) {
1208
+ const [fam, w, s, st] = String(key).split("__");
1209
+ if (!fam) continue;
1210
+ const arr = requiredIndex.get(fam) || [];
1211
+ arr.push({ w: parseInt(w, 10), s, st: parseInt(st, 10) });
1212
+ requiredIndex.set(fam, arr);
1213
+ }
1214
+ const IMPORT_RE = /@import\s+url\(["']?([^"')]+)["']?\)/g;
1215
+ const URL_RE = /url\((["']?)([^"')]+)\1\)/g;
1216
+ const FACE_RE = /@font-face[^{}]*\{[^}]*\}/g;
1217
+ function faceMatchesRequired(fam, styleSpec, weightSpec, stretchSpec) {
1218
+ if (!requiredIndex.has(fam)) return false;
1219
+ const need = requiredIndex.get(fam);
1220
+ const ws = parseWeightSpec(weightSpec);
1221
+ const ss = parseStyleSpec(styleSpec);
1222
+ const ts = parseStretchSpec(stretchSpec);
1223
+ const faceIsRange = ws.min !== ws.max;
1224
+ const faceSingleW = ws.min;
1225
+ const styleOK = (reqKind) => ss.kind === "normal" && reqKind === "normal" || ss.kind !== "normal" && (reqKind === "italic" || reqKind === "oblique");
1226
+ let exactMatched = false;
1227
+ for (const r of need) {
1228
+ const wOk = faceIsRange ? r.w >= ws.min && r.w <= ws.max : r.w === faceSingleW;
1229
+ const sOk = styleOK(normStyle(r.s));
1230
+ const tOk = r.st >= ts.min && r.st <= ts.max;
1231
+ if (wOk && sOk && tOk) {
1232
+ exactMatched = true;
1233
+ break;
1234
+ }
829
1235
  }
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"}`);
1236
+ if (exactMatched) return true;
1237
+ if (!faceIsRange) {
1238
+ for (const r of need) {
1239
+ const sOk = styleOK(normStyle(r.s));
1240
+ const tOk = r.st >= ts.min && r.st <= ts.max;
1241
+ const nearWeight = Math.abs(faceSingleW - r.w) <= 300;
1242
+ if (nearWeight && sOk && tOk) {
1243
+ return true;
1244
+ }
837
1245
  }
838
1246
  }
839
- } catch (e) {
1247
+ return false;
1248
+ }
1249
+ function parseUnicodeRange(ur) {
1250
+ if (!ur) return [];
1251
+ const ranges = [];
1252
+ const parts = ur.split(",").map((s) => s.trim()).filter(Boolean);
1253
+ for (const p of parts) {
1254
+ const m = p.match(/^U\+([0-9A-Fa-f?]+)(?:-([0-9A-Fa-f?]+))?$/);
1255
+ if (!m) continue;
1256
+ const a = m[1], b = m[2];
1257
+ const expand = (hex) => {
1258
+ if (!hex.includes("?")) return parseInt(hex, 16);
1259
+ const min = parseInt(hex.replace(/\?/g, "0"), 16);
1260
+ const max = parseInt(hex.replace(/\?/g, "F"), 16);
1261
+ return [min, max];
1262
+ };
1263
+ if (b) {
1264
+ const A = expand(a), B = expand(b);
1265
+ const min = Array.isArray(A) ? A[0] : A;
1266
+ const max = Array.isArray(B) ? B[1] : B;
1267
+ ranges.push([Math.min(min, max), Math.max(min, max)]);
1268
+ } else {
1269
+ const X = expand(a);
1270
+ if (Array.isArray(X)) ranges.push([X[0], X[1]]);
1271
+ else ranges.push([X, X]);
1272
+ }
1273
+ }
1274
+ return ranges;
840
1275
  }
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);
1276
+ function unicodeIntersects(used, ranges) {
1277
+ if (!ranges.length) return true;
1278
+ if (!used || used.size === 0) return true;
1279
+ for (const cp of used) {
1280
+ for (const [a, b] of ranges) if (cp >= a && cp <= b) return true;
1281
+ }
1282
+ return false;
1283
+ }
1284
+ function extractSrcUrls(srcValue, baseHref) {
1285
+ const urls = [];
1286
+ if (!srcValue) return urls;
1287
+ for (const m of srcValue.matchAll(URL_RE)) {
1288
+ let u = (m[2] || "").trim();
1289
+ if (!u || u.startsWith("data:")) continue;
1290
+ if (!/^https?:/i.test(u)) {
1291
+ try {
1292
+ u = new URL(u, baseHref || location.href).href;
1293
+ } catch (e) {
1294
+ }
851
1295
  }
1296
+ urls.push(u);
852
1297
  }
1298
+ return urls;
853
1299
  }
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;
1300
+ function inlineUrlsInCssBlock(cssBlock, baseHref) {
1301
+ return __async(this, null, function* () {
1302
+ var _a2, _b2, _c2, _d2, _e2;
1303
+ let out = cssBlock;
1304
+ for (const m of cssBlock.matchAll(URL_RE)) {
1305
+ const raw = extractURL(m[0]);
1306
+ if (!raw) continue;
1307
+ let abs = raw;
1308
+ if (!abs.startsWith("http") && !abs.startsWith("data:")) {
1309
+ try {
1310
+ abs = new URL(abs, baseHref || location.href).href;
1311
+ } catch (e) {
1312
+ }
878
1313
  }
879
- if (!loadedFonts.has(key)) {
880
- cssFinal = cssFinal.replace(face, "");
1314
+ if (isIconFont2(abs)) continue;
1315
+ if ((_a2 = cache.resource) == null ? void 0 : _a2.has(abs)) {
1316
+ (_b2 = cache.font) == null ? void 0 : _b2.add(abs);
1317
+ out = out.replace(m[0], `url(${cache.resource.get(abs)})`);
881
1318
  continue;
882
1319
  }
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;
1320
+ if ((_c2 = cache.font) == null ? void 0 : _c2.has(abs)) continue;
1321
+ try {
1322
+ const r = yield snapFetch(abs, { as: "dataURL", useProxy, silent: true });
1323
+ if (r.ok && typeof r.data === "string") {
1324
+ const b64 = r.data;
1325
+ (_d2 = cache.resource) == null ? void 0 : _d2.set(abs, b64);
1326
+ (_e2 = cache.font) == null ? void 0 : _e2.add(abs);
1327
+ out = out.replace(m[0], `url(${b64})`);
891
1328
  }
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;
1329
+ } catch (e) {
1330
+ console.warn("[snapDOM] Failed to fetch font resource:", abs);
1331
+ }
1332
+ }
1333
+ return out;
1334
+ });
1335
+ }
1336
+ function subsetFromRanges(ranges) {
1337
+ if (!ranges.length) return null;
1338
+ const hit = (a, b) => ranges.some(([x, y]) => !(y < a || x > b));
1339
+ const latin = hit(0, 255) || hit(305, 305);
1340
+ const latinExt = hit(256, 591) || hit(7680, 7935);
1341
+ const greek = hit(880, 1023);
1342
+ const cyr = hit(1024, 1279);
1343
+ const viet = hit(7840, 7929) || hit(258, 259) || hit(416, 417) || hit(431, 432);
1344
+ if (viet) return "vietnamese";
1345
+ if (cyr) return "cyrillic";
1346
+ if (greek) return "greek";
1347
+ if (latinExt) return "latin-ext";
1348
+ if (latin) return "latin";
1349
+ return null;
1350
+ }
1351
+ function buildSimpleExcluder(ex = {}) {
1352
+ const famSet = new Set((ex.families || []).map((s) => String(s).toLowerCase()));
1353
+ const domSet = new Set((ex.domains || []).map((s) => String(s).toLowerCase()));
1354
+ const subSet = new Set((ex.subsets || []).map((s) => String(s).toLowerCase()));
1355
+ return (meta, parsedRanges) => {
1356
+ if (famSet.size && famSet.has(meta.family.toLowerCase())) return true;
1357
+ if (domSet.size) {
1358
+ for (const u of meta.srcUrls) {
1359
+ try {
1360
+ if (domSet.has(new URL(u).host.toLowerCase())) return true;
1361
+ } catch (e) {
897
1362
  }
898
- if (cache.font.has(url)) continue;
1363
+ }
1364
+ }
1365
+ if (subSet.size) {
1366
+ const label = subsetFromRanges(parsedRanges);
1367
+ if (label && subSet.has(label)) return true;
1368
+ }
1369
+ return false;
1370
+ };
1371
+ }
1372
+ const simpleExcluder = buildSimpleExcluder(exclude);
1373
+ function dedupeFontFaces(cssText) {
1374
+ var _a2, _b2, _c2, _d2, _e2, _f2;
1375
+ if (!cssText) return cssText;
1376
+ const FACE_RE2 = /@font-face[^{}]*\{[^}]*\}/gi;
1377
+ const seen = /* @__PURE__ */ new Set();
1378
+ const out = [];
1379
+ for (const block of cssText.match(FACE_RE2) || []) {
1380
+ const familyRaw = ((_a2 = block.match(/font-family:\s*([^;]+);/i)) == null ? void 0 : _a2[1]) || "";
1381
+ const family = pickPrimaryFamily(familyRaw);
1382
+ const weightSpec = (((_b2 = block.match(/font-weight:\s*([^;]+);/i)) == null ? void 0 : _b2[1]) || "400").trim();
1383
+ const styleSpec = (((_c2 = block.match(/font-style:\s*([^;]+);/i)) == null ? void 0 : _c2[1]) || "normal").trim();
1384
+ const stretchSpec = (((_d2 = block.match(/font-stretch:\s*([^;]+);/i)) == null ? void 0 : _d2[1]) || "100%").trim();
1385
+ const urange = (((_e2 = block.match(/unicode-range:\s*([^;]+);/i)) == null ? void 0 : _e2[1]) || "").trim();
1386
+ const srcRaw = (((_f2 = block.match(/src\s*:\s*([^;]+);/i)) == null ? void 0 : _f2[1]) || "").trim();
1387
+ const urls = extractSrcUrls(srcRaw, location.href);
1388
+ const srcPart = urls.length ? urls.map((u) => String(u).toLowerCase()).sort().join("|") : srcRaw.toLowerCase();
1389
+ const key = [
1390
+ String(family || "").toLowerCase(),
1391
+ weightSpec,
1392
+ styleSpec,
1393
+ stretchSpec,
1394
+ urange.toLowerCase(),
1395
+ srcPart
1396
+ ].join("|");
1397
+ if (!seen.has(key)) {
1398
+ seen.add(key);
1399
+ out.push(block);
1400
+ }
1401
+ }
1402
+ if (out.length === 0) return cssText;
1403
+ let i = 0;
1404
+ return cssText.replace(FACE_RE2, () => out[i++] || "");
1405
+ }
1406
+ function buildFontsCacheKey() {
1407
+ const req = Array.from(required || []).sort().join("|");
1408
+ const ex = exclude ? JSON.stringify({
1409
+ families: (exclude.families || []).map((s) => String(s).toLowerCase()).sort(),
1410
+ domains: (exclude.domains || []).map((s) => String(s).toLowerCase()).sort(),
1411
+ subsets: (exclude.subsets || []).map((s) => String(s).toLowerCase()).sort()
1412
+ }) : "";
1413
+ const lf = (localFonts || []).map((f) => `${(f.family || "").toLowerCase()}::${f.weight || "normal"}::${f.style || "normal"}::${f.src || ""}`).sort().join("|");
1414
+ const px = useProxy || "";
1415
+ return `fonts-embed-css::req=${req}::ex=${ex}::lf=${lf}::px=${px}`;
1416
+ }
1417
+ const cacheKey = buildFontsCacheKey();
1418
+ if ((_a = cache.resource) == null ? void 0 : _a.has(cacheKey)) {
1419
+ const css = cache.resource.get(cacheKey);
1420
+ return css;
1421
+ }
1422
+ const requiredFamilies = familiesFromRequired(required);
1423
+ const importUrls = [];
1424
+ for (const styleTag of document.querySelectorAll("style")) {
1425
+ const cssText = styleTag.textContent || "";
1426
+ for (const m of cssText.matchAll(IMPORT_RE)) {
1427
+ const u = m[1];
1428
+ if (!u || isIconFont2(u)) continue;
1429
+ const hasLink = !!document.querySelector(`link[rel="stylesheet"][href="${u}"]`);
1430
+ if (!hasLink) importUrls.push(u);
1431
+ }
1432
+ }
1433
+ if (importUrls.length) {
1434
+ yield Promise.all(importUrls.map((u) => new Promise((resolve) => {
1435
+ if (document.querySelector(`link[rel="stylesheet"][href="${u}"]`)) return resolve(null);
1436
+ const link = document.createElement("link");
1437
+ link.rel = "stylesheet";
1438
+ link.href = u;
1439
+ link.setAttribute("data-snapdom", "injected-import");
1440
+ link.onload = () => resolve(link);
1441
+ link.onerror = () => resolve(null);
1442
+ document.head.appendChild(link);
1443
+ })));
1444
+ }
1445
+ let finalCSS = "";
1446
+ const linkNodes = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter((l) => !!l.href);
1447
+ for (const link of linkNodes) {
1448
+ try {
1449
+ if (isIconFont2(link.href)) continue;
1450
+ let cssText = "";
1451
+ let sameOrigin = false;
1452
+ try {
1453
+ sameOrigin = new URL(link.href, location.href).origin === location.origin;
1454
+ } catch (e) {
1455
+ }
1456
+ if (!sameOrigin) {
1457
+ if (!isLikelyFontStylesheet(link.href, requiredFamilies)) continue;
1458
+ }
1459
+ if (sameOrigin) {
1460
+ const sheet = Array.from(document.styleSheets).find((s) => s.href === link.href);
1461
+ if (sheet) {
899
1462
  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})`);
1463
+ const rules = sheet.cssRules || [];
1464
+ cssText = Array.from(rules).map((r) => r.cssText).join("");
910
1465
  } catch (e) {
911
- console.warn("[snapdom] Failed to fetch font resource:", url);
912
1466
  }
913
1467
  }
914
- cssFinal = cssFinal.replace(face, inlined);
915
1468
  }
916
- finalCSS += cssFinal + "\n";
1469
+ if (!cssText) {
1470
+ const res = yield snapFetch(link.href, { as: "text", useProxy });
1471
+ cssText = res.data;
1472
+ if (isIconFont2(link.href)) continue;
1473
+ }
1474
+ let facesOut = "";
1475
+ for (const face of cssText.match(FACE_RE) || []) {
1476
+ const famRaw = (((_b = face.match(/font-family:\s*([^;]+);/i)) == null ? void 0 : _b[1]) || "").trim();
1477
+ const family = pickPrimaryFamily(famRaw);
1478
+ if (!family || isIconFont2(family)) {
1479
+ continue;
1480
+ }
1481
+ const weightSpec = (((_c = face.match(/font-weight:\s*([^;]+);/i)) == null ? void 0 : _c[1]) || "400").trim();
1482
+ const styleSpec = (((_d = face.match(/font-style:\s*([^;]+);/i)) == null ? void 0 : _d[1]) || "normal").trim();
1483
+ const stretchSpec = (((_e = face.match(/font-stretch:\s*([^;]+);/i)) == null ? void 0 : _e[1]) || "100%").trim();
1484
+ const urange = (((_f = face.match(/unicode-range:\s*([^;]+);/i)) == null ? void 0 : _f[1]) || "").trim();
1485
+ const srcRaw = (((_g = face.match(/src\s*:\s*([^;]+);/i)) == null ? void 0 : _g[1]) || "").trim();
1486
+ const srcUrls = extractSrcUrls(srcRaw, link.href);
1487
+ if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) {
1488
+ continue;
1489
+ }
1490
+ const ranges = parseUnicodeRange(urange);
1491
+ if (!unicodeIntersects(usedCodepoints, ranges)) {
1492
+ continue;
1493
+ }
1494
+ const meta = { family, weightSpec, styleSpec, stretchSpec, unicodeRange: urange, srcRaw, srcUrls, href: link.href };
1495
+ if (exclude && simpleExcluder(meta, ranges)) {
1496
+ continue;
1497
+ }
1498
+ const newFace = /url\(/i.test(srcRaw) ? yield inlineUrlsInCssBlock(face, link.href) : face;
1499
+ facesOut += newFace + "";
1500
+ }
1501
+ if (facesOut.trim()) finalCSS += facesOut + "";
917
1502
  } catch (e) {
918
- console.warn("[snapdom] Failed to fetch CSS:", link.href);
1503
+ console.warn("[snapdom] Failed to process stylesheet:", link.href);
919
1504
  }
920
1505
  }
921
1506
  for (const sheet of document.styleSheets) {
922
1507
  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
- }
1508
+ if (sheet.href && linkNodes.some((l) => l.href === sheet.href)) continue;
1509
+ const rules = sheet.cssRules || [];
1510
+ for (const rule of rules) {
1511
+ if (rule.type !== CSSRule.FONT_FACE_RULE) continue;
1512
+ const famRaw = (rule.style.getPropertyValue("font-family") || "").trim();
1513
+ const family = pickPrimaryFamily(famRaw);
1514
+ if (!family || isIconFont2(family)) continue;
1515
+ const weightSpec = (rule.style.getPropertyValue("font-weight") || "400").trim();
1516
+ const styleSpec = (rule.style.getPropertyValue("font-style") || "normal").trim();
1517
+ const stretchSpec = (rule.style.getPropertyValue("font-stretch") || "100%").trim();
1518
+ const srcRaw = (rule.style.getPropertyValue("src") || "").trim();
1519
+ const urange = (rule.style.getPropertyValue("unicode-range") || "").trim();
1520
+ const srcUrls = extractSrcUrls(srcRaw, sheet.href || location.href);
1521
+ if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
1522
+ const ranges = parseUnicodeRange(urange);
1523
+ if (!unicodeIntersects(usedCodepoints, ranges)) continue;
1524
+ const meta = { family, weightSpec, styleSpec, stretchSpec, unicodeRange: urange, srcRaw, srcUrls, href: sheet.href || location.href };
1525
+ if (exclude && simpleExcluder(meta, ranges)) continue;
1526
+ if (/url\(/i.test(srcRaw)) {
1527
+ const inlinedSrc = yield inlineUrlsInCssBlock(srcRaw, sheet.href || location.href);
1528
+ finalCSS += `@font-face{font-family:${family};src:${inlinedSrc};font-style:${styleSpec};font-weight:${weightSpec};font-stretch:${stretchSpec};${urange ? `unicode-range:${urange};` : ""}}`;
1529
+ } else {
1530
+ finalCSS += `@font-face{font-family:${family};src:${srcRaw};font-style:${styleSpec};font-weight:${weightSpec};font-stretch:${stretchSpec};${urange ? `unicode-range:${urange};` : ""}}`;
974
1531
  }
975
1532
  }
976
1533
  } catch (e) {
977
- console.warn("[snapdom] Cannot access stylesheet", sheet.href, e);
978
1534
  }
979
1535
  }
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)) {
1536
+ try {
1537
+ for (const f of document.fonts || []) {
1538
+ if (!f || !f.family || f.status !== "loaded" || !f._snapdomSrc) continue;
1539
+ const fam = String(f.family).replace(/^['"]+|['"]+$/g, "");
1540
+ if (isIconFont2(fam)) continue;
1541
+ if (!requiredIndex.has(fam)) continue;
1542
+ if ((exclude == null ? void 0 : exclude.families) && exclude.families.some((n) => String(n).toLowerCase() === fam.toLowerCase())) {
1543
+ continue;
1544
+ }
1545
+ let b64 = f._snapdomSrc;
1546
+ if (!String(b64).startsWith("data:")) {
1547
+ if ((_h = cache.resource) == null ? void 0 : _h.has(f._snapdomSrc)) {
1548
+ b64 = cache.resource.get(f._snapdomSrc);
1549
+ (_i = cache.font) == null ? void 0 : _i.add(f._snapdomSrc);
1550
+ } else if (!((_j = cache.font) == null ? void 0 : _j.has(f._snapdomSrc))) {
989
1551
  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);
1552
+ const r = yield snapFetch(f._snapdomSrc, { as: "dataURL", useProxy, silent: true });
1553
+ if (r.ok && typeof r.data === "string") {
1554
+ b64 = r.data;
1555
+ (_k = cache.resource) == null ? void 0 : _k.set(f._snapdomSrc, b64);
1556
+ (_l = cache.font) == null ? void 0 : _l.add(f._snapdomSrc);
1557
+ } else {
1558
+ continue;
1559
+ }
999
1560
  } catch (e) {
1000
- console.warn("[snapdom] Failed to fetch dynamic font src:", font._snapdomSrc);
1561
+ console.warn("[snapdom] Failed to fetch dynamic font src:", f._snapdomSrc);
1001
1562
  continue;
1002
1563
  }
1003
1564
  }
1004
1565
  }
1005
- finalCSS += `@font-face{font-family:'${font.family}';src:url(${b64});font-style:${font.style || "normal"};font-weight:${font.weight || "normal"};}`;
1566
+ finalCSS += `@font-face{font-family:'${fam}';src:url(${b64});font-style:${f.style || "normal"};font-weight:${f.weight || "normal"};}`;
1006
1567
  }
1568
+ } catch (e) {
1007
1569
  }
1008
1570
  for (const font of localFonts) {
1009
1571
  if (!font || typeof font !== "object") continue;
1010
- const { family, src, weight = "normal", style = "normal" } = font;
1011
- if (!family || !src) continue;
1572
+ const family = String(font.family || "").replace(/^['"]+|['"]+$/g, "");
1573
+ if (!family || isIconFont2(family)) continue;
1574
+ if (!requiredIndex.has(family)) continue;
1575
+ if ((exclude == null ? void 0 : exclude.families) && exclude.families.some((n) => String(n).toLowerCase() === family.toLowerCase())) continue;
1576
+ const weight = font.weight != null ? String(font.weight) : "normal";
1577
+ const style = font.style != null ? String(font.style) : "normal";
1578
+ const stretch = font.stretchPct != null ? `${font.stretchPct}%` : "100%";
1579
+ const src = String(font.src || "");
1012
1580
  let b64 = src;
1013
1581
  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;
1582
+ if ((_m = cache.resource) == null ? void 0 : _m.has(src)) {
1583
+ b64 = cache.resource.get(src);
1584
+ (_n = cache.font) == null ? void 0 : _n.add(src);
1585
+ } else if (!((_o = cache.font) == null ? void 0 : _o.has(src))) {
1586
+ try {
1587
+ const r = yield snapFetch(src, { as: "dataURL", useProxy, silent: true });
1588
+ if (r.ok && typeof r.data === "string") {
1589
+ b64 = r.data;
1590
+ (_p = cache.resource) == null ? void 0 : _p.set(src, b64);
1591
+ (_q = cache.font) == null ? void 0 : _q.add(src);
1592
+ } else {
1593
+ continue;
1594
+ }
1595
+ } catch (e) {
1596
+ console.warn("[snapdom] Failed to fetch localFonts src:", src);
1597
+ continue;
1598
+ }
1027
1599
  }
1028
- } else {
1029
- cache.resource.set(src, b64);
1030
- cache.font.add(src);
1031
1600
  }
1032
- finalCSS += `@font-face{font-family:'${family}';src:url(${b64});font-style:${style};font-weight:${weight};}`;
1601
+ finalCSS += `@font-face{font-family:'${family}';src:url(${b64});font-style:${style};font-weight:${weight};font-stretch:${stretch};}`;
1033
1602
  }
1034
1603
  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
- }
1604
+ finalCSS = dedupeFontFaces(finalCSS);
1605
+ (_r = cache.resource) == null ? void 0 : _r.set(cacheKey, finalCSS);
1042
1606
  }
1043
1607
  return finalCSS;
1044
1608
  });
1045
1609
  }
1610
+ function collectUsedFontVariants(root) {
1611
+ const req = /* @__PURE__ */ new Set();
1612
+ if (!root) return req;
1613
+ const tw = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null);
1614
+ const addFromStyle = (cs) => {
1615
+ const family = pickPrimaryFamily(cs.fontFamily);
1616
+ if (!family) return;
1617
+ const key = (w, s, st) => `${family}__${normWeight(w)}__${normStyle(s)}__${normStretchPct(st)}`;
1618
+ req.add(key(cs.fontWeight, cs.fontStyle, cs.fontStretch));
1619
+ };
1620
+ addFromStyle(getComputedStyle(root));
1621
+ const csBeforeRoot = getComputedStyle(root, "::before");
1622
+ if (csBeforeRoot && csBeforeRoot.content && csBeforeRoot.content !== "none") addFromStyle(csBeforeRoot);
1623
+ const csAfterRoot = getComputedStyle(root, "::after");
1624
+ if (csAfterRoot && csAfterRoot.content && csAfterRoot.content !== "none") addFromStyle(csAfterRoot);
1625
+ while (tw.nextNode()) {
1626
+ const el = (
1627
+ /** @type {Element} */
1628
+ tw.currentNode
1629
+ );
1630
+ const cs = getComputedStyle(el);
1631
+ addFromStyle(cs);
1632
+ const b = getComputedStyle(el, "::before");
1633
+ if (b && b.content && b.content !== "none") addFromStyle(b);
1634
+ const a = getComputedStyle(el, "::after");
1635
+ if (a && a.content && a.content !== "none") addFromStyle(a);
1636
+ }
1637
+ return req;
1638
+ }
1639
+ function collectUsedCodepoints(root) {
1640
+ const used = /* @__PURE__ */ new Set();
1641
+ const pushText = (txt) => {
1642
+ if (!txt) return;
1643
+ for (const ch of txt) used.add(ch.codePointAt(0));
1644
+ };
1645
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null);
1646
+ while (walker.nextNode()) {
1647
+ const n = walker.currentNode;
1648
+ if (n.nodeType === Node.TEXT_NODE) {
1649
+ pushText(n.nodeValue || "");
1650
+ } else if (n.nodeType === Node.ELEMENT_NODE) {
1651
+ const el = (
1652
+ /** @type {Element} */
1653
+ n
1654
+ );
1655
+ for (const pseudo of ["::before", "::after"]) {
1656
+ const cs = getComputedStyle(el, pseudo);
1657
+ const c = cs == null ? void 0 : cs.getPropertyValue("content");
1658
+ if (!c || c === "none") continue;
1659
+ if (/^"/.test(c) || /^'/.test(c)) {
1660
+ pushText(c.slice(1, -1));
1661
+ } else {
1662
+ const matches = c.match(/\\[0-9A-Fa-f]{1,6}/g);
1663
+ if (matches) {
1664
+ for (const m of matches) {
1665
+ try {
1666
+ used.add(parseInt(m.slice(1), 16));
1667
+ } catch (e) {
1668
+ }
1669
+ }
1670
+ }
1671
+ }
1672
+ }
1673
+ }
1674
+ }
1675
+ return used;
1676
+ }
1677
+ function ensureFontsReady(families, warmupRepetitions = 1) {
1678
+ return __async(this, null, function* () {
1679
+ try {
1680
+ yield document.fonts.ready;
1681
+ } catch (e) {
1682
+ }
1683
+ const fams = Array.from(families || []).filter(Boolean);
1684
+ if (fams.length === 0) return;
1685
+ const warmupOnce = () => {
1686
+ const container = document.createElement("div");
1687
+ container.style.cssText = `position: absolute !important;left: -9999px !important;top: 0 !important;opacity: 0 !important;pointer-events: none !important;contain: layout size style;`;
1688
+ for (const fam of fams) {
1689
+ const span = document.createElement("span");
1690
+ span.textContent = "AaBbGg1234\xC1\xC9\xCD\xD3\xDA\xE7\xF1\u2014\u221E";
1691
+ span.style.fontFamily = `"${fam}"`;
1692
+ span.style.fontWeight = "700";
1693
+ span.style.fontStyle = "italic";
1694
+ span.style.fontSize = "32px";
1695
+ span.style.lineHeight = "1";
1696
+ span.style.whiteSpace = "nowrap";
1697
+ span.style.margin = "0";
1698
+ span.style.padding = "0";
1699
+ container.appendChild(span);
1700
+ }
1701
+ document.body.appendChild(container);
1702
+ container.offsetWidth;
1703
+ document.body.removeChild(container);
1704
+ };
1705
+ for (let i = 0; i < Math.max(1, warmupRepetitions); i++) {
1706
+ warmupOnce();
1707
+ yield new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
1708
+ }
1709
+ });
1710
+ }
1046
1711
 
1047
1712
  // src/modules/pseudo.js
1048
- function inlinePseudoElements(source, clone, styleMap, styleCache, options) {
1713
+ function inlinePseudoElements(source, clone, sessionCache, options) {
1049
1714
  return __async(this, null, function* () {
1050
1715
  var _a;
1051
1716
  if (!(source instanceof Element) || !(clone instanceof Element)) return;
@@ -1075,8 +1740,8 @@
1075
1740
  span.textContent = first;
1076
1741
  span.dataset.snapdomPseudo = "::first-letter";
1077
1742
  const snapshot2 = snapshotComputedStyle(style);
1078
- const key2 = getStyleKey(snapshot2, "span", options);
1079
- styleMap.set(span, key2);
1743
+ const key2 = getStyleKey(snapshot2, "span");
1744
+ sessionCache.styleMap.set(span, key2);
1080
1745
  const restNode = document.createTextNode(rest);
1081
1746
  clone.replaceChild(restNode, textNode);
1082
1747
  clone.insertBefore(span, restNode);
@@ -1096,7 +1761,7 @@
1096
1761
  const borderStyle = style.borderStyle;
1097
1762
  const borderWidth = parseFloat(style.borderWidth);
1098
1763
  const transform = style.transform;
1099
- const isIconFont2 = isIconFont(fontFamily);
1764
+ const isIconFont22 = isIconFont2(fontFamily);
1100
1765
  const hasExplicitContent = content !== "none" && cleanContent !== "";
1101
1766
  const hasBg = bg && bg !== "none";
1102
1767
  const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
@@ -1109,9 +1774,9 @@
1109
1774
  pseudoEl.dataset.snapdomPseudo = pseudo;
1110
1775
  pseudoEl.style.verticalAlign = "middle";
1111
1776
  const snapshot = snapshotComputedStyle(style);
1112
- const key = getStyleKey(snapshot, "span", options);
1113
- styleMap.set(pseudoEl, key);
1114
- if (isIconFont2 && cleanContent.length === 1) {
1777
+ const key = getStyleKey(snapshot, "span");
1778
+ sessionCache.styleMap.set(pseudoEl, key);
1779
+ if (isIconFont22 && cleanContent.length === 1) {
1115
1780
  const { dataUrl, width: width2, height: height2 } = yield iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
1116
1781
  const imgEl = document.createElement("img");
1117
1782
  imgEl.src = dataUrl;
@@ -1123,15 +1788,15 @@
1123
1788
  if (rawUrl == null ? void 0 : rawUrl.trim()) {
1124
1789
  try {
1125
1790
  const imgEl = document.createElement("img");
1126
- const dataUrl = yield fetchImage(safeEncodeURI(rawUrl), options);
1127
- imgEl.src = dataUrl;
1791
+ const dataUrl = yield snapFetch(safeEncodeURI(rawUrl), { as: "dataURL", useProxy: options.useProxy });
1792
+ imgEl.src = dataUrl.data;
1128
1793
  imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
1129
1794
  pseudoEl.appendChild(imgEl);
1130
1795
  } catch (e) {
1131
1796
  console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
1132
1797
  }
1133
1798
  }
1134
- } else if (!isIconFont2 && hasExplicitContent) {
1799
+ } else if (!isIconFont22 && hasExplicitContent) {
1135
1800
  pseudoEl.textContent = cleanContent;
1136
1801
  }
1137
1802
  if (hasBg) {
@@ -1159,7 +1824,7 @@
1159
1824
  const sChildren = Array.from(source.children);
1160
1825
  const cChildren = Array.from(clone.children).filter((child) => !child.dataset.snapdomPseudo);
1161
1826
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
1162
- yield inlinePseudoElements(sChildren[i], cChildren[i], styleMap, styleCache, options);
1827
+ yield inlinePseudoElements(sChildren[i], cChildren[i], sessionCache, options);
1163
1828
  }
1164
1829
  });
1165
1830
  }
@@ -1216,13 +1881,16 @@
1216
1881
 
1217
1882
  // src/core/prepare.js
1218
1883
  function prepareClone(_0) {
1219
- return __async(this, arguments, function* (element, compress = false, embedFonts = false, options = {}) {
1884
+ return __async(this, arguments, function* (element, options = {}) {
1220
1885
  var _a, _b;
1221
- const styleMap = /* @__PURE__ */ new Map();
1222
- const styleCache = /* @__PURE__ */ new WeakMap();
1223
- const nodeMap = /* @__PURE__ */ new Map();
1886
+ const sessionCache = {
1887
+ styleMap: cache.session.styleMap,
1888
+ styleCache: cache.session.styleCache,
1889
+ nodeMap: cache.session.nodeMap
1890
+ };
1224
1891
  let clone;
1225
1892
  let classCSS = "";
1893
+ let shadowScopedCSS = "";
1226
1894
  stabilizeLayout(element);
1227
1895
  try {
1228
1896
  inlineExternalDefsAndSymbols(element);
@@ -1230,43 +1898,45 @@
1230
1898
  console.warn("inlineExternal defs or symbol failed:", e);
1231
1899
  }
1232
1900
  try {
1233
- clone = deepClone(element, styleMap, styleCache, nodeMap, compress, options, element);
1901
+ clone = deepClone(element, sessionCache, options, element);
1234
1902
  } catch (e) {
1235
1903
  console.warn("deepClone failed:", e);
1236
1904
  throw e;
1237
1905
  }
1238
1906
  try {
1239
- yield inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts, options.useProxy);
1907
+ yield inlinePseudoElements(element, clone, sessionCache, options);
1240
1908
  } catch (e) {
1241
1909
  console.warn("inlinePseudoElements failed:", e);
1242
1910
  }
1243
1911
  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
- }
1912
+ try {
1913
+ const styleNodes = clone.querySelectorAll("style[data-sd]");
1914
+ for (const s of styleNodes) {
1915
+ shadowScopedCSS += s.textContent || "";
1916
+ s.remove();
1262
1917
  }
1263
- } else {
1264
- for (const [node, key] of styleMap.entries()) {
1265
- if (node.tagName === "STYLE") continue;
1918
+ } catch (e) {
1919
+ }
1920
+ const keyToClass = generateCSSClasses(sessionCache.styleMap);
1921
+ classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
1922
+ classCSS = shadowScopedCSS + classCSS;
1923
+ for (const [node, key] of sessionCache.styleMap.entries()) {
1924
+ if (node.tagName === "STYLE") continue;
1925
+ if (node.getRootNode && node.getRootNode() instanceof ShadowRoot) {
1266
1926
  node.setAttribute("style", key.replace(/;/g, "; "));
1927
+ continue;
1928
+ }
1929
+ const className = keyToClass.get(key);
1930
+ if (className) node.classList.add(className);
1931
+ const bgImage = (_a = node.style) == null ? void 0 : _a.backgroundImage;
1932
+ const hasIcon = (_b = node.dataset) == null ? void 0 : _b.snapdomHasIcon;
1933
+ if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
1934
+ if (hasIcon) {
1935
+ node.style.verticalAlign = "middle";
1936
+ node.style.display = "inline";
1267
1937
  }
1268
1938
  }
1269
- for (const [cloneNode, originalNode] of nodeMap.entries()) {
1939
+ for (const [cloneNode, originalNode] of sessionCache.nodeMap.entries()) {
1270
1940
  const scrollX = originalNode.scrollLeft;
1271
1941
  const scrollY = originalNode.scrollTop;
1272
1942
  const hasScroll = scrollX || scrollY;
@@ -1285,9 +1955,9 @@
1285
1955
  cloneNode.appendChild(inner);
1286
1956
  }
1287
1957
  }
1288
- if (element === nodeMap.get(clone)) {
1289
- const computed = styleCache.get(element) || window.getComputedStyle(element);
1290
- styleCache.set(element, computed);
1958
+ if (element === sessionCache.nodeMap.get(clone)) {
1959
+ const computed = sessionCache.styleCache.get(element) || window.getComputedStyle(element);
1960
+ sessionCache.styleCache.set(element, computed);
1291
1961
  const transform = stripTranslate(computed.transform);
1292
1962
  clone.style.margin = "0";
1293
1963
  clone.style.position = "static";
@@ -1300,13 +1970,13 @@
1300
1970
  clone.style.clear = "none";
1301
1971
  clone.style.transform = transform || "";
1302
1972
  }
1303
- for (const [cloneNode, originalNode] of nodeMap.entries()) {
1973
+ for (const [cloneNode, originalNode] of sessionCache.nodeMap.entries()) {
1304
1974
  if (originalNode.tagName === "PRE") {
1305
1975
  cloneNode.style.marginTop = "0";
1306
1976
  cloneNode.style.marginBlockStart = "0";
1307
1977
  }
1308
1978
  }
1309
- return { clone, classCSS, styleCache };
1979
+ return { clone, classCSS, styleCache: sessionCache.styleCache };
1310
1980
  });
1311
1981
  }
1312
1982
  function stabilizeLayout(element) {
@@ -1324,22 +1994,27 @@
1324
1994
  var _blobToDataUrlCache = /* @__PURE__ */ new Map();
1325
1995
  function blobUrlToDataUrl(blobUrl) {
1326
1996
  return __async(this, null, function* () {
1997
+ var _a;
1998
+ if ((_a = cache.resource) == null ? void 0 : _a.has(blobUrl)) return cache.resource.get(blobUrl);
1327
1999
  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;
2000
+ const p = (() => __async(null, null, function* () {
2001
+ var _a2;
2002
+ const r = yield snapFetch(blobUrl, { as: "dataURL", silent: true });
2003
+ if (!r.ok || typeof r.data !== "string") {
2004
+ throw new Error(`[snapDOM] Failed to read blob URL: ${blobUrl}`);
2005
+ }
2006
+ (_a2 = cache.resource) == null ? void 0 : _a2.set(blobUrl, r.data);
2007
+ return r.data;
2008
+ }))();
2009
+ _blobToDataUrlCache.set(blobUrl, p);
2010
+ try {
2011
+ const data = yield p;
2012
+ _blobToDataUrlCache.set(blobUrl, data);
2013
+ return data;
2014
+ } catch (e) {
2015
+ _blobToDataUrlCache.delete(blobUrl);
2016
+ throw e;
2017
+ }
1343
2018
  });
1344
2019
  }
1345
2020
  var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
@@ -1463,17 +2138,36 @@
1463
2138
  }
1464
2139
  img.removeAttribute("srcset");
1465
2140
  img.removeAttribute("sizes");
1466
- const src = img.src;
1467
- try {
1468
- const dataUrl = yield fetchImage(src, { useProxy: options.useProxy });
1469
- img.src = dataUrl;
2141
+ const src = img.src || "";
2142
+ if (!src) return;
2143
+ const r = yield snapFetch(src, { as: "dataURL", useProxy: options.useProxy });
2144
+ if (r.ok && typeof r.data === "string" && r.data.startsWith("data:")) {
2145
+ img.src = r.data;
1470
2146
  if (!img.width) img.width = img.naturalWidth || 100;
1471
2147
  if (!img.height) img.height = img.naturalHeight || 100;
1472
- } catch (e) {
2148
+ return;
2149
+ }
2150
+ const w = img.width || img.naturalWidth || 100;
2151
+ const h = img.height || img.naturalHeight || 100;
2152
+ if (options.placeholders !== false) {
1473
2153
  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";
2154
+ fallback.style.cssText = [
2155
+ `width:${w}px`,
2156
+ `height:${h}px`,
2157
+ "background:#ccc",
2158
+ "display:inline-block",
2159
+ "text-align:center",
2160
+ `line-height:${h}px`,
2161
+ "color:#666",
2162
+ "font-size:12px",
2163
+ "overflow:hidden"
2164
+ ].join(";");
2165
+ fallback.textContent = "img";
1476
2166
  img.replaceWith(fallback);
2167
+ } else {
2168
+ const spacer = document.createElement("div");
2169
+ spacer.style.cssText = `display:inline-block;width:${w}px;height:${h}px;visibility:hidden;`;
2170
+ img.replaceWith(spacer);
1477
2171
  }
1478
2172
  });
1479
2173
  for (let i = 0; i < imgs.length; i += 4) {
@@ -1526,10 +2220,6 @@
1526
2220
  cloneNode.style.setProperty(prop, inlined.join(", "));
1527
2221
  }
1528
2222
  }
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
2223
  const sChildren = Array.from(srcNode.children);
1534
2224
  const cChildren = Array.from(cloneNode.children);
1535
2225
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
@@ -1540,17 +2230,17 @@
1540
2230
  }
1541
2231
 
1542
2232
  // src/core/capture.js
1543
- function captureDOM(_0) {
1544
- return __async(this, arguments, function* (element, options = {}) {
2233
+ function captureDOM(element, options) {
2234
+ return __async(this, null, function* () {
1545
2235
  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;
2236
+ applyCachePolicy(options.cache);
2237
+ const fast = options.fast;
1548
2238
  let clone, classCSS, styleCache;
1549
2239
  let fontsCSS = "";
1550
2240
  let baseCSS = "";
1551
2241
  let dataURL;
1552
2242
  let svgString;
1553
- ({ clone, classCSS, styleCache } = yield prepareClone(element, compress, embedFonts, options));
2243
+ ({ clone, classCSS, styleCache } = yield prepareClone(element, options));
1554
2244
  yield new Promise((resolve) => {
1555
2245
  idle(() => __async(null, null, function* () {
1556
2246
  yield inlineImages(clone, options);
@@ -1563,28 +2253,34 @@
1563
2253
  resolve();
1564
2254
  }), { fast });
1565
2255
  });
1566
- if (embedFonts) {
2256
+ if (options.embedFonts) {
1567
2257
  yield new Promise((resolve) => {
1568
2258
  idle(() => __async(null, null, function* () {
1569
- fontsCSS = yield embedCustomFonts({ localFonts, useProxy });
2259
+ const required = collectUsedFontVariants(element);
2260
+ const usedCodepoints = collectUsedCodepoints(element);
2261
+ if (isSafari) {
2262
+ const families = new Set(
2263
+ Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
2264
+ );
2265
+ yield ensureFontsReady(families, 2);
2266
+ }
2267
+ fontsCSS = yield embedCustomFonts({ required, usedCodepoints, preCached: false, exclude: options.excludeFonts, useProxy: options.useProxy });
1570
2268
  resolve();
1571
2269
  }), { fast });
1572
2270
  });
1573
2271
  }
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
- }
2272
+ const usedTags = collectUsedTagNames(clone).sort();
2273
+ const tagKey = usedTags.join(",");
2274
+ if (cache.baseStyle.has(tagKey)) {
2275
+ baseCSS = cache.baseStyle.get(tagKey);
2276
+ } else {
2277
+ yield new Promise((resolve) => {
2278
+ idle(() => {
2279
+ baseCSS = generateDedupedBaseCSS(usedTags);
2280
+ cache.baseStyle.set(tagKey, baseCSS);
2281
+ resolve();
2282
+ }, { fast });
2283
+ });
1588
2284
  }
1589
2285
  yield new Promise((resolve) => {
1590
2286
  idle(() => {
@@ -1643,228 +2339,250 @@
1643
2339
  });
1644
2340
  }
1645
2341
 
1646
- // src/api/snapdom.js
1647
- function toImg(_0) {
1648
- return __async(this, arguments, function* (url, { scale = 1 } = {}) {
2342
+ // src/core/context.js
2343
+ function normalizeCachePolicy(v) {
2344
+ if (v === true) return "soft";
2345
+ if (v === false) return "disabled";
2346
+ if (typeof v === "string") {
2347
+ const s = v.toLowerCase().trim();
2348
+ if (s === "disabled" || s === "full" || s === "auto" || s === "soft") return (
2349
+ /** @type {CachePolicy} */
2350
+ s
2351
+ );
2352
+ }
2353
+ return "soft";
2354
+ }
2355
+ function createContext(options = {}) {
2356
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
2357
+ const resolvedFormat = (_a = options.format) != null ? _a : "png";
2358
+ const cachePolicy = normalizeCachePolicy(options.cache);
2359
+ return {
2360
+ // Debug & perf
2361
+ debug: (_b = options.debug) != null ? _b : false,
2362
+ fast: (_c = options.fast) != null ? _c : true,
2363
+ scale: (_d = options.scale) != null ? _d : 1,
2364
+ // DOM filters
2365
+ exclude: (_e = options.exclude) != null ? _e : [],
2366
+ filter: (_f = options.filter) != null ? _f : null,
2367
+ placeholders: options.placeholders !== false,
2368
+ // default true
2369
+ // Fuentes
2370
+ embedFonts: (_g = options.embedFonts) != null ? _g : false,
2371
+ iconFonts: Array.isArray(options.iconFonts) ? options.iconFonts : options.iconFonts ? [options.iconFonts] : [],
2372
+ localFonts: Array.isArray(options.localFonts) ? options.localFonts : [],
2373
+ excludeFonts: (_h = options.excludeFonts) != null ? _h : void 0,
2374
+ /** Cache policy (disabled|full|auto|soft). Default: "soft" */
2375
+ /** @type {CachePolicy} */
2376
+ cache: cachePolicy,
2377
+ // Red
2378
+ useProxy: typeof options.useProxy === "string" ? options.useProxy : "",
2379
+ // Salida
2380
+ width: (_i = options.width) != null ? _i : null,
2381
+ height: (_j = options.height) != null ? _j : null,
2382
+ format: resolvedFormat,
2383
+ type: (_k = options.type) != null ? _k : "svg",
2384
+ quality: (_l = options.quality) != null ? _l : 0.92,
2385
+ dpr: (_m = options.dpr) != null ? _m : window.devicePixelRatio || 1,
2386
+ backgroundColor: (_n = options.backgroundColor) != null ? _n : ["jpg", "jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null,
2387
+ filename: (_o = options.filename) != null ? _o : "snapDOM"
2388
+ // Plugins (reservado para futura activación)
2389
+ // plugins: normalizePlugins(...),
2390
+ };
2391
+ }
2392
+
2393
+ // src/exporters/toImg.js
2394
+ function toImg(url, options) {
2395
+ return __async(this, null, function* () {
1649
2396
  const img = new Image();
1650
2397
  img.src = url;
1651
2398
  yield img.decode();
1652
- if (scale !== 1) {
1653
- img.style.width = `${img.naturalWidth * scale}px`;
1654
- img.style.height = `${img.naturalHeight * scale}px`;
2399
+ if (options.scale !== 1) {
2400
+ img.style.width = `${img.naturalWidth * options.scale}px`;
2401
+ img.style.height = `${img.naturalHeight * options.scale}px`;
1655
2402
  }
1656
2403
  return img;
1657
2404
  });
1658
2405
  }
1659
- function toCanvas(_0) {
1660
- return __async(this, arguments, function* (url, { dpr = 1, scale = 1 } = {}) {
2406
+
2407
+ // src/exporters/toCanvas.js
2408
+ function toCanvas(url, options) {
2409
+ return __async(this, null, function* () {
2410
+ const { scale: scale2, dpr } = options;
2411
+ const safari = isSafari();
1661
2412
  const img = new Image();
1662
- img.src = url;
1663
2413
  img.crossOrigin = "anonymous";
1664
2414
  img.loading = "eager";
1665
2415
  img.decoding = "sync";
1666
- const isSafariBrowser = isSafari();
1667
- let appended = false;
1668
- if (isSafariBrowser) {
1669
- document.body.appendChild(img);
1670
- appended = true;
2416
+ img.src = url;
2417
+ let tempSlot = null;
2418
+ if (safari) {
2419
+ tempSlot = document.createElement("div");
2420
+ tempSlot.setAttribute("aria-hidden", "true");
2421
+ tempSlot.style.cssText = "position:absolute;left:-99999px;top:0;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none;contain:size layout style;";
2422
+ document.body.appendChild(tempSlot);
2423
+ tempSlot.appendChild(img);
1671
2424
  }
1672
2425
  yield img.decode();
1673
- if (isSafariBrowser) {
1674
- yield new Promise((resolve) => setTimeout(resolve, 100));
2426
+ if (safari) {
2427
+ yield new Promise((r) => setTimeout(r, 100));
1675
2428
  }
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;
2429
+ const width = img.naturalWidth * scale2;
2430
+ const height = img.naturalHeight * scale2;
1682
2431
  const canvas = document.createElement("canvas");
1683
2432
  canvas.width = Math.ceil(width * dpr);
1684
2433
  canvas.height = Math.ceil(height * dpr);
1685
2434
  canvas.style.width = `${width}px`;
1686
2435
  canvas.style.height = `${height}px`;
1687
2436
  const ctx = canvas.getContext("2d");
1688
- ctx.scale(dpr, dpr);
2437
+ if (dpr !== 1) ctx.scale(dpr, dpr);
1689
2438
  ctx.drawImage(img, 0, 0, width, height);
1690
- if (appended) img.remove();
2439
+ if (tempSlot && tempSlot.parentNode) {
2440
+ try {
2441
+ tempSlot.parentNode.removeChild(tempSlot);
2442
+ } catch (e) {
2443
+ }
2444
+ }
1691
2445
  return canvas;
1692
2446
  });
1693
2447
  }
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";
2448
+
2449
+ // src/exporters/toBlob.js
2450
+ function toBlob(url, options) {
2451
+ return __async(this, null, function* () {
2452
+ const type = options.type;
1707
2453
  if (type === "svg") {
1708
2454
  const svgText = decodeURIComponent(url.split(",")[1]);
1709
2455
  return new Blob([svgText], { type: "image/svg+xml" });
1710
2456
  }
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;
2457
+ const canvas = yield toCanvas(url, options);
2458
+ const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
2459
+ return new Promise(
2460
+ (resolve) => finalCanvas.toBlob(
2461
+ (blob) => resolve(blob),
2462
+ `image/${type}`,
2463
+ options.quality
2464
+ )
2465
+ );
1729
2466
  });
1730
2467
  }
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);
2468
+
2469
+ // src/modules/rasterize.js
2470
+ function rasterize(url, options) {
2471
+ return __async(this, null, function* () {
2472
+ const canvas = yield toCanvas(url, options);
2473
+ const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
1736
2474
  const img = new Image();
1737
- img.src = canvas.toDataURL(`image/${format}`, quality);
2475
+ img.src = finalCanvas.toDataURL(`image/${options.format}`, options.quality);
1738
2476
  yield img.decode();
1739
- img.style.width = `${canvas.width / dpr}px`;
1740
- img.style.height = `${canvas.height / dpr}px`;
2477
+ img.style.width = `${finalCanvas.width / options.dpr}px`;
2478
+ img.style.height = `${finalCanvas.height / options.dpr}px`;
1741
2479
  return img;
1742
2480
  });
1743
2481
  }
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);
2482
+
2483
+ // src/exporters/download.js
2484
+ function download(url, options) {
2485
+ return __async(this, null, function* () {
2486
+ if (options.format === "svg") {
2487
+ const blob = yield toBlob(url, __spreadProps(__spreadValues({}, options), { type: "svg" }));
1748
2488
  const objectURL = URL.createObjectURL(blob);
1749
2489
  const a2 = document.createElement("a");
1750
2490
  a2.href = objectURL;
1751
- a2.download = `${filename}.svg`;
2491
+ a2.download = options.filename;
1752
2492
  a2.click();
1753
2493
  URL.revokeObjectURL(objectURL);
1754
2494
  return;
1755
2495
  }
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);
2496
+ const canvas = yield toCanvas(url, options);
2497
+ const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
1766
2498
  const a = document.createElement("a");
1767
- a.href = dataURL;
1768
- a.download = `${filename}.${format}`;
2499
+ a.href = finalCanvas.toDataURL(`image/${options.format}`, options.quality);
2500
+ a.download = options.filename;
1769
2501
  a.click();
1770
2502
  });
1771
2503
  }
1772
- function snapdom(_0) {
1773
- return __async(this, arguments, function* (element, options = {}) {
1774
- options = __spreadValues({ scale: 1 }, options);
2504
+
2505
+ // src/api/snapdom.js
2506
+ var INTERNAL_TOKEN = Symbol("snapdom.internal");
2507
+ function snapdom(element, userOptions) {
2508
+ return __async(this, null, function* () {
1775
2509
  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);
2510
+ const context = createContext(userOptions);
2511
+ if (context.iconFonts && context.iconFonts.length > 0) extendIconFonts(context.iconFonts);
2512
+ return snapdom.capture(element, context, INTERNAL_TOKEN);
1780
2513
  });
1781
2514
  }
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;
2515
+ snapdom.capture = (el, context, _token) => __async(null, null, function* () {
2516
+ if (_token !== INTERNAL_TOKEN) {
2517
+ throw new Error("[snapdom.capture] is internal. Use snapdom(...) instead.");
2518
+ }
2519
+ const url = yield captureDOM(el, context);
2520
+ const ensureContext = (opts) => __spreadValues(__spreadValues({}, context), opts || {});
2521
+ const withFormat = (format) => (opts) => rasterize(url, ensureContext(__spreadProps(__spreadValues({}, opts || {}), { format })));
1787
2522
  return {
1788
2523
  url,
1789
- options,
1790
2524
  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
- }
2525
+ toImg: (opts) => toImg(url, ensureContext(opts)),
2526
+ toCanvas: (opts) => toCanvas(url, ensureContext(opts)),
2527
+ toBlob: (opts) => toBlob(url, ensureContext(opts)),
2528
+ toPng: withFormat("png"),
2529
+ toJpg: withFormat("jpeg"),
2530
+ toWebp: withFormat("webp"),
2531
+ download: (opts) => download(url, ensureContext(opts))
1801
2532
  };
1802
2533
  });
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
- });
2534
+ snapdom.toRaw = (el, options) => snapdom(el, options).then((result) => result.toRaw());
2535
+ snapdom.toImg = (el, options) => snapdom(el, options).then((result) => result.toImg());
2536
+ snapdom.toCanvas = (el, options) => snapdom(el, options).then((result) => result.toCanvas());
2537
+ snapdom.toBlob = (el, options) => snapdom(el, options).then((result) => result.toBlob());
2538
+ snapdom.toPng = (el, options) => snapdom(el, __spreadProps(__spreadValues({}, options), { format: "png" })).then((result) => result.toPng());
2539
+ snapdom.toJpg = (el, options) => snapdom(el, __spreadProps(__spreadValues({}, options), { format: "jpeg" })).then((result) => result.toJpg());
2540
+ snapdom.toWebp = (el, options) => snapdom(el, __spreadProps(__spreadValues({}, options), { format: "webp" })).then((result) => result.toWebp());
2541
+ snapdom.download = (el, options) => snapdom(el, options).then((result) => result.download());
1837
2542
 
1838
2543
  // src/api/preCache.js
1839
2544
  function preCache() {
1840
2545
  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
- }
2546
+ var _a;
2547
+ const {
2548
+ embedFonts = true,
2549
+ cacheOpt = "full",
2550
+ useProxy = ""
2551
+ } = options;
2552
+ applyCachePolicy(cacheOpt);
1852
2553
  try {
1853
2554
  yield document.fonts.ready;
1854
2555
  } catch (e) {
1855
2556
  }
1856
2557
  precacheCommonTags();
2558
+ cache.session = cache.session || {};
2559
+ if (!cache.session.styleCache) {
2560
+ cache.session.styleCache = /* @__PURE__ */ new WeakMap();
2561
+ }
2562
+ try {
2563
+ yield inlineBackgroundImages(
2564
+ root,
2565
+ /* mirror */
2566
+ void 0,
2567
+ cache.session.styleCache,
2568
+ { useProxy }
2569
+ );
2570
+ } catch (e) {
2571
+ }
1857
2572
  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("*"));
2573
+ try {
2574
+ if (root == null ? void 0 : root.querySelectorAll) {
2575
+ imgEls = Array.from(root.querySelectorAll("img[src]"));
2576
+ allEls = Array.from(root.querySelectorAll("*"));
2577
+ }
2578
+ } catch (e) {
1861
2579
  }
1862
2580
  const promises = [];
1863
2581
  for (const img of imgEls) {
1864
2582
  const src = img == null ? void 0 : img.src;
1865
2583
  if (!src) continue;
1866
2584
  if (!cache.image.has(src)) {
1867
- const p = Promise.resolve().then(() => fetchImage(src, { useProxy })).then((dataURL) => {
2585
+ const p = Promise.resolve().then(() => snapFetch(src, { as: "dataURL", useProxy })).then((dataURL) => {
1868
2586
  cache.image.set(src, dataURL);
1869
2587
  }).catch(() => {
1870
2588
  });
@@ -1878,8 +2596,8 @@
1878
2596
  } catch (e) {
1879
2597
  }
1880
2598
  if (bg && bg !== "none") {
1881
- const bgSplits = splitBackgroundImage(bg);
1882
- for (const entry of bgSplits) {
2599
+ const parts = splitBackgroundImage(bg);
2600
+ for (const entry of parts) {
1883
2601
  if (entry.startsWith("url(")) {
1884
2602
  const p = Promise.resolve().then(() => inlineSingleBackgroundEntry(entry, __spreadProps(__spreadValues({}, options), { useProxy }))).catch(() => {
1885
2603
  });
@@ -1890,10 +2608,23 @@
1890
2608
  }
1891
2609
  if (embedFonts) {
1892
2610
  try {
1893
- yield embedCustomFonts({ preCached: true, localFonts: options.localFonts, useProxy: options.useProxy });
2611
+ const required = collectUsedFontVariants(root);
2612
+ const usedCodepoints = collectUsedCodepoints(root);
2613
+ if (typeof isSafari === "function" ? isSafari() : !!isSafari) {
2614
+ const families = new Set(
2615
+ Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
2616
+ );
2617
+ yield ensureFontsReady(families, 2);
2618
+ }
2619
+ yield embedCustomFonts({
2620
+ required,
2621
+ usedCodepoints,
2622
+ exclude: options.excludeFonts,
2623
+ localFonts: options.localFonts,
2624
+ useProxy: (_a = options.useProxy) != null ? _a : useProxy
2625
+ });
1894
2626
  } catch (e) {
1895
2627
  }
1896
- ;
1897
2628
  }
1898
2629
  yield Promise.allSettled(promises);
1899
2630
  });