@zumer/snapdom-plugins 2.2.1 → 3.0.0-beta.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/html-export.js CHANGED
@@ -3,8 +3,13 @@
3
3
  * Adds a toHtml() export that returns the capture as a self-contained,
4
4
  * re-renderable HTML document (clone + inlined styles/fonts) instead of pixels.
5
5
  *
6
- * It unwraps the SVG <foreignObject> snapdom already produced, so the markup
7
- * and CSS match the capture byte-for-byte. Nothing is rasterized.
6
+ * It serializes the captured clone directly, so the markup and CSS match the capture
7
+ * byte-for-byte. Nothing is rasterized, and nothing is re-parsed.
8
+ *
9
+ * That fidelity cuts both ways: the output carries the captured markup AS IT WAS, including
10
+ * any event-handler attributes the original nodes had. It is a faithful copy of a page, not a
11
+ * sanitized one — treat a downloaded or served .html from here the way you would treat the
12
+ * page it came from.
8
13
  *
9
14
  * @param {Object} [options]
10
15
  * @param {boolean} [options.fullDocument=true] - Wrap in <!DOCTYPE html>…; if false, return just <style> + fragment
@@ -17,26 +22,64 @@ export function htmlExport(options = {}) {
17
22
  filename = 'capture.html',
18
23
  } = options;
19
24
 
