@zumer/snapdom 1.7.1 → 1.9.3-next-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -35
- package/dist/snapdom.js +1089 -598
- package/dist/snapdom.min.js +13 -13
- package/dist/snapdom.min.mjs +13 -13
- package/dist/snapdom.mjs +559 -171
- package/package.json +3 -2
- package/types/snapdom.d.ts +7 -2
package/dist/snapdom.js
CHANGED
|
@@ -1,11 +1,61 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.1.
|
|
4
|
+
* v.1.9.3-next-dev.0
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
8
|
+
|
|
8
9
|
(() => {
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
12
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
13
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
14
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
15
|
+
var __spreadValues = (a, b) => {
|
|
16
|
+
for (var prop in b || (b = {}))
|
|
17
|
+
if (__hasOwnProp.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
if (__getOwnPropSymbols)
|
|
20
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
21
|
+
if (__propIsEnum.call(b, prop))
|
|
22
|
+
__defNormalProp(a, prop, b[prop]);
|
|
23
|
+
}
|
|
24
|
+
return a;
|
|
25
|
+
};
|
|
26
|
+
var __objRest = (source, exclude) => {
|
|
27
|
+
var target = {};
|
|
28
|
+
for (var prop in source)
|
|
29
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
30
|
+
target[prop] = source[prop];
|
|
31
|
+
if (source != null && __getOwnPropSymbols)
|
|
32
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
33
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
34
|
+
target[prop] = source[prop];
|
|
35
|
+
}
|
|
36
|
+
return target;
|
|
37
|
+
};
|
|
38
|
+
var __async = (__this, __arguments, generator) => {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
var fulfilled = (value) => {
|
|
41
|
+
try {
|
|
42
|
+
step(generator.next(value));
|
|
43
|
+
} catch (e) {
|
|
44
|
+
reject(e);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var rejected = (value) => {
|
|
48
|
+
try {
|
|
49
|
+
step(generator.throw(value));
|
|
50
|
+
} catch (e) {
|
|
51
|
+
reject(e);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
55
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
9
59
|
// src/core/cache.js
|
|
10
60
|
var imageCache = /* @__PURE__ */ new Map();
|
|
11
61
|
var bgCache = /* @__PURE__ */ new Map();
|
|
@@ -13,6 +63,7 @@
|
|
|
13
63
|
var defaultStylesCache = /* @__PURE__ */ new Map();
|
|
14
64
|
var baseCSSCache = /* @__PURE__ */ new Map();
|
|
15
65
|
var computedStyleCache = /* @__PURE__ */ new WeakMap();
|
|
66
|
+
var processedFontURLs = /* @__PURE__ */ new Set();
|
|
16
67
|
|
|
17
68
|
// src/utils/cssTools.js
|
|
18
69
|
var commonTags = [
|
|
@@ -152,6 +203,27 @@
|
|
|
152
203
|
}
|
|
153
204
|
|
|
154
205
|
// src/utils/helpers.js
|
|
206
|
+
function inlineSingleBackgroundEntry(_0) {
|
|
207
|
+
return __async(this, arguments, function* (entry, options = {}) {
|
|
208
|
+
const match = entry.match(/url\(["']?(.*?)["']?\)/);
|
|
209
|
+
const rawUrl = match == null ? void 0 : match[1];
|
|
210
|
+
const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
|
|
211
|
+
if (rawUrl) {
|
|
212
|
+
const encodedUrl = safeEncodeURI(rawUrl);
|
|
213
|
+
if (bgCache.has(encodedUrl)) {
|
|
214
|
+
return options.skipInline ? void 0 : `url(${bgCache.get(encodedUrl)})`;
|
|
215
|
+
} else {
|
|
216
|
+
const dataUrl = yield fetchImage(encodedUrl, { useProxy: options.useProxy });
|
|
217
|
+
bgCache.set(encodedUrl, dataUrl);
|
|
218
|
+
return options.skipInline ? void 0 : `url("${dataUrl}")`;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (isGradient || entry === "none") {
|
|
222
|
+
return entry;
|
|
223
|
+
}
|
|
224
|
+
return entry;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
155
227
|
function idle(fn, { fast = false } = {}) {
|
|
156
228
|
if (fast) return fn();
|
|
157
229
|
if ("requestIdleCallback" in window) {
|
|
@@ -180,7 +252,7 @@
|
|
|
180
252
|
if (clean.startsWith("\\")) {
|
|
181
253
|
try {
|
|
182
254
|
return String.fromCharCode(parseInt(clean.replace("\\", ""), 16));
|
|
183
|
-
} catch {
|
|
255
|
+
} catch (e) {
|
|
184
256
|
return clean;
|
|
185
257
|
}
|
|
186
258
|
}
|
|
@@ -196,53 +268,119 @@
|
|
|
196
268
|
}
|
|
197
269
|
return url;
|
|
198
270
|
}
|
|
199
|
-
function
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
271
|
+
function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
|
|
272
|
+
function getCrossOriginMode(url) {
|
|
273
|
+
try {
|
|
274
|
+
const parsed = new URL(url, window.location.href);
|
|
275
|
+
return parsed.origin === window.location.origin ? "use-credentials" : "anonymous";
|
|
276
|
+
} catch (e) {
|
|
277
|
+
return "anonymous";
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function fetchWithFallback(url) {
|
|
281
|
+
return __async(this, null, function* () {
|
|
282
|
+
const fetchBlobAsDataURL = (fetchUrl) => fetch(fetchUrl, {
|
|
283
|
+
mode: "cors",
|
|
284
|
+
credentials: getCrossOriginMode(fetchUrl) === "use-credentials" ? "include" : "omit"
|
|
285
|
+
}).then((r) => r.blob()).then((blob) => new Promise((resolve, reject) => {
|
|
286
|
+
const reader = new FileReader();
|
|
287
|
+
reader.onloadend = () => {
|
|
288
|
+
const base64 = reader.result;
|
|
289
|
+
if (typeof base64 !== "string" || !base64.startsWith("data:image/")) {
|
|
290
|
+
reject(new Error("Invalid image data URL"));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
resolve(base64);
|
|
294
|
+
};
|
|
295
|
+
reader.onerror = () => reject(new Error("FileReader error"));
|
|
296
|
+
reader.readAsDataURL(blob);
|
|
297
|
+
}));
|
|
298
|
+
try {
|
|
299
|
+
return yield fetchBlobAsDataURL(url);
|
|
300
|
+
} catch (e) {
|
|
301
|
+
if (useProxy && typeof useProxy === "string") {
|
|
302
|
+
const proxied = useProxy.replace(/\/$/, "") + safeEncodeURI(url);
|
|
303
|
+
try {
|
|
304
|
+
return yield fetchBlobAsDataURL(proxied);
|
|
305
|
+
} catch (e2) {
|
|
306
|
+
console.error(`[SnapDOM - fetchImage] Proxy fallback failed for: ${url}`);
|
|
307
|
+
throw new Error("CORS restrictions prevented image capture (even via proxy)");
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
console.error(`[SnapDOM - fetchImage] No valid proxy URL provided for fallback: ${url}`);
|
|
311
|
+
throw new Error("Fetch fallback failed and no proxy provided");
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const crossOriginValue = getCrossOriginMode(src);
|
|
317
|
+
console.log(`[SnapDOM - fetchImage] Start loading image: ${src} with crossOrigin=${crossOriginValue}`);
|
|
213
318
|
if (imageCache.has(src)) {
|
|
319
|
+
console.log(`[SnapDOM - fetchImage] Cache hit for: ${src}`);
|
|
214
320
|
return Promise.resolve(imageCache.get(src));
|
|
215
321
|
}
|
|
322
|
+
const isDataURI = src.startsWith("data:image/");
|
|
323
|
+
if (isDataURI) {
|
|
324
|
+
imageCache.set(src, src);
|
|
325
|
+
return Promise.resolve(src);
|
|
326
|
+
}
|
|
327
|
+
const isSVG = /\.svg(\?.*)?$/i.test(src);
|
|
328
|
+
if (isSVG) {
|
|
329
|
+
return (() => __async(null, null, function* () {
|
|
330
|
+
try {
|
|
331
|
+
const response = yield fetch(src, {
|
|
332
|
+
mode: "cors",
|
|
333
|
+
credentials: crossOriginValue === "use-credentials" ? "include" : "omit"
|
|
334
|
+
});
|
|
335
|
+
const svgText = yield response.text();
|
|
336
|
+
const encoded = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`;
|
|
337
|
+
imageCache.set(src, encoded);
|
|
338
|
+
return encoded;
|
|
339
|
+
} catch (e) {
|
|
340
|
+
return fetchWithFallback(src);
|
|
341
|
+
}
|
|
342
|
+
}))();
|
|
343
|
+
}
|
|
216
344
|
return new Promise((resolve, reject) => {
|
|
217
345
|
const timeoutId = setTimeout(() => {
|
|
346
|
+
console.log(`[SnapDOM - fetchImage] Timeout after ${timeout}ms for image: ${src}`);
|
|
218
347
|
reject(new Error("Image load timed out"));
|
|
219
348
|
}, timeout);
|
|
220
349
|
const image = new Image();
|
|
221
|
-
image.crossOrigin =
|
|
222
|
-
image.onload =
|
|
350
|
+
image.crossOrigin = crossOriginValue;
|
|
351
|
+
image.onload = () => __async(null, null, function* () {
|
|
223
352
|
clearTimeout(timeoutId);
|
|
224
353
|
try {
|
|
225
|
-
|
|
354
|
+
yield image.decode();
|
|
226
355
|
const canvas = document.createElement("canvas");
|
|
227
356
|
canvas.width = image.width;
|
|
228
357
|
canvas.height = image.height;
|
|
229
358
|
const ctx = canvas.getContext("2d");
|
|
230
359
|
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
360
|
+
const dataURL = canvas.toDataURL("image/png");
|
|
361
|
+
imageCache.set(src, dataURL);
|
|
362
|
+
resolve(dataURL);
|
|
363
|
+
} catch (e) {
|
|
231
364
|
try {
|
|
232
|
-
const
|
|
233
|
-
imageCache.set(src,
|
|
234
|
-
resolve(
|
|
235
|
-
} catch (
|
|
236
|
-
reject(
|
|
365
|
+
const fallbackDataURL = yield fetchWithFallback(src);
|
|
366
|
+
imageCache.set(src, fallbackDataURL);
|
|
367
|
+
resolve(fallbackDataURL);
|
|
368
|
+
} catch (e2) {
|
|
369
|
+
reject(e2);
|
|
237
370
|
}
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
image.onerror = () => __async(null, null, function* () {
|
|
374
|
+
clearTimeout(timeoutId);
|
|
375
|
+
console.error(`[SnapDOM - fetchImage] Image failed to load: ${src}`);
|
|
376
|
+
try {
|
|
377
|
+
const fallbackDataURL = yield fetchWithFallback(src);
|
|
378
|
+
imageCache.set(src, fallbackDataURL);
|
|
379
|
+
resolve(fallbackDataURL);
|
|
238
380
|
} catch (e) {
|
|
239
381
|
reject(e);
|
|
240
382
|
}
|
|
241
|
-
};
|
|
242
|
-
image.onerror = (e) => {
|
|
243
|
-
clearTimeout(timeoutId);
|
|
244
|
-
reject(new Error("Failed to load image: " + (e.message || "Unknown error")));
|
|
245
|
-
};
|
|
383
|
+
});
|
|
246
384
|
image.src = src;
|
|
247
385
|
});
|
|
248
386
|
}
|
|
@@ -256,35 +394,137 @@
|
|
|
256
394
|
function isSafari() {
|
|
257
395
|
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
258
396
|
}
|
|
397
|
+
function stripTranslate(transform) {
|
|
398
|
+
if (!transform || transform === "none") return "";
|
|
399
|
+
let cleaned = transform.replace(/translate[XY]?\([^)]*\)/g, "");
|
|
400
|
+
cleaned = cleaned.replace(/matrix\(([^)]+)\)/g, (_, values) => {
|
|
401
|
+
const parts = values.split(",").map((s) => s.trim());
|
|
402
|
+
if (parts.length !== 6) return `matrix(${values})`;
|
|
403
|
+
parts[4] = "0";
|
|
404
|
+
parts[5] = "0";
|
|
405
|
+
return `matrix(${parts.join(", ")})`;
|
|
406
|
+
});
|
|
407
|
+
cleaned = cleaned.replace(/matrix3d\(([^)]+)\)/g, (_, values) => {
|
|
408
|
+
const parts = values.split(",").map((s) => s.trim());
|
|
409
|
+
if (parts.length !== 16) return `matrix3d(${values})`;
|
|
410
|
+
parts[12] = "0";
|
|
411
|
+
parts[13] = "0";
|
|
412
|
+
return `matrix3d(${parts.join(", ")})`;
|
|
413
|
+
});
|
|
414
|
+
return cleaned.trim().replace(/\s{2,}/g, " ");
|
|
415
|
+
}
|
|
416
|
+
function safeEncodeURI(uri) {
|
|
417
|
+
if (/%[0-9A-Fa-f]{2}/.test(uri)) return uri;
|
|
418
|
+
try {
|
|
419
|
+
return encodeURI(uri);
|
|
420
|
+
} catch (e) {
|
|
421
|
+
return uri;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function splitBackgroundImage(bg) {
|
|
425
|
+
const parts = [];
|
|
426
|
+
let depth = 0;
|
|
427
|
+
let lastIndex = 0;
|
|
428
|
+
for (let i = 0; i < bg.length; i++) {
|
|
429
|
+
const char = bg[i];
|
|
430
|
+
if (char === "(") depth++;
|
|
431
|
+
if (char === ")") depth--;
|
|
432
|
+
if (char === "," && depth === 0) {
|
|
433
|
+
parts.push(bg.slice(lastIndex, i).trim());
|
|
434
|
+
lastIndex = i + 1;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
parts.push(bg.slice(lastIndex).trim());
|
|
438
|
+
return parts;
|
|
439
|
+
}
|
|
259
440
|
|
|
260
441
|
// src/modules/styles.js
|
|
442
|
+
var snapshotCache = /* @__PURE__ */ new WeakMap();
|
|
443
|
+
var snapshotKeyCache = /* @__PURE__ */ new Map();
|
|
444
|
+
function snapshotComputedStyleFull(style) {
|
|
445
|
+
const result = {};
|
|
446
|
+
for (let i = 0; i < style.length; i++) {
|
|
447
|
+
const prop = style[i];
|
|
448
|
+
let val = style.getPropertyValue(prop);
|
|
449
|
+
if ((prop === "background-image" || prop === "content") && val.includes("url(") && !val.includes("data:")) {
|
|
450
|
+
val = "none";
|
|
451
|
+
}
|
|
452
|
+
result[prop] = val;
|
|
453
|
+
}
|
|
454
|
+
return result;
|
|
455
|
+
}
|
|
261
456
|
function inlineAllStyles(source, clone, styleMap, cache, compress) {
|
|
457
|
+
var _a;
|
|
262
458
|
if (source.tagName === "STYLE") return;
|
|
263
459
|
if (!cache.has(source)) {
|
|
264
460
|
cache.set(source, getStyle(source));
|
|
265
461
|
}
|
|
266
462
|
const style = cache.get(source);
|
|
267
|
-
|
|
268
|
-
|
|
463
|
+
if (!snapshotCache.has(source)) {
|
|
464
|
+
const snapshot2 = snapshotComputedStyleFull(style);
|
|
465
|
+
snapshotCache.set(source, snapshot2);
|
|
466
|
+
}
|
|
467
|
+
const snapshot = snapshotCache.get(source);
|
|
468
|
+
const hash = Object.entries(snapshot).sort(([a], [b]) => a.localeCompare(b)).map(([prop, val]) => `${prop}:${val}`).join(";");
|
|
469
|
+
if (snapshotKeyCache.has(hash)) {
|
|
470
|
+
styleMap.set(clone, snapshotKeyCache.get(hash));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const tagName = ((_a = source.tagName) == null ? void 0 : _a.toLowerCase()) || "div";
|
|
269
474
|
const key = getStyleKey(snapshot, tagName, compress);
|
|
475
|
+
snapshotKeyCache.set(hash, key);
|
|
270
476
|
styleMap.set(clone, key);
|
|
271
477
|
}
|
|
272
478
|
|
|
273
479
|
// src/core/clone.js
|
|
274
|
-
function
|
|
275
|
-
|
|
480
|
+
function isSlotElement(node) {
|
|
481
|
+
return node.nodeType === Node.ELEMENT_NODE && node.tagName === "SLOT";
|
|
482
|
+
}
|
|
483
|
+
function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {}, originalRoot) {
|
|
484
|
+
var _a, _b;
|
|
485
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
486
|
+
return node.cloneNode(true);
|
|
487
|
+
}
|
|
488
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return node.cloneNode(true);
|
|
489
|
+
if (node.getAttribute("data-capture") === "exclude") {
|
|
276
490
|
const spacer = document.createElement("div");
|
|
277
491
|
const rect = node.getBoundingClientRect();
|
|
278
492
|
spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
|
|
279
493
|
return spacer;
|
|
280
494
|
}
|
|
495
|
+
if (options.exclude && Array.isArray(options.exclude)) {
|
|
496
|
+
for (const selector of options.exclude) {
|
|
497
|
+
try {
|
|
498
|
+
if ((_a = node.matches) == null ? void 0 : _a.call(node, selector)) {
|
|
499
|
+
const spacer = document.createElement("div");
|
|
500
|
+
const rect = node.getBoundingClientRect();
|
|
501
|
+
spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
|
|
502
|
+
return spacer;
|
|
503
|
+
}
|
|
504
|
+
} catch (err) {
|
|
505
|
+
console.warn(`Invalid selector in exclude option: ${selector}`, err);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (typeof options.filter === "function") {
|
|
510
|
+
try {
|
|
511
|
+
if (!options.filter(node, originalRoot || node)) {
|
|
512
|
+
const spacer = document.createElement("div");
|
|
513
|
+
const rect = node.getBoundingClientRect();
|
|
514
|
+
spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
|
|
515
|
+
return spacer;
|
|
516
|
+
}
|
|
517
|
+
} catch (err) {
|
|
518
|
+
console.warn("Error in filter function:", err);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
281
521
|
if (node.tagName === "IFRAME") {
|
|
282
522
|
const fallback = document.createElement("div");
|
|
283
523
|
fallback.textContent = "";
|
|
284
|
-
fallback.style.cssText = `width: ${node.offsetWidth}px; height: ${node.offsetHeight}px; background: 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;`;
|
|
524
|
+
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;`;
|
|
285
525
|
return fallback;
|
|
286
526
|
}
|
|
287
|
-
if (node.
|
|
527
|
+
if (node.getAttribute("data-capture") === "placeholder") {
|
|
288
528
|
const clone2 = node.cloneNode(false);
|
|
289
529
|
nodeMap.set(clone2, node);
|
|
290
530
|
inlineAllStyles(node, clone2, styleMap, styleCache, compress);
|
|
@@ -301,18 +541,10 @@
|
|
|
301
541
|
img.width = node.width;
|
|
302
542
|
img.height = node.height;
|
|
303
543
|
img.style.display = "inline-block";
|
|
304
|
-
img.style.width = `${node.width}px`;
|
|
305
|
-
img.style.height = `${node.height}px`;
|
|
544
|
+
img.style.width = node.style.width || `${node.width}px`;
|
|
545
|
+
img.style.height = node.style.height || `${node.height}px`;
|
|
306
546
|
return img;
|
|
307
547
|
}
|
|
308
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
309
|
-
if (node.parentElement?.shadowRoot) {
|
|
310
|
-
const tag = node.parentElement.tagName.toLowerCase();
|
|
311
|
-
if (customElements.get(tag)) return null;
|
|
312
|
-
}
|
|
313
|
-
return node.cloneNode(true);
|
|
314
|
-
}
|
|
315
|
-
if (node.nodeType !== Node.ELEMENT_NODE) return node.cloneNode(true);
|
|
316
548
|
const clone = node.cloneNode(false);
|
|
317
549
|
nodeMap.set(clone, node);
|
|
318
550
|
if (node instanceof HTMLInputElement) {
|
|
@@ -323,8 +555,10 @@
|
|
|
323
555
|
if (node.checked) clone.setAttribute("checked", "");
|
|
324
556
|
}
|
|
325
557
|
} else if (node instanceof HTMLTextAreaElement) {
|
|
326
|
-
|
|
558
|
+
const rect = node.getBoundingClientRect();
|
|
327
559
|
clone.textContent = node.value;
|
|
560
|
+
clone.style.width = `${rect.width}px`;
|
|
561
|
+
clone.style.height = `${rect.height}px`;
|
|
328
562
|
} else if (node instanceof HTMLSelectElement) {
|
|
329
563
|
clone.value = node.value;
|
|
330
564
|
Array.from(clone.options).forEach((opt) => {
|
|
@@ -336,568 +570,804 @@
|
|
|
336
570
|
});
|
|
337
571
|
}
|
|
338
572
|
inlineAllStyles(node, clone, styleMap, styleCache, compress);
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
573
|
+
if (isSlotElement(node)) {
|
|
574
|
+
const assigned = ((_b = node.assignedNodes) == null ? void 0 : _b.call(node, { flatten: true })) || [];
|
|
575
|
+
const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
|
|
576
|
+
const fragment = document.createDocumentFragment();
|
|
577
|
+
for (const child of nodesToClone) {
|
|
578
|
+
const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
|
|
579
|
+
if (clonedChild) fragment.appendChild(clonedChild);
|
|
580
|
+
}
|
|
581
|
+
return fragment;
|
|
582
|
+
} else if (node instanceof HTMLTextAreaElement) {
|
|
583
|
+
} else {
|
|
584
|
+
const baseChildren = node.shadowRoot ? node.shadowRoot.childNodes : node.childNodes;
|
|
585
|
+
for (const child of baseChildren) {
|
|
586
|
+
const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
|
|
587
|
+
if (clonedChild) clone.appendChild(clonedChild);
|
|
588
|
+
}
|
|
589
|
+
if (node.shadowRoot && node.childNodes.length > 0 && !node.shadowRoot.querySelector("slot")) {
|
|
590
|
+
const lightDomContent = document.createDocumentFragment();
|
|
591
|
+
for (const child of node.childNodes) {
|
|
592
|
+
const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
|
|
593
|
+
if (clonedChild) lightDomContent.appendChild(clonedChild);
|
|
594
|
+
}
|
|
595
|
+
clone.appendChild(lightDomContent);
|
|
596
|
+
}
|
|
350
597
|
}
|
|
351
598
|
return clone;
|
|
352
599
|
}
|
|
353
600
|
|
|
601
|
+
// src/modules/iconFonts.js
|
|
602
|
+
var defaultIconFonts = [
|
|
603
|
+
// /uicons/i,
|
|
604
|
+
/font\s*awesome/i,
|
|
605
|
+
/material\s*icons/i,
|
|
606
|
+
/ionicons/i,
|
|
607
|
+
/glyphicons/i,
|
|
608
|
+
/feather/i,
|
|
609
|
+
/bootstrap\s*icons/i,
|
|
610
|
+
/remix\s*icons/i,
|
|
611
|
+
/heroicons/i,
|
|
612
|
+
/layui/i,
|
|
613
|
+
/lucide/i
|
|
614
|
+
];
|
|
615
|
+
var userIconFonts = [];
|
|
616
|
+
function extendIconFonts(fonts) {
|
|
617
|
+
const list = Array.isArray(fonts) ? fonts : [fonts];
|
|
618
|
+
for (const f of list) {
|
|
619
|
+
if (f instanceof RegExp) {
|
|
620
|
+
userIconFonts.push(f);
|
|
621
|
+
} else if (typeof f === "string") {
|
|
622
|
+
userIconFonts.push(new RegExp(f, "i"));
|
|
623
|
+
} else {
|
|
624
|
+
console.warn("[snapdom] Ignored invalid iconFont value:", f);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function isIconFont(input) {
|
|
629
|
+
const text = typeof input === "string" ? input : "";
|
|
630
|
+
const candidates = [...defaultIconFonts, ...userIconFonts];
|
|
631
|
+
for (const rx of candidates) {
|
|
632
|
+
if (rx instanceof RegExp && rx.test(text)) return true;
|
|
633
|
+
}
|
|
634
|
+
if (/icon/i.test(text) || /glyph/i.test(text) || /symbols/i.test(text) || /feather/i.test(text) || /fontawesome/i.test(text)) return true;
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
|
|
354
638
|
// src/modules/fonts.js
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
resourceCache.set(url, b64);
|
|
417
|
-
return { original: match[0], inlined: `url(${b64})` };
|
|
418
|
-
} catch (err) {
|
|
419
|
-
console.warn("[snapdom] Failed to fetch font:", url);
|
|
420
|
-
return null;
|
|
421
|
-
}
|
|
422
|
-
})
|
|
423
|
-
);
|
|
424
|
-
let cssFinal = cssText;
|
|
425
|
-
for (const r of inlinedCSS) {
|
|
426
|
-
if (r) {
|
|
427
|
-
cssFinal = cssFinal.replace(r.original, r.inlined);
|
|
639
|
+
function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
|
|
640
|
+
return __async(this, null, function* () {
|
|
641
|
+
fontFamily = fontFamily.replace(/^['"]+|['"]+$/g, "");
|
|
642
|
+
const dpr = window.devicePixelRatio || 1;
|
|
643
|
+
const tempCanvas = document.createElement("canvas");
|
|
644
|
+
const tempCtx = tempCanvas.getContext("2d");
|
|
645
|
+
tempCtx.font = fontWeight ? `${fontWeight} ${fontSize}px "${fontFamily}"` : `${fontSize}px "${fontFamily}"`;
|
|
646
|
+
const metrics = tempCtx.measureText(unicodeChar);
|
|
647
|
+
const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.8;
|
|
648
|
+
const descent = metrics.actualBoundingBoxDescent || fontSize * 0.2;
|
|
649
|
+
const height = ascent + descent;
|
|
650
|
+
const width = metrics.width;
|
|
651
|
+
const canvas = document.createElement("canvas");
|
|
652
|
+
canvas.width = Math.ceil(width * dpr);
|
|
653
|
+
canvas.height = Math.ceil(height * dpr);
|
|
654
|
+
const ctx = canvas.getContext("2d");
|
|
655
|
+
ctx.scale(dpr, dpr);
|
|
656
|
+
ctx.font = tempCtx.font;
|
|
657
|
+
ctx.textAlign = "left";
|
|
658
|
+
ctx.textBaseline = "alphabetic";
|
|
659
|
+
ctx.fillStyle = color;
|
|
660
|
+
ctx.fillText(unicodeChar, 0, ascent);
|
|
661
|
+
return canvas.toDataURL();
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
function isStylesheetLoaded(href) {
|
|
665
|
+
return Array.from(document.styleSheets).some((sheet) => sheet.href === href);
|
|
666
|
+
}
|
|
667
|
+
function injectLinkIfMissing(href) {
|
|
668
|
+
return new Promise((resolve) => {
|
|
669
|
+
if (isStylesheetLoaded(href)) return resolve(null);
|
|
670
|
+
const link = document.createElement("link");
|
|
671
|
+
link.rel = "stylesheet";
|
|
672
|
+
link.href = href;
|
|
673
|
+
link.setAttribute("data-snapdom", "injected-import");
|
|
674
|
+
link.onload = () => resolve(link);
|
|
675
|
+
link.onerror = () => resolve(null);
|
|
676
|
+
document.head.appendChild(link);
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
function embedCustomFonts() {
|
|
680
|
+
return __async(this, arguments, function* ({ preCached = false } = {}) {
|
|
681
|
+
if (resourceCache.has("fonts-embed-css")) {
|
|
682
|
+
if (preCached) {
|
|
683
|
+
const style = document.createElement("style");
|
|
684
|
+
style.setAttribute("data-snapdom", "embedFonts");
|
|
685
|
+
style.textContent = resourceCache.get("fonts-embed-css");
|
|
686
|
+
document.head.appendChild(style);
|
|
687
|
+
}
|
|
688
|
+
return resourceCache.get("fonts-embed-css");
|
|
689
|
+
}
|
|
690
|
+
const importRegex = /@import\s+url\(["']?([^"')]+)["']?\)/g;
|
|
691
|
+
const styleImports = [];
|
|
692
|
+
for (const styleTag of document.querySelectorAll("style")) {
|
|
693
|
+
const cssText = styleTag.textContent || "";
|
|
694
|
+
const matches = Array.from(cssText.matchAll(importRegex));
|
|
695
|
+
for (const match of matches) {
|
|
696
|
+
const importUrl = match[1];
|
|
697
|
+
if (isIconFont(importUrl)) continue;
|
|
698
|
+
if (!isStylesheetLoaded(importUrl)) {
|
|
699
|
+
styleImports.push(importUrl);
|
|
428
700
|
}
|
|
429
701
|
}
|
|
430
|
-
finalCSS += cssFinal + "\n";
|
|
431
|
-
} catch (e) {
|
|
432
|
-
console.warn("[snapdom] Failed to fetch CSS:", link.href);
|
|
433
702
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}
|
|
703
|
+
yield Promise.all(styleImports.map(injectLinkIfMissing));
|
|
704
|
+
const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter((link) => link.href);
|
|
705
|
+
let finalCSS = "";
|
|
706
|
+
for (const link of links) {
|
|
707
|
+
try {
|
|
708
|
+
const res = yield fetch(link.href);
|
|
709
|
+
const cssText = yield res.text();
|
|
710
|
+
if (isIconFont(link.href) || isIconFont(cssText)) continue;
|
|
711
|
+
const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
|
|
712
|
+
const inlinedCSS = yield Promise.all(
|
|
713
|
+
Array.from(cssText.matchAll(urlRegex)).map((match) => __async(null, null, function* () {
|
|
714
|
+
let rawUrl = extractURL(match[0]);
|
|
715
|
+
if (!rawUrl) return null;
|
|
716
|
+
let url = rawUrl;
|
|
717
|
+
if (!url.startsWith("http") && !url.startsWith("data:")) {
|
|
718
|
+
url = new URL(url, link.href).href;
|
|
719
|
+
}
|
|
720
|
+
if (isIconFont(url)) return null;
|
|
721
|
+
if (resourceCache.has(url)) {
|
|
722
|
+
processedFontURLs.add(url);
|
|
723
|
+
return { original: match[0], inlined: `url(${resourceCache.get(url)})` };
|
|
724
|
+
}
|
|
725
|
+
if (processedFontURLs.has(url)) return null;
|
|
726
|
+
try {
|
|
727
|
+
const fontRes = yield fetch(url);
|
|
728
|
+
const blob = yield fontRes.blob();
|
|
729
|
+
const b64 = yield new Promise((resolve) => {
|
|
730
|
+
const reader = new FileReader();
|
|
731
|
+
reader.onload = () => resolve(reader.result);
|
|
732
|
+
reader.readAsDataURL(blob);
|
|
733
|
+
});
|
|
734
|
+
resourceCache.set(url, b64);
|
|
735
|
+
processedFontURLs.add(url);
|
|
736
|
+
return { original: match[0], inlined: `url(${b64})` };
|
|
737
|
+
} catch (e) {
|
|
738
|
+
console.warn("[snapdom] Failed to fetch font resource:", url);
|
|
739
|
+
return null;
|
|
472
740
|
}
|
|
473
|
-
|
|
474
|
-
|
|
741
|
+
}))
|
|
742
|
+
);
|
|
743
|
+
let cssFinal = cssText;
|
|
744
|
+
for (const r of inlinedCSS) {
|
|
745
|
+
if (r) cssFinal = cssFinal.replace(r.original, r.inlined);
|
|
746
|
+
}
|
|
747
|
+
finalCSS += cssFinal + "\n";
|
|
748
|
+
} catch (e) {
|
|
749
|
+
console.warn("[snapdom] Failed to fetch CSS:", link.href);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
for (const sheet of document.styleSheets) {
|
|
753
|
+
try {
|
|
754
|
+
if (!sheet.href || links.every((link) => link.href !== sheet.href)) {
|
|
755
|
+
for (const rule of sheet.cssRules) {
|
|
756
|
+
if (rule.type === CSSRule.FONT_FACE_RULE) {
|
|
757
|
+
const src = rule.style.getPropertyValue("src");
|
|
758
|
+
const family = rule.style.getPropertyValue("font-family");
|
|
759
|
+
if (!src || isIconFont(family)) continue;
|
|
760
|
+
const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
|
|
761
|
+
let inlinedSrc = src;
|
|
762
|
+
const matches = Array.from(src.matchAll(urlRegex));
|
|
763
|
+
for (const match of matches) {
|
|
764
|
+
let rawUrl = match[2].trim();
|
|
765
|
+
if (!rawUrl) continue;
|
|
766
|
+
let url = rawUrl;
|
|
767
|
+
if (!url.startsWith("http") && !url.startsWith("data:")) {
|
|
768
|
+
url = new URL(url, sheet.href || location.href).href;
|
|
769
|
+
}
|
|
770
|
+
if (isIconFont(url)) continue;
|
|
771
|
+
if (resourceCache.has(url)) {
|
|
772
|
+
processedFontURLs.add(url);
|
|
773
|
+
inlinedSrc = inlinedSrc.replace(match[0], `url(${resourceCache.get(url)})`);
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (processedFontURLs.has(url)) continue;
|
|
777
|
+
try {
|
|
778
|
+
const res = yield fetch(url);
|
|
779
|
+
const blob = yield res.blob();
|
|
780
|
+
const b64 = yield new Promise((resolve) => {
|
|
781
|
+
const reader = new FileReader();
|
|
782
|
+
reader.onload = () => resolve(reader.result);
|
|
783
|
+
reader.readAsDataURL(blob);
|
|
784
|
+
});
|
|
785
|
+
resourceCache.set(url, b64);
|
|
786
|
+
processedFontURLs.add(url);
|
|
787
|
+
inlinedSrc = inlinedSrc.replace(match[0], `url(${b64})`);
|
|
788
|
+
} catch (e) {
|
|
789
|
+
console.warn("[snapdom] Failed to fetch font URL:", url);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
finalCSS += `@font-face {
|
|
793
|
+
font-family: ${family};
|
|
475
794
|
src: ${inlinedSrc};
|
|
476
795
|
font-style: ${rule.style.getPropertyValue("font-style") || "normal"};
|
|
477
796
|
font-weight: ${rule.style.getPropertyValue("font-weight") || "normal"};
|
|
478
797
|
}
|
|
479
798
|
`;
|
|
799
|
+
}
|
|
480
800
|
}
|
|
481
801
|
}
|
|
802
|
+
} catch (e) {
|
|
803
|
+
console.warn("[snapdom] Cannot access stylesheet", sheet.href, e);
|
|
482
804
|
}
|
|
483
|
-
} catch (e) {
|
|
484
|
-
console.warn("[snapdom] Cannot access stylesheet", sheet.href, e);
|
|
485
805
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
806
|
+
for (const font of document.fonts) {
|
|
807
|
+
if (font.family && font.status === "loaded" && font._snapdomSrc) {
|
|
808
|
+
if (isIconFont(font.family)) continue;
|
|
809
|
+
let b64 = font._snapdomSrc;
|
|
810
|
+
if (!b64.startsWith("data:")) {
|
|
811
|
+
if (resourceCache.has(font._snapdomSrc)) {
|
|
812
|
+
b64 = resourceCache.get(font._snapdomSrc);
|
|
813
|
+
processedFontURLs.add(font._snapdomSrc);
|
|
814
|
+
} else if (!processedFontURLs.has(font._snapdomSrc)) {
|
|
815
|
+
try {
|
|
816
|
+
const res = yield fetch(font._snapdomSrc);
|
|
817
|
+
const blob = yield res.blob();
|
|
818
|
+
b64 = yield new Promise((resolve) => {
|
|
819
|
+
const reader = new FileReader();
|
|
820
|
+
reader.onload = () => resolve(reader.result);
|
|
821
|
+
reader.readAsDataURL(blob);
|
|
822
|
+
});
|
|
823
|
+
resourceCache.set(font._snapdomSrc, b64);
|
|
824
|
+
processedFontURLs.add(font._snapdomSrc);
|
|
825
|
+
} catch (e) {
|
|
826
|
+
console.warn("[snapdom] Failed to fetch dynamic font src:", font._snapdomSrc);
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
506
829
|
}
|
|
507
830
|
}
|
|
508
|
-
|
|
509
|
-
finalCSS += `@font-face {
|
|
831
|
+
finalCSS += `@font-face {
|
|
510
832
|
font-family: '${font.family}';
|
|
511
833
|
src: url(${b64});
|
|
512
834
|
font-style: ${font.style || "normal"};
|
|
513
835
|
font-weight: ${font.weight || "normal"};
|
|
514
836
|
}
|
|
515
837
|
`;
|
|
838
|
+
}
|
|
516
839
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
840
|
+
if (finalCSS) {
|
|
841
|
+
resourceCache.set("fonts-embed-css", finalCSS);
|
|
842
|
+
if (preCached) {
|
|
843
|
+
const style = document.createElement("style");
|
|
844
|
+
style.setAttribute("data-snapdom", "embedFonts");
|
|
845
|
+
style.textContent = finalCSS;
|
|
846
|
+
document.head.appendChild(style);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return finalCSS;
|
|
850
|
+
});
|
|
525
851
|
}
|
|
526
852
|
|
|
527
853
|
// src/modules/pseudo.js
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const fontWeight = parseInt(style.getPropertyValue("font-weight")) || false;
|
|
542
|
-
const color = style.getPropertyValue("color") || "#000";
|
|
543
|
-
const pseudoEl = document.createElement("span");
|
|
544
|
-
pseudoEl.dataset.snapdomPseudo = pseudo;
|
|
545
|
-
const snapshot = snapshotComputedStyle(style);
|
|
546
|
-
const key = getStyleKey(snapshot, "span", compress);
|
|
547
|
-
styleMap.set(pseudoEl, key);
|
|
548
|
-
const isIconFont2 = fontFamily && /font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(
|
|
549
|
-
fontFamily
|
|
550
|
-
);
|
|
551
|
-
let cleanContent = parseContent(content);
|
|
552
|
-
if (!embedFonts && isIconFont2 && cleanContent.length === 1) {
|
|
553
|
-
const imgEl = document.createElement("img");
|
|
554
|
-
imgEl.src = await iconToImage(
|
|
555
|
-
cleanContent,
|
|
556
|
-
fontFamily,
|
|
557
|
-
fontWeight,
|
|
558
|
-
fontSize,
|
|
559
|
-
color
|
|
854
|
+
function inlinePseudoElements(source, clone, styleMap, styleCache, compress, embedFonts = false, useProxy) {
|
|
855
|
+
return __async(this, null, function* () {
|
|
856
|
+
if (!(source instanceof Element) || !(clone instanceof Element)) return;
|
|
857
|
+
for (const pseudo of ["::before", "::after", "::first-letter"]) {
|
|
858
|
+
try {
|
|
859
|
+
const style = getStyle(source, pseudo);
|
|
860
|
+
if (!style || typeof style[Symbol.iterator] !== "function") continue;
|
|
861
|
+
if (pseudo === "::first-letter") {
|
|
862
|
+
const normal = getComputedStyle(source);
|
|
863
|
+
const isMeaningful = style.color !== normal.color || style.fontSize !== normal.fontSize || style.fontWeight !== normal.fontWeight;
|
|
864
|
+
if (!isMeaningful) continue;
|
|
865
|
+
const textNode = Array.from(clone.childNodes).find(
|
|
866
|
+
(n) => n.nodeType === Node.TEXT_NODE && n.textContent && n.textContent.trim().length > 0
|
|
560
867
|
);
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
868
|
+
if (!textNode) continue;
|
|
869
|
+
const text = textNode.textContent;
|
|
870
|
+
const match = text.match(/^([^\p{L}\p{N}\s]*[\p{L}\p{N}](?:['’])?)/u);
|
|
871
|
+
const first = match == null ? void 0 : match[0];
|
|
872
|
+
const rest = text.slice((first == null ? void 0 : first.length) || 0);
|
|
873
|
+
if (!first || /[\uD800-\uDFFF]/.test(first)) continue;
|
|
874
|
+
const span = document.createElement("span");
|
|
875
|
+
span.textContent = first;
|
|
876
|
+
span.dataset.snapdomPseudo = "::first-letter";
|
|
877
|
+
const snapshot = snapshotComputedStyle(style);
|
|
878
|
+
const key = getStyleKey(snapshot, "span", compress);
|
|
879
|
+
styleMap.set(span, key);
|
|
880
|
+
const restNode = document.createTextNode(rest);
|
|
881
|
+
clone.replaceChild(restNode, textNode);
|
|
882
|
+
clone.insertBefore(span, restNode);
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
const content = style.getPropertyValue("content");
|
|
886
|
+
const bg = style.getPropertyValue("background-image");
|
|
887
|
+
const bgColor = style.getPropertyValue("background-color");
|
|
888
|
+
const hasContent = content !== "none";
|
|
889
|
+
const hasBg = bg && bg !== "none";
|
|
890
|
+
const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
|
|
891
|
+
if (hasContent || hasBg || hasBgColor) {
|
|
892
|
+
const fontFamily = style.getPropertyValue("font-family");
|
|
893
|
+
const fontSize = parseInt(style.getPropertyValue("font-size")) || 32;
|
|
894
|
+
const fontWeight = parseInt(style.getPropertyValue("font-weight")) || false;
|
|
895
|
+
const color = style.getPropertyValue("color") || "#000";
|
|
896
|
+
const pseudoEl = document.createElement("span");
|
|
897
|
+
pseudoEl.dataset.snapdomPseudo = pseudo;
|
|
898
|
+
const snapshot = snapshotComputedStyle(style);
|
|
899
|
+
const key = getStyleKey(snapshot, "span", compress);
|
|
900
|
+
styleMap.set(pseudoEl, key);
|
|
901
|
+
const isIconFont2 = isIconFont(fontFamily);
|
|
902
|
+
const cleanContent = parseContent(content);
|
|
903
|
+
if (isIconFont2 && cleanContent.length === 1) {
|
|
904
|
+
const imgEl = document.createElement("img");
|
|
905
|
+
imgEl.src = yield iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
|
|
906
|
+
imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
|
|
907
|
+
pseudoEl.appendChild(imgEl);
|
|
908
|
+
} else if (cleanContent.startsWith("url(")) {
|
|
909
|
+
const rawUrl = extractURL(cleanContent);
|
|
910
|
+
if (rawUrl && rawUrl.trim() !== "") {
|
|
911
|
+
try {
|
|
912
|
+
const imgEl = document.createElement("img");
|
|
913
|
+
const dataUrl = yield fetchImage(safeEncodeURI(rawUrl, { useProxy }));
|
|
914
|
+
imgEl.src = dataUrl;
|
|
915
|
+
imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
|
|
916
|
+
pseudoEl.appendChild(imgEl);
|
|
917
|
+
} catch (e) {
|
|
918
|
+
console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
|
|
919
|
+
}
|
|
574
920
|
}
|
|
921
|
+
} else if (!isIconFont2 && cleanContent && cleanContent !== "none") {
|
|
922
|
+
pseudoEl.textContent = cleanContent;
|
|
575
923
|
}
|
|
576
|
-
|
|
577
|
-
pseudoEl.textContent = cleanContent;
|
|
578
|
-
}
|
|
579
|
-
if (hasBg && bg) {
|
|
580
|
-
const rawUrl = extractURL(bg);
|
|
581
|
-
if (rawUrl) {
|
|
924
|
+
if (hasBg) {
|
|
582
925
|
try {
|
|
583
|
-
const
|
|
584
|
-
|
|
926
|
+
const bgSplits = splitBackgroundImage(bg);
|
|
927
|
+
const newBgParts = yield Promise.all(
|
|
928
|
+
bgSplits.map((entry) => inlineSingleBackgroundEntry(entry))
|
|
929
|
+
);
|
|
930
|
+
pseudoEl.style.backgroundImage = newBgParts.join(", ");
|
|
585
931
|
} catch (e) {
|
|
586
932
|
console.warn(`[snapdom] Failed to inline background-image for ${pseudo}`, e);
|
|
587
933
|
}
|
|
588
934
|
}
|
|
935
|
+
if (hasBgColor) pseudoEl.style.backgroundColor = bgColor;
|
|
936
|
+
const hasContent2 = pseudoEl.childNodes.length > 0 || pseudoEl.textContent && pseudoEl.textContent.trim() !== "";
|
|
937
|
+
const hasVisibleBox = hasContent2 || hasBg || hasBgColor;
|
|
938
|
+
if (!hasVisibleBox) continue;
|
|
939
|
+
pseudo === "::before" ? clone.insertBefore(pseudoEl, clone.firstChild) : clone.appendChild(pseudoEl);
|
|
589
940
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
941
|
+
} catch (e) {
|
|
942
|
+
console.warn(`[snapdom] Failed to capture ${pseudo} for`, source, e);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
const sChildren = Array.from(source.children);
|
|
946
|
+
const cChildren = Array.from(clone.children).filter((child) => !child.dataset.snapdomPseudo);
|
|
947
|
+
for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
|
|
948
|
+
yield inlinePseudoElements(
|
|
949
|
+
sChildren[i],
|
|
950
|
+
cChildren[i],
|
|
951
|
+
styleMap,
|
|
952
|
+
styleCache,
|
|
953
|
+
compress,
|
|
954
|
+
embedFonts,
|
|
955
|
+
useProxy
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// src/modules/svgDefs.js
|
|
962
|
+
function inlineExternalDef(root) {
|
|
963
|
+
if (!root) return;
|
|
964
|
+
const defsSources = document.querySelectorAll("svg > defs");
|
|
965
|
+
if (!defsSources.length) return;
|
|
966
|
+
root.querySelectorAll("svg").forEach((svg) => {
|
|
967
|
+
const uses = svg.querySelectorAll("use");
|
|
968
|
+
if (!uses.length) return;
|
|
969
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
970
|
+
uses.forEach((use) => {
|
|
971
|
+
const href = use.getAttribute("xlink:href") || use.getAttribute("href");
|
|
972
|
+
if (href && href.startsWith("#")) usedIds.add(href.slice(1));
|
|
973
|
+
});
|
|
974
|
+
if (!usedIds.size) return;
|
|
975
|
+
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
976
|
+
for (const id of usedIds) {
|
|
977
|
+
for (const source of defsSources) {
|
|
978
|
+
const def = source.querySelector(`#${CSS.escape(id)}`);
|
|
979
|
+
if (def) {
|
|
980
|
+
defs.appendChild(def.cloneNode(true));
|
|
981
|
+
break;
|
|
596
982
|
}
|
|
597
983
|
}
|
|
598
|
-
} catch (e) {
|
|
599
|
-
console.warn(`[snapdom] Failed to capture ${pseudo} for`, source, e);
|
|
600
984
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
await inlinePseudoElements(
|
|
606
|
-
sChildren[i],
|
|
607
|
-
cChildren[i],
|
|
608
|
-
styleMap,
|
|
609
|
-
styleCache,
|
|
610
|
-
compress,
|
|
611
|
-
embedFonts
|
|
612
|
-
);
|
|
613
|
-
}
|
|
985
|
+
if (defs.childNodes.length) {
|
|
986
|
+
svg.insertBefore(defs, svg.firstChild);
|
|
987
|
+
}
|
|
988
|
+
});
|
|
614
989
|
}
|
|
615
990
|
|
|
616
991
|
// src/core/prepare.js
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
clone
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
await inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts);
|
|
630
|
-
} catch (e) {
|
|
631
|
-
console.warn("inlinePseudoElements failed:", e);
|
|
632
|
-
}
|
|
633
|
-
let classCSS = "";
|
|
634
|
-
if (compress) {
|
|
635
|
-
const keyToClass = generateCSSClasses(styleMap);
|
|
636
|
-
classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
|
|
637
|
-
for (const [node, key] of styleMap.entries()) {
|
|
638
|
-
if (node.tagName === "STYLE") continue;
|
|
639
|
-
const className = keyToClass.get(key);
|
|
640
|
-
if (className) node.classList.add(className);
|
|
641
|
-
const bgImage = node.style?.backgroundImage;
|
|
642
|
-
node.removeAttribute("style");
|
|
643
|
-
if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
|
|
992
|
+
function prepareClone(_0) {
|
|
993
|
+
return __async(this, arguments, function* (element, compress = false, embedFonts = false, options = {}) {
|
|
994
|
+
var _a;
|
|
995
|
+
const styleMap = /* @__PURE__ */ new Map();
|
|
996
|
+
const styleCache = /* @__PURE__ */ new WeakMap();
|
|
997
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
998
|
+
let clone;
|
|
999
|
+
try {
|
|
1000
|
+
clone = deepClone(element, styleMap, styleCache, nodeMap, compress, options, element);
|
|
1001
|
+
} catch (e) {
|
|
1002
|
+
console.warn("deepClone failed:", e);
|
|
1003
|
+
throw e;
|
|
644
1004
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
1005
|
+
try {
|
|
1006
|
+
yield inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts, options.useProxy);
|
|
1007
|
+
} catch (e) {
|
|
1008
|
+
console.warn("inlinePseudoElements failed:", e);
|
|
649
1009
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
1010
|
+
try {
|
|
1011
|
+
inlineExternalDef(clone);
|
|
1012
|
+
} catch (e) {
|
|
1013
|
+
console.warn("inlineExternalDef failed:", e);
|
|
1014
|
+
}
|
|
1015
|
+
let classCSS = "";
|
|
1016
|
+
if (compress) {
|
|
1017
|
+
const keyToClass = generateCSSClasses(styleMap);
|
|
1018
|
+
classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
|
|
1019
|
+
for (const [node, key] of styleMap.entries()) {
|
|
1020
|
+
if (node.tagName === "STYLE") continue;
|
|
1021
|
+
const className = keyToClass.get(key);
|
|
1022
|
+
if (className) node.classList.add(className);
|
|
1023
|
+
const bgImage = (_a = node.style) == null ? void 0 : _a.backgroundImage;
|
|
1024
|
+
node.removeAttribute("style");
|
|
1025
|
+
if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
|
|
1026
|
+
}
|
|
1027
|
+
} else {
|
|
1028
|
+
for (const [node, key] of styleMap.entries()) {
|
|
1029
|
+
if (node.tagName === "STYLE") continue;
|
|
1030
|
+
node.setAttribute("style", key.replace(/;/g, "; "));
|
|
666
1031
|
}
|
|
667
|
-
cloneNode.appendChild(inner);
|
|
668
1032
|
}
|
|
669
|
-
|
|
670
|
-
|
|
1033
|
+
for (const [cloneNode, originalNode] of nodeMap.entries()) {
|
|
1034
|
+
const scrollX = originalNode.scrollLeft;
|
|
1035
|
+
const scrollY = originalNode.scrollTop;
|
|
1036
|
+
const hasScroll = scrollX || scrollY;
|
|
1037
|
+
if (hasScroll && cloneNode instanceof HTMLElement) {
|
|
1038
|
+
cloneNode.style.overflow = "hidden";
|
|
1039
|
+
cloneNode.style.scrollbarWidth = "none";
|
|
1040
|
+
cloneNode.style.msOverflowStyle = "none";
|
|
1041
|
+
const inner = document.createElement("div");
|
|
1042
|
+
inner.style.transform = `translate(${-scrollX}px, ${-scrollY}px)`;
|
|
1043
|
+
inner.style.willChange = "transform";
|
|
1044
|
+
inner.style.display = "inline-block";
|
|
1045
|
+
inner.style.width = "100%";
|
|
1046
|
+
while (cloneNode.firstChild) {
|
|
1047
|
+
inner.appendChild(cloneNode.firstChild);
|
|
1048
|
+
}
|
|
1049
|
+
cloneNode.appendChild(inner);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
if (element === nodeMap.get(clone)) {
|
|
1053
|
+
const computed = styleCache.get(element) || window.getComputedStyle(element);
|
|
1054
|
+
styleCache.set(element, computed);
|
|
1055
|
+
const transform = stripTranslate(computed.transform);
|
|
1056
|
+
clone.style.margin = "0";
|
|
1057
|
+
clone.style.position = "static";
|
|
1058
|
+
clone.style.top = "auto";
|
|
1059
|
+
clone.style.left = "auto";
|
|
1060
|
+
clone.style.right = "auto";
|
|
1061
|
+
clone.style.bottom = "auto";
|
|
1062
|
+
clone.style.zIndex = "auto";
|
|
1063
|
+
clone.style.float = "none";
|
|
1064
|
+
clone.style.clear = "none";
|
|
1065
|
+
clone.style.transform = transform || "";
|
|
1066
|
+
}
|
|
1067
|
+
for (const [cloneNode, originalNode] of nodeMap.entries()) {
|
|
1068
|
+
if (originalNode.tagName === "PRE") {
|
|
1069
|
+
cloneNode.style.marginTop = "0";
|
|
1070
|
+
cloneNode.style.marginBlockStart = "0";
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return { clone, classCSS, styleCache };
|
|
1074
|
+
});
|
|
671
1075
|
}
|
|
672
1076
|
|
|
673
1077
|
// src/modules/images.js
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
1078
|
+
function inlineImages(_0) {
|
|
1079
|
+
return __async(this, arguments, function* (clone, options = {}) {
|
|
1080
|
+
const imgs = Array.from(clone.querySelectorAll("img"));
|
|
1081
|
+
const processImg = (img) => __async(null, null, function* () {
|
|
1082
|
+
const src = img.src;
|
|
1083
|
+
try {
|
|
1084
|
+
const dataUrl = yield fetchImage(src, { useProxy: options.useProxy });
|
|
1085
|
+
img.src = dataUrl;
|
|
1086
|
+
if (!img.width) img.width = img.naturalWidth || 100;
|
|
1087
|
+
if (!img.height) img.height = img.naturalHeight || 100;
|
|
1088
|
+
} catch (e) {
|
|
1089
|
+
const fallback = document.createElement("div");
|
|
1090
|
+
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;`;
|
|
1091
|
+
fallback.innerText = "img";
|
|
1092
|
+
img.replaceWith(fallback);
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
for (let i = 0; i < imgs.length; i += 4) {
|
|
1096
|
+
const group = imgs.slice(i, i + 4).map(processImg);
|
|
1097
|
+
yield Promise.allSettled(group);
|
|
689
1098
|
}
|
|
690
|
-
};
|
|
691
|
-
for (let i = 0; i < imgs.length; i += 4) {
|
|
692
|
-
const group = imgs.slice(i, i + 4).map(processImg);
|
|
693
|
-
await Promise.allSettled(group);
|
|
694
|
-
}
|
|
1099
|
+
});
|
|
695
1100
|
}
|
|
696
1101
|
|
|
697
1102
|
// src/modules/background.js
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1103
|
+
function inlineBackgroundImages(_0, _1, _2) {
|
|
1104
|
+
return __async(this, arguments, function* (source, clone, styleCache, options = {}) {
|
|
1105
|
+
const queue = [[source, clone]];
|
|
1106
|
+
const imageProps = [
|
|
1107
|
+
"background-image",
|
|
1108
|
+
"mask",
|
|
1109
|
+
"mask-image",
|
|
1110
|
+
"-webkit-mask-image",
|
|
1111
|
+
"mask-source",
|
|
1112
|
+
"mask-box-image-source",
|
|
1113
|
+
"mask-border-source",
|
|
1114
|
+
"-webkit-mask-box-image-source"
|
|
1115
|
+
];
|
|
1116
|
+
while (queue.length) {
|
|
1117
|
+
const [srcNode, cloneNode] = queue.shift();
|
|
1118
|
+
const style = styleCache.get(srcNode) || getStyle(srcNode);
|
|
1119
|
+
if (!styleCache.has(srcNode)) styleCache.set(srcNode, style);
|
|
1120
|
+
for (const prop of imageProps) {
|
|
1121
|
+
const val = style.getPropertyValue(prop);
|
|
1122
|
+
if (!val || val === "none") continue;
|
|
1123
|
+
const splits = splitBackgroundImage(val);
|
|
1124
|
+
const inlined = yield Promise.all(
|
|
1125
|
+
splits.map((entry) => inlineSingleBackgroundEntry(entry, options))
|
|
1126
|
+
);
|
|
1127
|
+
if (inlined.some((p) => p && p !== "none" && !/^url\(undefined/.test(p))) {
|
|
1128
|
+
cloneNode.style.setProperty(prop, inlined.join(", "));
|
|
722
1129
|
}
|
|
723
1130
|
}
|
|
1131
|
+
const bgColor = style.getPropertyValue("background-color");
|
|
1132
|
+
if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") {
|
|
1133
|
+
cloneNode.style.backgroundColor = bgColor;
|
|
1134
|
+
}
|
|
1135
|
+
const sChildren = Array.from(srcNode.children);
|
|
1136
|
+
const cChildren = Array.from(cloneNode.children);
|
|
1137
|
+
for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
|
|
1138
|
+
queue.push([sChildren[i], cChildren[i]]);
|
|
1139
|
+
}
|
|
724
1140
|
}
|
|
725
|
-
|
|
726
|
-
cloneNode.style.backgroundImage = bgSplits.join(",");
|
|
727
|
-
}
|
|
728
|
-
const sChildren = Array.from(srcNode.children);
|
|
729
|
-
const cChildren = Array.from(cloneNode.children);
|
|
730
|
-
for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
|
|
731
|
-
queue.push([sChildren[i], cChildren[i]]);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
1141
|
+
});
|
|
734
1142
|
}
|
|
735
1143
|
|
|
736
1144
|
// src/core/capture.js
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
}, { fast });
|
|
751
|
-
});
|
|
752
|
-
await new Promise((resolve) => {
|
|
753
|
-
idle(async () => {
|
|
754
|
-
await inlineBackgroundImages(element, clone, styleCache, options);
|
|
755
|
-
resolve();
|
|
756
|
-
}, { fast });
|
|
757
|
-
});
|
|
758
|
-
if (embedFonts) {
|
|
759
|
-
await new Promise((resolve) => {
|
|
760
|
-
idle(async () => {
|
|
761
|
-
fontsCSS = await embedCustomFonts({ ignoreIconFonts: !embedFonts });
|
|
1145
|
+
function captureDOM(_0) {
|
|
1146
|
+
return __async(this, arguments, function* (element, options = {}) {
|
|
1147
|
+
if (!element) throw new Error("Element cannot be null or undefined");
|
|
1148
|
+
const { compress = true, embedFonts = false, fast = true, scale = 1, useProxy = "" } = options;
|
|
1149
|
+
let clone, classCSS, styleCache;
|
|
1150
|
+
let fontsCSS = "";
|
|
1151
|
+
let baseCSS = "";
|
|
1152
|
+
let dataURL;
|
|
1153
|
+
let svgString;
|
|
1154
|
+
({ clone, classCSS, styleCache } = yield prepareClone(element, compress, embedFonts, options));
|
|
1155
|
+
yield new Promise((resolve) => {
|
|
1156
|
+
idle(() => __async(null, null, function* () {
|
|
1157
|
+
yield inlineImages(clone, options);
|
|
762
1158
|
resolve();
|
|
763
|
-
}, { fast });
|
|
1159
|
+
}), { fast });
|
|
764
1160
|
});
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
idle(() => {
|
|
774
|
-
|
|
775
|
-
baseCSSCache.set(tagKey, baseCSS);
|
|
1161
|
+
yield new Promise((resolve) => {
|
|
1162
|
+
idle(() => __async(null, null, function* () {
|
|
1163
|
+
yield inlineBackgroundImages(element, clone, styleCache, options);
|
|
1164
|
+
resolve();
|
|
1165
|
+
}), { fast });
|
|
1166
|
+
});
|
|
1167
|
+
if (embedFonts) {
|
|
1168
|
+
yield new Promise((resolve) => {
|
|
1169
|
+
idle(() => __async(null, null, function* () {
|
|
1170
|
+
fontsCSS = yield embedCustomFonts();
|
|
776
1171
|
resolve();
|
|
777
|
-
}, { fast });
|
|
1172
|
+
}), { fast });
|
|
778
1173
|
});
|
|
779
1174
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
1175
|
+
if (compress) {
|
|
1176
|
+
const usedTags = collectUsedTagNames(clone).sort();
|
|
1177
|
+
const tagKey = usedTags.join(",");
|
|
1178
|
+
if (baseCSSCache.has(tagKey)) {
|
|
1179
|
+
baseCSS = baseCSSCache.get(tagKey);
|
|
1180
|
+
} else {
|
|
1181
|
+
yield new Promise((resolve) => {
|
|
1182
|
+
idle(() => {
|
|
1183
|
+
baseCSS = generateDedupedBaseCSS(usedTags);
|
|
1184
|
+
baseCSSCache.set(tagKey, baseCSS);
|
|
1185
|
+
resolve();
|
|
1186
|
+
}, { fast });
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
yield new Promise((resolve) => {
|
|
1191
|
+
idle(() => {
|
|
1192
|
+
const rect = element.getBoundingClientRect();
|
|
1193
|
+
let w = rect.width;
|
|
1194
|
+
let h = rect.height;
|
|
1195
|
+
const hasW = Number.isFinite(options.width);
|
|
1196
|
+
const hasH = Number.isFinite(options.height);
|
|
1197
|
+
const hasScale = typeof scale === "number" && scale !== 1;
|
|
1198
|
+
if (!hasScale) {
|
|
1199
|
+
const aspect = rect.width / rect.height;
|
|
1200
|
+
if (hasW && hasH) {
|
|
1201
|
+
w = options.width;
|
|
1202
|
+
h = options.height;
|
|
1203
|
+
} else if (hasW) {
|
|
1204
|
+
w = options.width;
|
|
1205
|
+
h = w / aspect;
|
|
1206
|
+
} else if (hasH) {
|
|
1207
|
+
h = options.height;
|
|
1208
|
+
w = h * aspect;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
w = Math.ceil(w);
|
|
1212
|
+
h = Math.ceil(h);
|
|
1213
|
+
clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
1214
|
+
clone.style.transformOrigin = "top left";
|
|
1215
|
+
if (!hasScale && (hasW || hasH)) {
|
|
1216
|
+
const originalW = rect.width;
|
|
1217
|
+
const originalH = rect.height;
|
|
1218
|
+
const scaleX = w / originalW;
|
|
1219
|
+
const scaleY = h / originalH;
|
|
1220
|
+
const existingTransform = clone.style.transform || "";
|
|
1221
|
+
const scaleTransform = `scale(${scaleX}, ${scaleY})`;
|
|
1222
|
+
clone.style.transform = `${scaleTransform} ${existingTransform}`.trim();
|
|
1223
|
+
} else if (hasScale && isSafari()) {
|
|
1224
|
+
clone.style.scale = `${scale}`;
|
|
1225
|
+
}
|
|
1226
|
+
const svgNS = "http://www.w3.org/2000/svg";
|
|
1227
|
+
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
1228
|
+
fo.setAttribute("width", "100%");
|
|
1229
|
+
fo.setAttribute("height", "100%");
|
|
1230
|
+
const styleTag = document.createElement("style");
|
|
1231
|
+
styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;}" + classCSS;
|
|
1232
|
+
fo.appendChild(styleTag);
|
|
1233
|
+
fo.appendChild(clone);
|
|
1234
|
+
const serializer = new XMLSerializer();
|
|
1235
|
+
const foString = serializer.serializeToString(fo);
|
|
1236
|
+
const svgHeader = `<svg xmlns="${svgNS}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`;
|
|
1237
|
+
const svgFooter = "</svg>";
|
|
1238
|
+
svgString = svgHeader + foString + svgFooter;
|
|
1239
|
+
dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
|
|
1240
|
+
resolve();
|
|
1241
|
+
}, { fast });
|
|
1242
|
+
});
|
|
1243
|
+
const sandbox = document.getElementById("snapdom-sandbox");
|
|
1244
|
+
if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
|
|
1245
|
+
return dataURL;
|
|
807
1246
|
});
|
|
808
|
-
const sandbox = document.getElementById("snapdom-sandbox");
|
|
809
|
-
if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
|
|
810
|
-
return dataURL;
|
|
811
1247
|
}
|
|
812
1248
|
|
|
813
1249
|
// src/api/snapdom.js
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1250
|
+
function toImg(_0, _1) {
|
|
1251
|
+
return __async(this, arguments, function* (url, { dpr = 1, scale = 1 }) {
|
|
1252
|
+
const img = new Image();
|
|
1253
|
+
img.src = url;
|
|
1254
|
+
yield img.decode();
|
|
1255
|
+
if (isSafari) {
|
|
1256
|
+
img.width = img.width * scale;
|
|
1257
|
+
img.height = img.height * scale;
|
|
1258
|
+
} else {
|
|
1259
|
+
img.width = img.width / scale;
|
|
1260
|
+
img.height = img.height / scale;
|
|
1261
|
+
}
|
|
1262
|
+
return img;
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
function toCanvas(_0) {
|
|
1266
|
+
return __async(this, arguments, function* (url, { dpr = 1, scale = 1 } = {}) {
|
|
1267
|
+
const img = new Image();
|
|
1268
|
+
img.src = url;
|
|
1269
|
+
yield img.decode();
|
|
1270
|
+
const canvas = document.createElement("canvas");
|
|
1271
|
+
const width = img.width * scale;
|
|
1272
|
+
const height = img.height * scale;
|
|
1273
|
+
canvas.width = Math.ceil(width * dpr);
|
|
1274
|
+
canvas.height = Math.ceil(height * dpr);
|
|
1275
|
+
const ctx = canvas.getContext("2d");
|
|
1276
|
+
ctx.scale(dpr, dpr);
|
|
1277
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
1278
|
+
canvas.style.width = `${width}px`;
|
|
1279
|
+
canvas.style.height = `${height}px`;
|
|
1280
|
+
return canvas;
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
function toBlob(_0) {
|
|
1284
|
+
return __async(this, arguments, function* (url, {
|
|
1285
|
+
type = "svg",
|
|
1286
|
+
scale = 1,
|
|
1287
|
+
backgroundColor = "#fff",
|
|
1288
|
+
quality
|
|
1289
|
+
} = {}) {
|
|
1290
|
+
const mime = {
|
|
1291
|
+
jpg: "image/jpeg",
|
|
1292
|
+
jpeg: "image/jpeg",
|
|
1293
|
+
png: "image/png",
|
|
1294
|
+
webp: "image/webp"
|
|
1295
|
+
}[type] || "image/png";
|
|
1296
|
+
if (type === "svg") {
|
|
1297
|
+
const svgText = decodeURIComponent(url.split(",")[1]);
|
|
1298
|
+
return new Blob([svgText], { type: "image/svg+xml" });
|
|
1299
|
+
}
|
|
1300
|
+
const canvas = yield createBackground(url, { dpr: 1, scale }, backgroundColor);
|
|
1301
|
+
return new Promise((resolve) => {
|
|
1302
|
+
canvas.toBlob((blob) => resolve(blob), `${mime}`, quality);
|
|
1303
|
+
});
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
function createBackground(_0, _1, _2) {
|
|
1307
|
+
return __async(this, arguments, function* (url, { dpr = 1, scale = 1 }, backgroundColor) {
|
|
1308
|
+
const baseCanvas = yield toCanvas(url, { dpr, scale });
|
|
1309
|
+
if (!backgroundColor) return baseCanvas;
|
|
1310
|
+
const temp = document.createElement("canvas");
|
|
1311
|
+
temp.width = baseCanvas.width;
|
|
1312
|
+
temp.height = baseCanvas.height;
|
|
1313
|
+
const ctx = temp.getContext("2d");
|
|
1314
|
+
ctx.fillStyle = backgroundColor;
|
|
1315
|
+
ctx.fillRect(0, 0, temp.width, temp.height);
|
|
1316
|
+
ctx.drawImage(baseCanvas, 0, 0);
|
|
1317
|
+
return temp;
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
function toRasterImg(_0, _1) {
|
|
1321
|
+
return __async(this, arguments, function* (url, { dpr = 1, scale = 1, backgroundColor = "#fff", quality }, format = "png") {
|
|
1322
|
+
const canvas = yield createBackground(url, { dpr, scale }, backgroundColor);
|
|
1323
|
+
const img = new Image();
|
|
1324
|
+
img.src = canvas.toDataURL(`image/${format}`, quality);
|
|
1325
|
+
yield img.decode();
|
|
1326
|
+
img.style.width = `${canvas.width / dpr}px`;
|
|
1327
|
+
img.style.height = `${canvas.height / dpr}px`;
|
|
1328
|
+
return img;
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
function download(_0) {
|
|
1332
|
+
return __async(this, arguments, function* (url, { dpr = 1, scale = 1, backgroundColor = "#fff", format = "png", filename = "capture" } = {}) {
|
|
1333
|
+
if (format === "svg") {
|
|
1334
|
+
const blob = yield toBlob(url);
|
|
1335
|
+
const objectURL = URL.createObjectURL(blob);
|
|
1336
|
+
const a2 = document.createElement("a");
|
|
1337
|
+
a2.href = objectURL;
|
|
1338
|
+
a2.download = `${filename}.svg`;
|
|
1339
|
+
a2.click();
|
|
1340
|
+
URL.revokeObjectURL(objectURL);
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
|
|
1344
|
+
const finalBg = backgroundColor != null ? backgroundColor : defaultBg;
|
|
1345
|
+
const canvas = yield createBackground(url, { dpr, scale }, finalBg);
|
|
1346
|
+
const mime = {
|
|
1347
|
+
jpg: "image/jpeg",
|
|
1348
|
+
jpeg: "image/jpeg",
|
|
1349
|
+
png: "image/png",
|
|
1350
|
+
webp: "image/webp"
|
|
1351
|
+
}[format] || "image/png";
|
|
1352
|
+
const dataURL = canvas.toDataURL(mime);
|
|
1353
|
+
const a = document.createElement("a");
|
|
1354
|
+
a.href = dataURL;
|
|
1355
|
+
a.download = `${filename}.${format}`;
|
|
1356
|
+
a.click();
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
function snapdom(_0) {
|
|
1360
|
+
return __async(this, arguments, function* (element, options = {}) {
|
|
1361
|
+
options = __spreadValues({ scale: 1 }, options);
|
|
1362
|
+
if (!element) throw new Error("Element cannot be null or undefined");
|
|
1363
|
+
if (options.iconFonts) {
|
|
1364
|
+
extendIconFonts(options.iconFonts);
|
|
1365
|
+
}
|
|
1366
|
+
return yield snapdom.capture(element, options);
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
snapdom.capture = (_0, ..._1) => __async(null, [_0, ..._1], function* (el, options = {}) {
|
|
1370
|
+
const url = yield captureDOM(el, options);
|
|
901
1371
|
const dpr = window.devicePixelRatio || 1;
|
|
902
1372
|
const scale = options.scale || 1;
|
|
903
1373
|
return {
|
|
@@ -906,75 +1376,96 @@
|
|
|
906
1376
|
toRaw: () => url,
|
|
907
1377
|
toImg: () => toImg(url, { dpr, scale }),
|
|
908
1378
|
toCanvas: () => toCanvas(url, { dpr, scale }),
|
|
909
|
-
toBlob: () => toBlob(url),
|
|
910
|
-
toPng: (options2) => toRasterImg(url, { dpr, scale,
|
|
911
|
-
toJpg: (options2) => toRasterImg(url, { dpr, scale,
|
|
912
|
-
toWebp: (options2) => toRasterImg(url, { dpr, scale,
|
|
1379
|
+
toBlob: (options2) => toBlob(url, __spreadValues({ dpr, scale }, options2)),
|
|
1380
|
+
toPng: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "png"),
|
|
1381
|
+
toJpg: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "jpeg"),
|
|
1382
|
+
toWebp: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "webp"),
|
|
913
1383
|
download: ({ format = "png", filename = "capture", backgroundColor } = {}) => download(url, { dpr, scale, backgroundColor, format, filename })
|
|
914
1384
|
};
|
|
915
|
-
};
|
|
916
|
-
snapdom.toRaw =
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
snapdom.
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
snapdom.
|
|
923
|
-
|
|
924
|
-
|
|
1385
|
+
});
|
|
1386
|
+
snapdom.toRaw = (el, options) => __async(null, null, function* () {
|
|
1387
|
+
return (yield snapdom.capture(el, options)).toRaw();
|
|
1388
|
+
});
|
|
1389
|
+
snapdom.toImg = (el, options) => __async(null, null, function* () {
|
|
1390
|
+
return (yield snapdom.capture(el, options)).toImg();
|
|
1391
|
+
});
|
|
1392
|
+
snapdom.toCanvas = (el, options) => __async(null, null, function* () {
|
|
1393
|
+
return (yield snapdom.capture(el, options)).toCanvas();
|
|
1394
|
+
});
|
|
1395
|
+
snapdom.toBlob = (el, options) => __async(null, null, function* () {
|
|
1396
|
+
return (yield snapdom.capture(el, options)).toBlob(options);
|
|
1397
|
+
});
|
|
1398
|
+
snapdom.toPng = (el, options) => __async(null, null, function* () {
|
|
1399
|
+
return (yield snapdom.capture(el, options)).toPng(options);
|
|
1400
|
+
});
|
|
1401
|
+
snapdom.toJpg = (el, options) => __async(null, null, function* () {
|
|
1402
|
+
return (yield snapdom.capture(el, options)).toJpg(options);
|
|
1403
|
+
});
|
|
1404
|
+
snapdom.toWebp = (el, options) => __async(null, null, function* () {
|
|
1405
|
+
return (yield snapdom.capture(el, options)).toWebp(options);
|
|
1406
|
+
});
|
|
1407
|
+
snapdom.download = (_0, ..._1) => __async(null, [_0, ..._1], function* (el, options = {}) {
|
|
1408
|
+
const _a = options, {
|
|
925
1409
|
format = "png",
|
|
926
1410
|
filename = "capture",
|
|
927
|
-
backgroundColor
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1411
|
+
backgroundColor
|
|
1412
|
+
} = _a, rest = __objRest(_a, [
|
|
1413
|
+
"format",
|
|
1414
|
+
"filename",
|
|
1415
|
+
"backgroundColor"
|
|
1416
|
+
]);
|
|
1417
|
+
const capture = yield snapdom.capture(el, rest);
|
|
1418
|
+
return yield capture.download({ format, filename, backgroundColor });
|
|
1419
|
+
});
|
|
933
1420
|
|
|
934
1421
|
// src/api/preCache.js
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
}
|
|
945
|
-
await document.fonts.ready;
|
|
946
|
-
precacheCommonTags();
|
|
947
|
-
let imgEls = [], allEls = [];
|
|
948
|
-
if (root?.querySelectorAll) {
|
|
949
|
-
imgEls = Array.from(root.querySelectorAll("img[src]"));
|
|
950
|
-
allEls = Array.from(root.querySelectorAll("*"));
|
|
951
|
-
}
|
|
952
|
-
const promises = [];
|
|
953
|
-
for (const img of imgEls) {
|
|
954
|
-
const src = img.src;
|
|
955
|
-
if (!imageCache.has(src)) {
|
|
956
|
-
const crossOrigin = crossOriginFn ? crossOriginFn(src) : "anonymous";
|
|
957
|
-
promises.push(
|
|
958
|
-
fetchImage(src, 3e3, crossOrigin).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
|
|
959
|
-
})
|
|
960
|
-
);
|
|
1422
|
+
function preCache() {
|
|
1423
|
+
return __async(this, arguments, function* (root = document, options = {}) {
|
|
1424
|
+
const { embedFonts = true, reset = false } = options;
|
|
1425
|
+
if (reset) {
|
|
1426
|
+
imageCache.clear();
|
|
1427
|
+
bgCache.clear();
|
|
1428
|
+
resourceCache.clear();
|
|
1429
|
+
baseCSSCache.clear();
|
|
1430
|
+
return;
|
|
961
1431
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
promises.push(
|
|
969
|
-
fetchImage(url, 3e3, crossOrigin).then((dataURL) => bgCache.set(url, dataURL)).catch(() => {
|
|
970
|
-
})
|
|
971
|
-
);
|
|
1432
|
+
yield document.fonts.ready;
|
|
1433
|
+
precacheCommonTags();
|
|
1434
|
+
let imgEls = [], allEls = [];
|
|
1435
|
+
if (root == null ? void 0 : root.querySelectorAll) {
|
|
1436
|
+
imgEls = Array.from(root.querySelectorAll("img[src]"));
|
|
1437
|
+
allEls = Array.from(root.querySelectorAll("*"));
|
|
972
1438
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1439
|
+
const promises = [];
|
|
1440
|
+
for (const img of imgEls) {
|
|
1441
|
+
const src = img.src;
|
|
1442
|
+
if (!imageCache.has(src)) {
|
|
1443
|
+
promises.push(
|
|
1444
|
+
fetchImage(src, { useProxy: options.useProxy }).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
|
|
1445
|
+
})
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
for (const el of allEls) {
|
|
1450
|
+
const bg = getStyle(el).backgroundImage;
|
|
1451
|
+
if (bg && bg !== "none") {
|
|
1452
|
+
const bgSplits = splitBackgroundImage(bg);
|
|
1453
|
+
for (const entry of bgSplits) {
|
|
1454
|
+
const isUrl = entry.startsWith("url(");
|
|
1455
|
+
if (isUrl) {
|
|
1456
|
+
promises.push(
|
|
1457
|
+
inlineSingleBackgroundEntry(entry, options).catch(() => {
|
|
1458
|
+
})
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
if (embedFonts) {
|
|
1465
|
+
yield embedCustomFonts({ preCached: true });
|
|
1466
|
+
}
|
|
1467
|
+
yield Promise.all(promises);
|
|
1468
|
+
});
|
|
978
1469
|
}
|
|
979
1470
|
|
|
980
1471
|
// src/index.browser.js
|