@zumer/snapdom 1.9.11 → 1.9.12-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 +7 -3
- package/dist/snapdom.js +201 -61
- package/dist/snapdom.min.js +4 -4
- package/dist/snapdom.min.mjs +4 -4
- package/dist/snapdom.mjs +185 -46
- package/package.json +1 -1
- package/types/snapdom.d.ts +15 -0
package/dist/snapdom.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.1.9.
|
|
4
|
+
* v.1.9.12-dev.0
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -127,10 +127,25 @@ var snapLogger = createSnapLogger("[snapDOM]", { ttlMs: 3 * 6e4, maxEntries: 10
|
|
|
127
127
|
var _inflight = /* @__PURE__ */ new Map();
|
|
128
128
|
var _errorCache = /* @__PURE__ */ new Map();
|
|
129
129
|
function isSpecialURL(url) {
|
|
130
|
-
return /^data:|^blob:|^about:blank
|
|
130
|
+
return /^data:|^blob:|^about:blank$/i.test(url);
|
|
131
|
+
}
|
|
132
|
+
function isAlreadyProxied(url, useProxy) {
|
|
133
|
+
try {
|
|
134
|
+
const baseHref = typeof location !== "undefined" && location.href ? location.href : "http://localhost/";
|
|
135
|
+
const proxyBaseRaw = useProxy.includes("{url}") ? useProxy.split("{url}")[0] : useProxy;
|
|
136
|
+
const proxyBase = new URL(proxyBaseRaw || ".", baseHref);
|
|
137
|
+
const u = new URL(url, baseHref);
|
|
138
|
+
if (u.origin === proxyBase.origin) return true;
|
|
139
|
+
const sp = u.searchParams;
|
|
140
|
+
if (sp && (sp.has("url") || sp.has("target"))) return true;
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
131
144
|
}
|
|
132
145
|
function shouldProxy(url, useProxy) {
|
|
133
|
-
if (!useProxy
|
|
146
|
+
if (!useProxy) return false;
|
|
147
|
+
if (isSpecialURL(url)) return false;
|
|
148
|
+
if (isAlreadyProxied(url, useProxy)) return false;
|
|
134
149
|
try {
|
|
135
150
|
const base = typeof location !== "undefined" && location.href ? location.href : "http://localhost/";
|
|
136
151
|
const u = new URL(url, base);
|
|
@@ -141,8 +156,19 @@ function shouldProxy(url, useProxy) {
|
|
|
141
156
|
}
|
|
142
157
|
function applyProxy(url, useProxy) {
|
|
143
158
|
if (!useProxy) return url;
|
|
144
|
-
if (useProxy.includes("{url}"))
|
|
145
|
-
|
|
159
|
+
if (useProxy.includes("{url}")) {
|
|
160
|
+
return useProxy.replace("{urlRaw}", safeEncodeURI(url)).replace("{url}", encodeURIComponent(url));
|
|
161
|
+
}
|
|
162
|
+
if (/[?&]url=?$/.test(useProxy)) {
|
|
163
|
+
return `${useProxy}${encodeURIComponent(url)}`;
|
|
164
|
+
}
|
|
165
|
+
if (useProxy.endsWith("?")) {
|
|
166
|
+
return `${useProxy}url=${encodeURIComponent(url)}`;
|
|
167
|
+
}
|
|
168
|
+
if (useProxy.endsWith("/")) {
|
|
169
|
+
return `${useProxy}${safeEncodeURI(url)}`;
|
|
170
|
+
}
|
|
171
|
+
const sep = useProxy.includes("?") ? "&" : "?";
|
|
146
172
|
return `${useProxy}${sep}url=${encodeURIComponent(url)}`;
|
|
147
173
|
}
|
|
148
174
|
function blobToDataURL(blob) {
|
|
@@ -169,6 +195,65 @@ async function snapFetch(url, options = {}) {
|
|
|
169
195
|
const errorTTL = options.errorTTL ?? 8e3;
|
|
170
196
|
const headers = options.headers || {};
|
|
171
197
|
const silent = !!options.silent;
|
|
198
|
+
if (/^data:/i.test(url)) {
|
|
199
|
+
try {
|
|
200
|
+
if (as === "text") {
|
|
201
|
+
return { ok: true, data: String(url), status: 200, url, fromCache: false };
|
|
202
|
+
}
|
|
203
|
+
if (as === "dataURL") {
|
|
204
|
+
return {
|
|
205
|
+
ok: true,
|
|
206
|
+
data: String(url),
|
|
207
|
+
status: 200,
|
|
208
|
+
url,
|
|
209
|
+
fromCache: false,
|
|
210
|
+
mime: String(url).slice(5).split(";")[0] || ""
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const [, meta = "", data = ""] = String(url).match(/^data:([^,]*),(.*)$/) || [];
|
|
214
|
+
const isBase64 = /;base64/i.test(meta);
|
|
215
|
+
const bin = isBase64 ? atob(data) : decodeURIComponent(data);
|
|
216
|
+
const bytes = new Uint8Array([...bin].map((c) => c.charCodeAt(0)));
|
|
217
|
+
const b = new Blob([bytes], { type: (meta || "").split(";")[0] || "" });
|
|
218
|
+
return { ok: true, data: b, status: 200, url, fromCache: false, mime: b.type || "" };
|
|
219
|
+
} catch {
|
|
220
|
+
return { ok: false, data: null, status: 0, url, fromCache: false, reason: "special_url_error" };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (/^blob:/i.test(url)) {
|
|
224
|
+
try {
|
|
225
|
+
const resp = await fetch(url);
|
|
226
|
+
if (!resp.ok) {
|
|
227
|
+
return { ok: false, data: null, status: resp.status, url, fromCache: false, reason: "http_error" };
|
|
228
|
+
}
|
|
229
|
+
const blob = await resp.blob();
|
|
230
|
+
const mime = blob.type || resp.headers.get("content-type") || "";
|
|
231
|
+
if (as === "dataURL") {
|
|
232
|
+
const dataURL = await blobToDataURL(blob);
|
|
233
|
+
return { ok: true, data: dataURL, status: resp.status, url, fromCache: false, mime };
|
|
234
|
+
}
|
|
235
|
+
if (as === "text") {
|
|
236
|
+
const text = await blob.text();
|
|
237
|
+
return { ok: true, data: text, status: resp.status, url, fromCache: false, mime };
|
|
238
|
+
}
|
|
239
|
+
return { ok: true, data: blob, status: resp.status, url, fromCache: false, mime };
|
|
240
|
+
} catch {
|
|
241
|
+
return { ok: false, data: null, status: 0, url, fromCache: false, reason: "network" };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (/^about:blank$/i.test(url)) {
|
|
245
|
+
if (as === "dataURL") {
|
|
246
|
+
return {
|
|
247
|
+
ok: true,
|
|
248
|
+
data: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==",
|
|
249
|
+
status: 200,
|
|
250
|
+
url,
|
|
251
|
+
fromCache: false,
|
|
252
|
+
mime: "image/png"
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return { ok: true, data: as === "text" ? "" : new Blob([]), status: 200, url, fromCache: false };
|
|
256
|
+
}
|
|
172
257
|
const key = makeKey(url, { as, timeout, useProxy, errorTTL });
|
|
173
258
|
const e = _errorCache.get(key);
|
|
174
259
|
if (e && e.until > Date.now()) {
|
|
@@ -197,7 +282,7 @@ async function snapFetch(url, options = {}) {
|
|
|
197
282
|
const resp = await fetch(finalURL, { signal: ctrl.signal, credentials: cred, headers });
|
|
198
283
|
if (!resp.ok) {
|
|
199
284
|
const result = { ok: false, data: null, status: resp.status, url: finalURL, fromCache: false, reason: "http_error" };
|
|
200
|
-
_errorCache.set(key, { until: Date.now() + errorTTL, result });
|
|
285
|
+
if (errorTTL > 0) _errorCache.set(key, { until: Date.now() + errorTTL, result });
|
|
201
286
|
if (!silent) {
|
|
202
287
|
const short = `${resp.status} ${resp.statusText || ""}`.trim();
|
|
203
288
|
snapLogger.warnOnce(
|
|
@@ -222,7 +307,9 @@ async function snapFetch(url, options = {}) {
|
|
|
222
307
|
} catch (err) {
|
|
223
308
|
const reason = err && typeof err === "object" && "name" in err && err.name === "AbortError" ? String(err.message || "").includes("timeout") ? "timeout" : "abort" : "network";
|
|
224
309
|
const result = { ok: false, data: null, status: 0, url: finalURL, fromCache: false, reason };
|
|
225
|
-
|
|
310
|
+
if (!/^blob:/i.test(url) && errorTTL > 0) {
|
|
311
|
+
_errorCache.set(key, { until: Date.now() + errorTTL, result });
|
|
312
|
+
}
|
|
226
313
|
if (!silent) {
|
|
227
314
|
const k = `${reason}:${as}:${new URL(url, location?.href ?? "http://localhost/").origin}`;
|
|
228
315
|
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`;
|
|
@@ -898,19 +985,27 @@ async function deepClone(node, sessionCache, options) {
|
|
|
898
985
|
return node.cloneNode(true);
|
|
899
986
|
}
|
|
900
987
|
if (node.getAttribute("data-capture") === "exclude") {
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
988
|
+
if (options.excludeMode === "visuallyHide") {
|
|
989
|
+
const spacer = document.createElement("div");
|
|
990
|
+
const rect = node.getBoundingClientRect();
|
|
991
|
+
spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
|
|
992
|
+
return spacer;
|
|
993
|
+
} else if (options.excludeMode === "remove") {
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
905
996
|
}
|
|
906
997
|
if (options.exclude && Array.isArray(options.exclude)) {
|
|
907
998
|
for (const selector of options.exclude) {
|
|
908
999
|
try {
|
|
909
1000
|
if (node.matches?.(selector)) {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1001
|
+
if (options.excludeMode === "visuallyHide") {
|
|
1002
|
+
const spacer = document.createElement("div");
|
|
1003
|
+
const rect = node.getBoundingClientRect();
|
|
1004
|
+
spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
|
|
1005
|
+
return spacer;
|
|
1006
|
+
} else if (options.excludeMode === "remove") {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
914
1009
|
}
|
|
915
1010
|
} catch (err) {
|
|
916
1011
|
console.warn(`Invalid selector in exclude option: ${selector}`, err);
|
|
@@ -920,10 +1015,14 @@ async function deepClone(node, sessionCache, options) {
|
|
|
920
1015
|
if (typeof options.filter === "function") {
|
|
921
1016
|
try {
|
|
922
1017
|
if (!options.filter(node)) {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1018
|
+
if (options.filterMode === "visuallyHide") {
|
|
1019
|
+
const spacer = document.createElement("div");
|
|
1020
|
+
const rect = node.getBoundingClientRect();
|
|
1021
|
+
spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
|
|
1022
|
+
return spacer;
|
|
1023
|
+
} else if (options.filterMode === "remove") {
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
927
1026
|
}
|
|
928
1027
|
} catch (err) {
|
|
929
1028
|
console.warn("Error in filter function:", err);
|
|
@@ -2385,7 +2484,7 @@ async function captureDOM(element, options) {
|
|
|
2385
2484
|
const families = new Set(
|
|
2386
2485
|
Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
|
|
2387
2486
|
);
|
|
2388
|
-
await ensureFontsReady(families,
|
|
2487
|
+
await ensureFontsReady(families, 2);
|
|
2389
2488
|
}
|
|
2390
2489
|
fontsCSS = await embedCustomFonts({
|
|
2391
2490
|
required,
|
|
@@ -2660,7 +2759,9 @@ function createContext(options = {}) {
|
|
|
2660
2759
|
scale: options.scale ?? 1,
|
|
2661
2760
|
// DOM filters
|
|
2662
2761
|
exclude: options.exclude ?? [],
|
|
2762
|
+
excludeMode: options.excludeMode ?? "visuallyHide",
|
|
2663
2763
|
filter: options.filter ?? null,
|
|
2764
|
+
filterMode: options.filterMode ?? "visuallyHide",
|
|
2664
2765
|
placeholders: options.placeholders !== false,
|
|
2665
2766
|
// default true
|
|
2666
2767
|
// Fuentes
|
|
@@ -2703,24 +2804,12 @@ async function toImg(url, options) {
|
|
|
2703
2804
|
// src/exporters/toCanvas.js
|
|
2704
2805
|
async function toCanvas(url, options) {
|
|
2705
2806
|
const { scale, dpr } = options;
|
|
2706
|
-
const safari = isSafari();
|
|
2707
2807
|
const img = new Image();
|
|
2708
2808
|
img.crossOrigin = "anonymous";
|
|
2709
2809
|
img.loading = "eager";
|
|
2710
2810
|
img.decoding = "sync";
|
|
2711
2811
|
img.src = url;
|
|
2712
|
-
let tempSlot = null;
|
|
2713
|
-
if (safari) {
|
|
2714
|
-
tempSlot = document.createElement("div");
|
|
2715
|
-
tempSlot.setAttribute("aria-hidden", "true");
|
|
2716
|
-
tempSlot.style.cssText = "position:absolute;left:-99999px;top:0;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none;contain:size layout style;";
|
|
2717
|
-
document.body.appendChild(tempSlot);
|
|
2718
|
-
tempSlot.appendChild(img);
|
|
2719
|
-
}
|
|
2720
2812
|
await img.decode();
|
|
2721
|
-
if (safari) {
|
|
2722
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
2723
|
-
}
|
|
2724
2813
|
const width = img.naturalWidth * scale;
|
|
2725
2814
|
const height = img.naturalHeight * scale;
|
|
2726
2815
|
const canvas = document.createElement("canvas");
|
|
@@ -2731,12 +2820,6 @@ async function toCanvas(url, options) {
|
|
|
2731
2820
|
const ctx = canvas.getContext("2d");
|
|
2732
2821
|
if (dpr !== 1) ctx.scale(dpr, dpr);
|
|
2733
2822
|
ctx.drawImage(img, 0, 0, width, height);
|
|
2734
|
-
if (tempSlot && tempSlot.parentNode) {
|
|
2735
|
-
try {
|
|
2736
|
-
tempSlot.parentNode.removeChild(tempSlot);
|
|
2737
|
-
} catch {
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
2823
|
return canvas;
|
|
2741
2824
|
}
|
|
2742
2825
|
|
|
@@ -2792,8 +2875,19 @@ async function download(url, options) {
|
|
|
2792
2875
|
|
|
2793
2876
|
// src/api/snapdom.js
|
|
2794
2877
|
var INTERNAL_TOKEN = Symbol("snapdom.internal");
|
|
2878
|
+
var _safariWarmup = false;
|
|
2795
2879
|
async function snapdom(element, userOptions) {
|
|
2796
2880
|
if (!element) throw new Error("Element cannot be null or undefined");
|
|
2881
|
+
if (isSafari()) {
|
|
2882
|
+
for (let i = 0; i < 3; i++) {
|
|
2883
|
+
try {
|
|
2884
|
+
await safariWarmup(element, userOptions);
|
|
2885
|
+
console.log("Iteraci\xF3n n\xFAmero:", i);
|
|
2886
|
+
_safariWarmup = false;
|
|
2887
|
+
} catch (error) {
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2797
2891
|
const context = createContext(userOptions);
|
|
2798
2892
|
if (context.iconFonts && context.iconFonts.length > 0) extendIconFonts(context.iconFonts);
|
|
2799
2893
|
if (!context.snap) {
|
|
@@ -2837,24 +2931,65 @@ snapdom.toPng = (el, options) => snapdom(el, { ...options, format: "png" }).then
|
|
|
2837
2931
|
snapdom.toJpg = (el, options) => snapdom(el, { ...options, format: "jpeg" }).then((result) => result.toJpg());
|
|
2838
2932
|
snapdom.toWebp = (el, options) => snapdom(el, { ...options, format: "webp" }).then((result) => result.toWebp());
|
|
2839
2933
|
snapdom.download = (el, options) => snapdom(el, options).then((result) => result.download());
|
|
2934
|
+
async function safariWarmup(element, baseOptions) {
|
|
2935
|
+
if (_safariWarmup) return;
|
|
2936
|
+
const preflight = {
|
|
2937
|
+
...baseOptions,
|
|
2938
|
+
fast: true,
|
|
2939
|
+
embedFonts: true,
|
|
2940
|
+
scale: 0.2
|
|
2941
|
+
};
|
|
2942
|
+
let url;
|
|
2943
|
+
try {
|
|
2944
|
+
url = await captureDOM(element, preflight);
|
|
2945
|
+
} catch {
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
await new Promise((resolve) => {
|
|
2949
|
+
const img = new Image();
|
|
2950
|
+
img.decoding = "sync";
|
|
2951
|
+
img.loading = "eager";
|
|
2952
|
+
img.style.position = "fixed";
|
|
2953
|
+
img.style.left = 0;
|
|
2954
|
+
img.style.top = 0;
|
|
2955
|
+
img.style.width = "10px";
|
|
2956
|
+
img.style.height = "10px";
|
|
2957
|
+
img.style.opacity = "0.01";
|
|
2958
|
+
img.style.transform = "translateZ(10px)";
|
|
2959
|
+
img.style.willChange = "transform,opacity;";
|
|
2960
|
+
img.src = url;
|
|
2961
|
+
const cleanup = async () => {
|
|
2962
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
2963
|
+
if (img && img.parentNode) img.parentNode.removeChild(img);
|
|
2964
|
+
_safariWarmup = true;
|
|
2965
|
+
resolve();
|
|
2966
|
+
};
|
|
2967
|
+
document.body.appendChild(img);
|
|
2968
|
+
cleanup();
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2840
2971
|
|
|
2841
2972
|
// src/api/preCache.js
|
|
2842
2973
|
async function preCache(root = document, options = {}) {
|
|
2843
2974
|
const {
|
|
2844
2975
|
embedFonts = true,
|
|
2845
|
-
cacheOpt = "full",
|
|
2846
2976
|
useProxy = ""
|
|
2847
2977
|
} = options;
|
|
2848
|
-
|
|
2978
|
+
const cacheMode = options.cache ?? options.cacheOpt ?? "full";
|
|
2979
|
+
applyCachePolicy(cacheMode);
|
|
2849
2980
|
try {
|
|
2850
|
-
await document.fonts
|
|
2981
|
+
await document.fonts?.ready;
|
|
2982
|
+
} catch {
|
|
2983
|
+
}
|
|
2984
|
+
try {
|
|
2985
|
+
precacheCommonTags();
|
|
2851
2986
|
} catch {
|
|
2852
2987
|
}
|
|
2853
|
-
precacheCommonTags();
|
|
2854
2988
|
cache.session = cache.session || {};
|
|
2855
2989
|
if (!cache.session.styleCache) {
|
|
2856
2990
|
cache.session.styleCache = /* @__PURE__ */ new WeakMap();
|
|
2857
2991
|
}
|
|
2992
|
+
cache.image = cache.image || /* @__PURE__ */ new Map();
|
|
2858
2993
|
try {
|
|
2859
2994
|
await inlineBackgroundImages(
|
|
2860
2995
|
root,
|
|
@@ -2875,11 +3010,14 @@ async function preCache(root = document, options = {}) {
|
|
|
2875
3010
|
}
|
|
2876
3011
|
const promises = [];
|
|
2877
3012
|
for (const img of imgEls) {
|
|
2878
|
-
const src = img?.src;
|
|
3013
|
+
const src = img?.currentSrc || img?.src;
|
|
2879
3014
|
if (!src) continue;
|
|
2880
3015
|
if (!cache.image.has(src)) {
|
|
2881
|
-
const p = Promise.resolve().then(
|
|
2882
|
-
|
|
3016
|
+
const p = Promise.resolve().then(async () => {
|
|
3017
|
+
const res = await snapFetch(src, { as: "dataURL", useProxy });
|
|
3018
|
+
if (res?.ok && typeof res.data === "string") {
|
|
3019
|
+
cache.image.set(src, res.data);
|
|
3020
|
+
}
|
|
2883
3021
|
}).catch(() => {
|
|
2884
3022
|
});
|
|
2885
3023
|
promises.push(p);
|
|
@@ -2906,7 +3044,8 @@ async function preCache(root = document, options = {}) {
|
|
|
2906
3044
|
try {
|
|
2907
3045
|
const required = collectUsedFontVariants(root);
|
|
2908
3046
|
const usedCodepoints = collectUsedCodepoints(root);
|
|
2909
|
-
|
|
3047
|
+
const safari = typeof isSafari === "function" ? isSafari() : !!isSafari;
|
|
3048
|
+
if (safari) {
|
|
2910
3049
|
const families = new Set(
|
|
2911
3050
|
Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
|
|
2912
3051
|
);
|
package/package.json
CHANGED
package/types/snapdom.d.ts
CHANGED
|
@@ -13,6 +13,9 @@ export type BlobType = "svg" | RasterMime;
|
|
|
13
13
|
export type IconFontMatcher = string | RegExp;
|
|
14
14
|
export type CachePolicy = "disabled" | "full" | "auto" | "soft";
|
|
15
15
|
|
|
16
|
+
export type ExcludeMode = 'visuallyHide' | 'remove';
|
|
17
|
+
export type FilterMode = 'visuallyHide' | 'remove';
|
|
18
|
+
|
|
16
19
|
export interface LocalFontDescriptor {
|
|
17
20
|
/** CSS font-family name (e.g. "Inter"). */
|
|
18
21
|
family: string;
|
|
@@ -87,12 +90,24 @@ export interface CaptureOptions {
|
|
|
87
90
|
/** CSS selectors removed from the cloned subtree before processing. */
|
|
88
91
|
exclude?: string[];
|
|
89
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Mode applied to excluded nodes of the cloned tree.
|
|
95
|
+
* Default: "visuallyHide"
|
|
96
|
+
*/
|
|
97
|
+
excludeMode?: ExcludeMode;
|
|
98
|
+
|
|
90
99
|
/**
|
|
91
100
|
* Advanced node filter; return false to exclude a node during traversal.
|
|
92
101
|
* Applied to the cloned subtree.
|
|
93
102
|
*/
|
|
94
103
|
filter?: (el: Element) => boolean;
|
|
95
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Mode applied to filtered nodes of the cloned tree.
|
|
107
|
+
* Default: "visuallyHide"
|
|
108
|
+
*/
|
|
109
|
+
filterMode?: FilterMode;
|
|
110
|
+
|
|
96
111
|
/**
|
|
97
112
|
* Whether to synthesize placeholders for broken images, etc.
|
|
98
113
|
* Default: true
|