25
+ // Where the serialized markup is parked between afterRender and toHtml. It has to hang
26
+ // off the CAPTURE, not off this closure: one plugin instance serves every capture it is
27
+ // registered for — always, when registered globally — so a single `let` here was shared
28
+ // state between them. Each afterRender overwrote it, and since a result's toHtml() is
29
+ // called later (that is the point of returning a result object), the LAST capture's
30
+ // markup is what every earlier result answered with:
31
+ //
32
+ // const a = await snapdom(alpha); const b = await snapdom(beta);
33
+ // await a.toHtml() // → beta's markup
34
+ //
35
+ // The capture context is the object both hooks already share (afterRender receives it,
36
+ // and defineExports' ctx is a shallow copy of it, made after the render), so parking the
37
+ // markup there scopes it to exactly one capture and survives every path — including
38
+ // burst's differential recapture, which re-enters the engine and re-fires afterRender.
39
+ const BODY = '__htmlExportBody';
40
+
20
41
  return {
21
42
  name: 'html-export',
43
+ // Reads the render state, mutates nothing, same answer every time: the fast paths can
44
+ // keep memoizing captures that use this plugin.
45
+ pure: true,
46
+
47
+ /**
48
+ * This plugin is a render ENGINE wearing an export's clothes: its input is the finished
49
+ * clone, not pixels. It used to reach that clone the long way around, because
50
+ * defineExports runs after core has released the stage fields:
51
+ *
52
+ * clone -> serialize to SVG -> decodeURIComponent -> DOMParser -> querySelector
53
+ *
54
+ * Four steps to recover a node core had in hand. afterRender is the last hook where
55
+ * `state.clone` is still alive, and by then it already sits inside the wrapper the SVG
56
+ * engine built for it, so `parentNode` IS the container the old code went looking for.
57
+ * One serialization, no decode, no parse.
58
+ *
59
+ * It also stops requiring the capture to be an SVG data URL, so this export keeps
60
+ * working under any render engine.
61
+ */
62
+ afterRender(state) {
63
+ const container = state.clone?.parentNode;
64
+ // HTML raw-text elements do not decode XML entities: XMLSerializer turned `>`
65
+ // and `&` in retained <style> text into different CSS when reopened as HTML.
66
+ state[BODY] = container ? container.outerHTML : '';
67
+ },
22
68
 
23
69
  defineExports() {
24
70
  return {
25
71
  html: async (ctx, opts = {}) => {
26
- const url = ctx.export.url;
27
- if (typeof url !== 'string' || !url.startsWith('data:image/svg+xml')) {
28
- throw new Error('[snapdom] html-export: capture is not an SVG data URL');
72
+ const frozenBody = ctx[BODY];
73
+ if (typeof frozenBody !== 'string') {
74
+ throw new Error(
75
+ '[snapdom] html-export: this capture produced no render. The plugin\'s afterRender ' +
76
+ 'hook never ran, so there is no markup to export (a capture stopped at ' +
77
+ 'needs:\'clone\' never reaches the render stage).'
78
+ );
29
79
  }
30
- const svgString = decodeURIComponent(url.replace(/^data:image\/svg\+xml[^,]*,/, ''));
31
-
32
- const doc = new DOMParser().parseFromString(svgString, 'image/svg+xml');
33
- const fo = doc.querySelector('foreignObject');
34
- if (!fo) throw new Error('[snapdom] html-export: capture has no foreignObject');
35
-
36
- const styleEl = fo.querySelector('style');
37
- const container = fo.querySelector('div');
38
- const css = styleEl ? styleEl.textContent : '';
39
- const body = container ? new XMLSerializer().serializeToString(container) : '';
80
+ const a = ctx.artifacts;
81
+ const css = a ? (a.scrollbarCSS + a.baseCSS + a.fontsCSS + a.classCSS) : '';
82
+ const body = frozenBody;
40
83
 
41
84
  const asDoc = opts.fullDocument ?? fullDocument;
42
85
  const html = asDoc
@@ -49,7 +92,10 @@ export function htmlExport(options = {}) {
49
92
  const objUrl = URL.createObjectURL(blob);
50
93
  const a = document.createElement('a');
51
94
  a.href = objUrl;
52
- a.download = typeof dl === 'string' ? dl : (opts.filename || filename);
95
+ const requested = ctx.export?.requestedOptions;
96
+ a.download = typeof dl === 'string' ? dl
97
+ : (requested && !Object.hasOwn(requested, 'filename') && opts.filename === ctx.filename
98
+ ? filename : (opts.filename || filename));
53
99
  a.click();
54
100
  setTimeout(() => URL.revokeObjectURL(objUrl), 5000);
55
101
  }
package/index.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Type declarations for `@zumer/snapdom-plugins`.
3
+ *
4
+ * The plugin instance type comes from the core package: one definition of what a plugin IS,
5
+ * so a plugin that stops matching the host's contract fails to typecheck instead of failing
6
+ * at capture time.
7
+ */
8
+ export * from './filter.js';
9
+ export * from './color-tint.js';
10
+ export * from './replace-text.js';
11
+ export * from './timestamp-overlay.js';
12
+ export * from './redact-inputs.js';
13
+ export * from './ascii-export.js';
14
+ export * from './pdf-image.js';
15
+ export * from './html-export.js';
16
+ export * from './gif-export.js';
17
+ export * from './video-export.js';
18
+ export * from './agent-map.js';
19
+ export * from './context-export.js';
package/index.js CHANGED
@@ -9,11 +9,12 @@
9
9
  export { timestampOverlay } from './timestamp-overlay.js';
10
10
  export { asciiExport } from './ascii-export.js';
11
11
  export { replaceText } from './replace-text.js';
12
+ export { redactInputs } from './redact-inputs.js';
12
13
  export { filter } from './filter.js';
13
14
  export { colorTint } from './color-tint.js';
14
15
  export { pdfImage } from './pdf-image.js';
15
16
  export { agentMap } from './agent-map.js';
17
+ export { contextExport } from './context-export.js';
16
18
  export { htmlExport } from './html-export.js';
17
19
  export { gifExport } from './gif-export.js';
18
20
  export { videoExport } from './video-export.js';
19
- export { htmlInCanvasPlugin as htmlInCanvas } from './html-in-canvas.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zumer/snapdom-plugins",
3
- "version": "2.2.1",
3
+ "version": "3.0.0-beta.0",
4
4
  "description": "Official plugins for SnapDOM",
5
5
  "type": "module",
6
6
  "exports": {
@@ -8,19 +8,23 @@
8
8
  "./timestamp-overlay": "./timestamp-overlay.js",
9
9
  "./ascii-export": "./ascii-export.js",
10
10
  "./replace-text": "./replace-text.js",
11
+ "./redact-inputs": "./redact-inputs.js",
11
12
  "./filter": "./filter.js",
12
13
  "./color-tint": "./color-tint.js",
13
- "./html-in-canvas": "./html-in-canvas.js",
14
14
  "./pdf-image": "./pdf-image.js",
15
15
  "./agent-map": "./agent-map.js",
16
+ "./context-export": "./context-export.js",
16
17
  "./html-export": "./html-export.js",
17
18
  "./gif-export": "./gif-export.js",
18
19
  "./video-export": "./video-export.js"
19
20
  },
20
21
  "files": [
21
22
  "*.js",
22
- "README.md"
23
+ "*.d.ts",
24
+ "README.md",
25
+ "LICENSE"
23
26
  ],
27
+ "types": "./index.d.ts",
24
28
  "keywords": [
25
29
  "snapdom",
26
30
  "snapdom-plugin",
@@ -37,8 +41,11 @@
37
41
  "bugs": {
38
42
  "url": "https://github.com/zumerlab/snapdom/issues"
39
43
  },
40
- "homepage": "https://zumerlab.github.io/snapdom/",
44
+ "homepage": "https://snapdom.dev/",
41
45
  "peerDependencies": {
42
- "@zumer/snapdom": ">=2.12.1"
46
+ "@zumer/snapdom": ">=3.0.0-0"
47
+ },
48
+ "devDependencies": {
49
+ "@zumer/snapdom": "file:../.."
43
50
  }
44
51
  }
package/pdf-image.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Type declarations for `@zumer/snapdom-plugins/pdf-image`.
3
+ *
4
+ * The plugin instance type comes from the core package: one definition of what a plugin IS,
5
+ * so a plugin that stops matching the host's contract fails to typecheck instead of failing
6
+ * at capture time.
7
+ */
8
+ import type { SnapdomPlugin } from '@zumer/snapdom';
9
+
10
+ export interface PdfImageOptions {
11
+ orientation?: 'portrait' | 'landscape';
12
+ /** JPEG quality, 0-1. */
13
+ quality?: number;
14
+ filename?: string;
15
+ }
16
+
17
+ /** Adds `result.toPdfImage()`: the capture as a JPEG embedded in a downloadable PDF. */
18
+ export declare function pdfImage(options?: PdfImageOptions): SnapdomPlugin;
package/pdf-image.js CHANGED
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * pdfImage - Official SnapDOM Plugin
3
- * Exports the capture as a PNG embedded in a downloadable PDF.
3
+ * Exports the capture as a JPEG embedded in a downloadable PDF.
4
+ * Pinned by __tests__/plugins.imageExports.v3.test.js.
5
+ * @module plugins/pdf-image
4
6
  *
5
7
  * @param {Object} [options]
6
8
  * @param {string} [options.orientation='portrait'] - 'portrait' | 'landscape'
@@ -18,12 +20,14 @@ export function pdfImage(options = {}) {
18
20
  return {
19
21
  name: 'pdf-image',
20
22
 
21
- defineExports() {
23
+ defineExports({ exports }) {
22
24
  return {
23
- pdfImage: async (ctx) => {
24
- const img = new Image();
25
- img.src = ctx.export.url;
26
- await new Promise((res, rej) => { img.onload = res; img.onerror = rej; });
25
+ pdfImage: async (_ctx, opts) => {
26
+ // Core owns sizing, captured originals and Safari's image/font paint workarounds.
27
+ const canvas = await exports.canvas({
28
+ ...opts, canvas: null,
29
+ backgroundColor: !opts.backgroundColor || opts.backgroundColor === 'transparent' ? '#ffffff' : opts.backgroundColor,
30
+ });
27
31
 
28
32
  const isLandscape = orientation === 'landscape';
29
33
  const pageW = isLandscape ? 841.89 : 595.28; // A4 in points
@@ -31,17 +35,13 @@ export function pdfImage(options = {}) {
31
35
  const margin = 40;
32
36
  const maxW = pageW - margin * 2;
33
37
  const maxH = pageH - margin * 2;
34
- const scale = Math.min(maxW / img.width, maxH / img.height, 1);
35
- const w = img.width * scale;
36
- const h = img.height * scale;
38
+ const scale = Math.min(maxW / canvas.width, maxH / canvas.height, 1);
39
+ const w = canvas.width * scale;
40
+ const h = canvas.height * scale;
37
41
  const x = (pageW - w) / 2;
38
42
  const y = (pageH - h) / 2;
39
43
 
40
44
  // Get image as JPEG data
41
- const canvas = document.createElement('canvas');
42
- canvas.width = img.width;
43
- canvas.height = img.height;
44
- canvas.getContext('2d').drawImage(img, 0, 0);
45
45
  const jpegDataUrl = canvas.toDataURL('image/jpeg', quality);
46
46
  const jpegBase64 = jpegDataUrl.split(',')[1];
47
47
  const jpegBytes = atob(jpegBase64);
@@ -61,7 +61,7 @@ export function pdfImage(options = {}) {
61
61
 
62
62
  // Obj 4 — image with binary JPEG stream
63
63
  realOffsets.push(textPart.length);
64
- const imgHeader = `4 0 obj\n<< /Type /XObject /Subtype /Image /Width ${img.width} /Height ${img.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${jpegLen} >>\nstream\n`;
64
+ const imgHeader = `4 0 obj\n<< /Type /XObject /Subtype /Image /Width ${canvas.width} /Height ${canvas.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${jpegLen} >>\nstream\n`;
65
65
 
66
66
  const imgHeaderBytes = enc.encode(imgHeader);
67
67
  const preImgBytes = enc.encode(textPart);
@@ -0,0 +1,55 @@
1
+ // Internal contract shared by official exporters. No core traversal or global state:
2
+ // each capture owns its policy, and export snapshots contain only the permitted values.
3
+ const PRIVACY = Symbol('snapdom.privacy');
4
+ const EMPTY_NAMES = new Set();
5
+ const parentOf = (el) => el.assignedSlot || el.parentElement || el.getRootNode?.().host || null;
6
+ const canonicalName = (el, name) => el.namespaceURI === 'http://www.w3.org/1999/xhtml'
7
+ ? name.toLowerCase() : name;
8
+
9
+ export function getPrivacyPolicy(ctx) {
10
+ return ctx?.[PRIVACY] || null;
11
+ }
12
+
13
+ export function setPrivacyPolicy(ctx, policy) {
14
+ ctx[PRIVACY] = policy;
15
+ }
16
+
17
+ export function createPrivacyPolicy(blocks, attributes, fieldMatches) {
18
+ const blocked = new WeakMap();
19
+ const namesByNode = new WeakMap();
20
+ const isBlocked = (el) => {
21
+ if (!blocks.length || !el || el.nodeType !== 1) return false;
22
+ if (blocked.has(el)) return blocked.get(el);
23
+ const trail = [];
24
+ let result = false;
25
+ for (let node = el; node; node = parentOf(node)) {
26
+ if (blocked.has(node)) { result = blocked.get(node); break; }
27
+ trail.push(node);
28
+ if (blocks.some(selector => node.matches(selector))) { result = true; break; }
29
+ }
30
+ for (const node of trail) blocked.set(node, result);
31
+ return result;
32
+ };
33
+ const redactedNames = (el) => {
34
+ if (!attributes.length || !el || el.nodeType !== 1) return EMPTY_NAMES;
35
+ if (namesByNode.has(el)) return namesByNode.get(el);
36
+ const names = new Set();
37
+ for (const rule of attributes) {
38
+ if (el.matches(rule.selector)) {
39
+ for (const name of rule.names) names.add(canonicalName(el, name));
40
+ }
41
+ }
42
+ namesByNode.set(el, names);
43
+ return names;
44
+ };
45
+ const redactsAttribute = (el, name) => redactedNames(el).has(canonicalName(el, name));
46
+ return {
47
+ blocks,
48
+ isBlocked,
49
+ redactedNames,
50
+ redactsAttribute,
51
+ attribute: (el, name) => redactsAttribute(el, name) ? null : el.getAttribute(name),
52
+ isField: (el) => /^(?:input|textarea)$/.test(el.localName || '') &&
53
+ (fieldMatches(el) || redactsAttribute(el, 'value')),
54
+ };
55
+ }
@@ -0,0 +1,150 @@
1
+ /** Clone-only enforcement for the explicitly configured redactInputs extensions.
2
+ * Matching happens before mutations. The render-boundary pass removes resources added
3
+ * after afterClone, without invoking the caller's field masker a second time.
4
+ */
5
+ const STATE = Symbol('redact-inputs-clone-state');
6
+ const SVG_NS = 'http://www.w3.org/2000/svg';
7
+ const SVG_DEFINITIONS = new Set([
8
+ 'defs', 'symbol', 'linearGradient', 'radialGradient', 'pattern', 'clipPath',
9
+ 'mask', 'marker', 'filter',
10
+ ]);
11
+
12
+ function sourceFor(node, ctx, known) {
13
+ if (node === ctx.clone) return ctx.element || node;
14
+ const mapped = ctx.nodeMap?.get(node);
15
+ if (mapped) return mapped;
16
+ // External SVG definitions are copied with native cloneNode, outside the nodeMap.
17
+ // Resolve their author id in the source tree scope before using clone-side selectors.
18
+ if (node.namespaceURI === SVG_NS) {
19
+ const scope = ctx.element?.getRootNode?.();
20
+ const path = [];
21
+ for (let parent = node; parent && parent !== ctx.clone; parent = parent.parentElement) {
22
+ let original = ctx.nodeMap?.get(parent) || known.get(parent)?.source;
23
+ if (!original || original === parent) {
24
+ original = parent.id && (scope?.getElementById?.(parent.id) ||
25
+ ctx.element?.ownerDocument?.getElementById(parent.id));
26
+ }
27
+ if (original && original !== parent) {
28
+ for (let i = path.length - 1; i >= 0 && original; i--) original = original.children[path[i]];
29
+ if (original?.localName === node.localName && original.namespaceURI === node.namespaceURI) return original;
30
+ }
31
+ if (!parent.parentElement) break;
32
+ path.push([...parent.parentElement.children].indexOf(parent));
33
+ }
34
+ }
35
+ return node;
36
+ }
37
+
38
+ function isDefinition(node) {
39
+ for (let el = node; el?.nodeType === 1 && el.namespaceURI === SVG_NS; el = el.parentElement) {
40
+ if (SVG_DEFINITIONS.has(el.localName)) return true;
41
+ }
42
+ return false;
43
+ }
44
+
45
+ function spacerFor(node, source) {
46
+ const doc = node.ownerDocument;
47
+ // SVG shapes have no HTML flow box to reserve. An empty group preserves their place
48
+ // in the SVG tree without introducing HTML into that namespace.
49
+ if (node.namespaceURI === SVG_NS && node.localName !== 'svg') {
50
+ return doc.createElementNS(SVG_NS, 'g');
51
+ }
52
+ let style;
53
+ try { style = source.ownerDocument.defaultView.getComputedStyle(source); } catch { /* detached clone */ }
54
+ let rect;
55
+ try { rect = source.getBoundingClientRect(); } catch { /* detached clone */ }
56
+ const width = source.offsetWidth || parseFloat(node.style?.width) || rect?.width || 0;
57
+ const height = source.offsetHeight || parseFloat(node.style?.height) || rect?.height || 0;
58
+ const display = style?.display || node.style?.display || 'block';
59
+ const out = node.namespaceURI === SVG_NS
60
+ ? doc.createElementNS(SVG_NS, 'svg') : doc.createElement('div');
61
+ out.style.cssText = `display:${display === 'inline' ? 'inline-block' : display};` +
62
+ `width:${width}px;height:${height}px;visibility:hidden;`;
63
+ return out;
64
+ }
65
+
66
+ function forgetSubtree(map, root) {
67
+ if (!map?.delete) return;
68
+ map.delete(root);
69
+ for (const node of root.querySelectorAll('*')) map.delete(node);
70
+ }
71
+
72
+ function removeAttribute(node, name) {
73
+ const lower = name.toLowerCase();
74
+ // Removing a value attribute alone leaves a dirty form control's displayed property
75
+ // intact. Textareas serialize their value as child text, so that must be cleared too.
76
+ if (lower === 'value' && (node.localName === 'input' || node.localName === 'textarea')) {
77
+ node.value = '';
78
+ node.defaultValue = '';
79
+ if (node.localName === 'textarea') node.textContent = '';
80
+ }
81
+ if (lower === 'checked' && node.localName === 'input') {
82
+ node.checked = false;
83
+ node.defaultChecked = false;
84
+ }
85
+ if (lower === 'selected' && node.localName === 'option') {
86
+ node.selected = false;
87
+ node.defaultSelected = false;
88
+ }
89
+ // Flush a lazily serialized CSSOM value before removal. Blink/WebKit can otherwise
90
+ // materialize style="" during serialization after late background writes.
91
+ if (lower === 'style') node.getAttribute(name);
92
+ node.removeAttribute(name);
93
+ }
94
+
95
+ /**
96
+ * @param {object} ctx Capture context.
97
+ * @param {{isBlocked: Function, redactedNames: Function}} policy Validated plugin policy.
98
+ * @param {{final?: boolean}} options The final pass runs in beforeRender.
99
+ */
100
+ export function sanitizeClone(ctx, policy, { final = false } = {}) {
101
+ const root = ctx.clone;
102
+ if (!root?.querySelectorAll) return;
103
+ const states = ctx[STATE] ||= new WeakMap();
104
+ let state = states.get(policy);
105
+ if (!state) {
106
+ state = { nodes: new WeakMap(), blockedRoot: false };
107
+ states.set(policy, state);
108
+ }
109
+
110
+ // Snapshot ALL matches before removing classes, ids, or selector-dependent metadata.
111
+ // Remember them between passes: an unmapped copied def may lose its identifying id.
112
+ const work = [root, ...root.querySelectorAll('*')].map(node => {
113
+ const previous = state.nodes.get(node);
114
+ const source = previous?.source || sourceFor(node, ctx, state.nodes);
115
+ const names = new Set(previous?.names || []);
116
+ for (const name of policy.redactedNames(source)) names.add(name);
117
+ const blocked = previous?.blocked || policy.isBlocked(source);
118
+ const entry = { node, source, names, blocked };
119
+ state.nodes.set(node, entry);
120
+ return entry;
121
+ });
122
+
123
+ if (state.blockedRoot || work[0].blocked) {
124
+ state.blockedRoot = true;
125
+ forgetSubtree(ctx.nodeMap, root);
126
+ ctx.clone = spacerFor(root, ctx.element || root);
127
+ // A root pseudo can already have contributed literal content to scoped CSS. A fully
128
+ // blocked capture has no authored paint to preserve, including late font/assets CSS.
129
+ ctx.classCSS = '';
130
+ if (final) {
131
+ ctx.fontsCSS = '';
132
+ ctx.scrollbarCSS = '';
133
+ }
134
+ return;
135
+ }
136
+
137
+ for (const { node, source, names, blocked } of work) {
138
+ if (node !== root && !root.contains(node)) continue;
139
+ if (blocked) {
140
+ forgetSubtree(ctx.nodeMap, node);
141
+ if (isDefinition(node)) node.remove();
142
+ else node.replaceWith(spacerFor(node, source));
143
+ continue;
144
+ }
145
+ for (const name of names) removeAttribute(node, name);
146
+ // Layout reconciliation runs after beforeRender and writes inline dimensions to
147
+ // mapped nodes. Explicitly removing style must also opt that node out of repinning.
148
+ if (final && [...names].some(name => name.toLowerCase() === 'style')) ctx.nodeMap?.delete(node);
149
+ }
150
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Type declarations for `@zumer/snapdom-plugins/redact-inputs`.
3
+ *
4
+ * The plugin instance type comes from the core package: one definition of what a plugin IS,
5
+ * so a plugin that stops matching the host's contract fails to typecheck instead of failing
6
+ * at capture time.
7
+ */
8
+ import type { SnapdomPlugin } from '@zumer/snapdom';
9
+
10
+ export interface RedactInputsOptions {
11
+ /** `input` type values to redact. */
12
+ types?: string[];
13
+ /** autocomplete tokens to redact; a trailing '*' matches a prefix. */
14
+ autocomplete?: string[];
15
+ /** Extra CSS selector for input/textarea source elements. */
16
+ selector?: string;
17
+ /** Redact EVERY input and textarea, ignoring the lists above. */
18
+ all?: boolean;
19
+ /** Custom masker, called with the cloned field. Defaults to same-length bullets; glyph widths may differ. */
20
+ mask?: (value: string, el: Element) => string;
21
+ /**
22
+ * CSS selectors for source subtrees to exclude. Uses the capture's excludeMode ('hide' by default).
23
+ * If the capture root belongs to a blocked subtree, 'remove' throws; use 'hide'.
24
+ */
25
+ blocks?: string | string[];
26
+ /**
27
+ * Remove exact named attributes from matching clones and their semantic projections; names are not patterns.
28
+ * Does not erase copies already rendered as text, CSS content or bitmap pixels; use blocks for visible content.
29
+ * A value rule on input/textarea also clears its displayed value and omits its structured state.value.
30
+ */
31
+ attributes?: Array<{ selector: string; names: string[] }>;
32
+ }
33
+
34
+ /**
35
+ * Masks form-control values before they reach the serialized capture. Core masks
36
+ * `type="password"` only — that mask is fidelity-neutral because the control already paints
37
+ * bullets. Everything the browser paints in the clear is captured as-is unless you add this.
38
+ * Optional blocks exclude whole subtrees; attribute rules remove named attributes. The live DOM is unchanged.
39
+ * These rules do not search captured text or images for sensitive content.
40
+ * The default mask without selectors permits unchanged-repeat memoization; custom masks and selectors run every capture.
41
+ * Nonempty blocks/attribute rules add a beforeRender pass, so experimental html-in-canvas captures use SVG.
42
+ */
43
+ export declare function redactInputs(options?: RedactInputsOptions): SnapdomPlugin;