@guidelytech/guide-runtime 0.16.0 → 0.17.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.
@@ -0,0 +1,445 @@
1
+ import {
2
+ normalizeText
3
+ } from "./chunk-EPR4LWA2.js";
4
+
5
+ // src/capture-dom.ts
6
+ var CAPTURE_INTERACTIVE_SELECTOR = "a,button,input,select,textarea,label,summary,[role=button],[role=link],[role=tab],[role=menuitem],[role=menuitemcheckbox],[role=menuitemradio],[role=option],[role=checkbox],[role=radio],[role=switch],[role=combobox],[contenteditable]:not([contenteditable=false])";
7
+ var REDACT_SELECTOR = "[data-guidely-redact]";
8
+ var TEXT_INPUT_TYPES = /* @__PURE__ */ new Set(["", "text", "email", "search", "tel", "url", "number", "password", "date", "datetime-local", "month", "week", "time"]);
9
+ function closestSafe(el, selector) {
10
+ if (!selector) return null;
11
+ try {
12
+ return el.closest(selector);
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+ function inputType(el) {
18
+ return el.tagName === "INPUT" ? (el.getAttribute("type") || "").toLowerCase() : "";
19
+ }
20
+ function isEditableHost(el) {
21
+ const v = el.getAttribute("contenteditable");
22
+ return v != null && v.toLowerCase() !== "false";
23
+ }
24
+ function isTextEntry(el) {
25
+ if (el.tagName === "TEXTAREA") return true;
26
+ if (el.tagName === "INPUT") return TEXT_INPUT_TYPES.has(inputType(el));
27
+ return isEditableHost(el) || !!closestSafe(el, "[contenteditable]:not([contenteditable=false])");
28
+ }
29
+ function actionTarget(target) {
30
+ const hit = closestSafe(target, CAPTURE_INTERACTIVE_SELECTOR) ?? target;
31
+ if (!isEditableHost(hit) && isTextEntry(hit) && hit.tagName !== "INPUT" && hit.tagName !== "TEXTAREA") return closestSafe(hit, "[contenteditable]:not([contenteditable=false])") ?? hit;
32
+ return hit;
33
+ }
34
+ function labelControl(el) {
35
+ if (el.tagName !== "LABEL") return null;
36
+ const label = el;
37
+ if (label.control) return label.control;
38
+ const forId = label.getAttribute("for");
39
+ if (forId) return label.ownerDocument.getElementById(forId);
40
+ return label.querySelector("input,select,textarea");
41
+ }
42
+ function isMaskedElement(el, maskSelector) {
43
+ return !!closestSafe(el, REDACT_SELECTOR) || !!closestSafe(el, maskSelector);
44
+ }
45
+ var SENSITIVE_NAME = /pass|card|cvv|cvc|ssn|secret|token|otp/i;
46
+ var SENSITIVE_AUTOCOMPLETE = /cc-|one-time-code|current-password|new-password/i;
47
+ function isSensitiveField(el, maskSelector = "") {
48
+ if (inputType(el) === "password") return true;
49
+ if (SENSITIVE_AUTOCOMPLETE.test(el.getAttribute("autocomplete") || "")) return true;
50
+ if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (SENSITIVE_NAME.test(el.getAttribute("name") || "") || SENSITIVE_NAME.test(el.id || ""))) return true;
51
+ return isMaskedElement(el, maskSelector);
52
+ }
53
+ var EMAIL = /[A-Z0-9._%+-]+@[A-Z0-9-]+(?:\.[A-Z0-9-]+)*\.[A-Z]{2,}/gi;
54
+ var NUMBER_RUN = /(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d+(?:[ .-]\d+)*/g;
55
+ function classifyNumber(run) {
56
+ const groups = run.match(/\d+/g) ?? [];
57
+ const lens = groups.map((g) => g.length);
58
+ const digits = lens.reduce((a, b) => a + b, 0);
59
+ const special = /[+(.]/.test(run);
60
+ if (lens.length === 3 && lens[0] === 3 && lens[1] === 2 && lens[2] === 4 && /^\d{3}[ -]\d{2}[ -]\d{4}$/.test(run)) return "ssn";
61
+ if (digits >= 13 && digits <= 19 && !special && lens.every((l) => l >= 4)) return "card";
62
+ if (digits >= 10 && digits <= 13 && (/[+(]/.test(run) || lens.length >= 3 && lens[lens.length - 1] === 4 && lens.every((l) => l >= 2) && !run.includes("."))) return "phone";
63
+ return null;
64
+ }
65
+ function findSensitiveText(text) {
66
+ const out = [];
67
+ if (text.length < 7) return out;
68
+ if (text.includes("@")) {
69
+ EMAIL.lastIndex = 0;
70
+ for (let m = EMAIL.exec(text); m && out.length < 200; m = EMAIL.exec(text)) out.push([m.index, m.index + m[0].length]);
71
+ }
72
+ if (/\d{3}/.test(text)) {
73
+ NUMBER_RUN.lastIndex = 0;
74
+ for (let m = NUMBER_RUN.exec(text); m && out.length < 200; m = NUMBER_RUN.exec(text)) {
75
+ if (m[0].length >= 9 && classifyNumber(m[0])) out.push([m.index, m.index + m[0].length]);
76
+ }
77
+ }
78
+ return out;
79
+ }
80
+ function looksLikeSecretValue(value) {
81
+ NUMBER_RUN.lastIndex = 0;
82
+ for (let m = NUMBER_RUN.exec(value); m; m = NUMBER_RUN.exec(value)) {
83
+ const kind = classifyNumber(m[0]);
84
+ if (kind === "card" || kind === "ssn") return true;
85
+ }
86
+ return false;
87
+ }
88
+ var ROLE_MAP = {
89
+ button: "button",
90
+ link: "link",
91
+ tab: "tab",
92
+ menuitem: "menuitem",
93
+ menuitemcheckbox: "menuitem",
94
+ menuitemradio: "menuitem",
95
+ checkbox: "checkbox",
96
+ radio: "radio",
97
+ switch: "switch",
98
+ option: "option",
99
+ combobox: "dropdown",
100
+ listbox: "dropdown",
101
+ textbox: "field",
102
+ searchbox: "field",
103
+ spinbutton: "field"
104
+ };
105
+ function captureRole(el) {
106
+ const explicit = (el.getAttribute("role") || "").trim().split(/\s+/)[0].toLowerCase();
107
+ if (ROLE_MAP[explicit]) return ROLE_MAP[explicit];
108
+ const tag = el.tagName.toLowerCase();
109
+ if (tag === "a") return el.hasAttribute("href") ? "link" : "element";
110
+ if (tag === "button" || tag === "summary") return "button";
111
+ if (tag === "select") return "dropdown";
112
+ if (tag === "option") return "option";
113
+ if (tag === "textarea") return "field";
114
+ if (tag === "input") {
115
+ const t = inputType(el);
116
+ if (t === "checkbox") return "checkbox";
117
+ if (t === "radio") return "radio";
118
+ if (t === "button" || t === "submit" || t === "reset" || t === "image") return "button";
119
+ return "field";
120
+ }
121
+ if (isEditableHost(el)) return "field";
122
+ return "element";
123
+ }
124
+ var clip = (s, max = 80) => s.length > max ? `${s.slice(0, max - 1).trimEnd()}\u2026` : s;
125
+ function textOf(el) {
126
+ return normalizeText(el.innerText ?? el.textContent);
127
+ }
128
+ function labelledBy(el) {
129
+ const ids = (el.getAttribute("aria-labelledby") || "").split(/\s+/).filter(Boolean);
130
+ return normalizeText(ids.map((id) => el.ownerDocument.getElementById(id)).filter((x) => !!x).map(textOf).join(" "));
131
+ }
132
+ function associatedLabel(el) {
133
+ const labels = el.labels;
134
+ if (labels && labels.length) return normalizeText(Array.from(labels).map(textOf).join(" "));
135
+ if (el.id) {
136
+ try {
137
+ const l = el.ownerDocument.querySelector(`label[for="${el.id.replace(/["\\]/g, "\\$&")}"]`);
138
+ if (l) return textOf(l);
139
+ } catch {
140
+ }
141
+ }
142
+ const wrap = el.parentElement?.closest("label");
143
+ return wrap ? textOf(wrap) : "";
144
+ }
145
+ function captureLabel(el) {
146
+ const attr = (name) => normalizeText(el.getAttribute(name));
147
+ const tag = el.tagName;
148
+ const field = tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || isEditableHost(el);
149
+ const candidates = [
150
+ () => attr("aria-label"),
151
+ () => labelledBy(el),
152
+ () => /^(INPUT|TEXTAREA|SELECT|METER|PROGRESS|OUTPUT)$/.test(tag) ? associatedLabel(el) : "",
153
+ () => tag === "INPUT" && /^(button|submit|reset)$/.test(inputType(el)) ? normalizeText(el.value) : "",
154
+ () => field ? "" : textOf(el),
155
+ () => attr("placeholder"),
156
+ () => attr("alt"),
157
+ () => normalizeText(el.querySelector?.("img[alt]")?.getAttribute("alt")),
158
+ () => attr("title"),
159
+ () => normalizeText(el.querySelector?.("svg title")?.textContent),
160
+ () => attr("name")
161
+ ];
162
+ for (const c of candidates) {
163
+ const v = c();
164
+ if (v) return clip(v);
165
+ }
166
+ return "";
167
+ }
168
+ function describeCapturedElement(el, maskSelector = "") {
169
+ return { label: isMaskedElement(el, maskSelector) ? "" : captureLabel(el), role: captureRole(el), tag: el.tagName.toLowerCase() };
170
+ }
171
+ function fieldValue(el) {
172
+ const raw = el.tagName === "INPUT" || el.tagName === "TEXTAREA" ? el.value : textOf(el);
173
+ return (raw || "").trim().slice(0, 200);
174
+ }
175
+ function selectedOptionText(el) {
176
+ return normalizeText(Array.from(el.selectedOptions ?? []).map((o) => o.text || o.value).join(", ")).slice(0, 200);
177
+ }
178
+ var clamp01 = (n) => Math.min(1, Math.max(0, n));
179
+ var round = (n) => Math.round(n * 1e4) / 1e4;
180
+ function normalizeRect(rect, viewport, pad = 4) {
181
+ if (!viewport.width || !viewport.height || rect.width <= 0 || rect.height <= 0) return null;
182
+ const x0 = clamp01((rect.x - pad) / viewport.width);
183
+ const y0 = clamp01((rect.y - pad) / viewport.height);
184
+ const x1 = clamp01((rect.x + rect.width + pad) / viewport.width);
185
+ const y1 = clamp01((rect.y + rect.height + pad) / viewport.height);
186
+ if (x1 <= x0 || y1 <= y0) return null;
187
+ return { x: round(x0), y: round(y0), width: round(x1 - x0), height: round(y1 - y0) };
188
+ }
189
+ function toFrameRect(rect, scaleX, scaleY, frame, pad = 0) {
190
+ const x0 = Math.max(0, Math.floor((rect.x - pad) * scaleX));
191
+ const y0 = Math.max(0, Math.floor((rect.y - pad) * scaleY));
192
+ const x1 = Math.min(frame.width, Math.ceil((rect.x + rect.width + pad) * scaleX));
193
+ const y1 = Math.min(frame.height, Math.ceil((rect.y + rect.height + pad) * scaleY));
194
+ return x1 > x0 && y1 > y0 ? { x: x0, y: y0, width: x1 - x0, height: y1 - y0 } : null;
195
+ }
196
+ function intersectsViewport(r, vw, vh) {
197
+ return r.width > 0 && r.height > 0 && r.x < vw && r.y < vh && r.x + r.width > 0 && r.y + r.height > 0;
198
+ }
199
+ var SKIP_TEXT_PARENTS = /^(SCRIPT|STYLE|NOSCRIPT|TEMPLATE|TEXTAREA|OPTION)$/;
200
+ function collectRedactions(doc, options = {}) {
201
+ const win = doc.defaultView;
202
+ const vw = win?.innerWidth ?? 0;
203
+ const vh = win?.innerHeight ?? 0;
204
+ const out = [];
205
+ const push = (r) => {
206
+ if (intersectsViewport(r, vw, vh)) out.push({ x: r.x, y: r.y, width: r.width, height: r.height });
207
+ };
208
+ const root = doc.body;
209
+ if (!root) return out;
210
+ const mask = options.maskSelector || "";
211
+ const blocks = /* @__PURE__ */ new Set();
212
+ try {
213
+ root.querySelectorAll(mask ? `${REDACT_SELECTOR},${mask}` : REDACT_SELECTOR).forEach((el) => blocks.add(el));
214
+ } catch {
215
+ root.querySelectorAll(REDACT_SELECTOR).forEach((el) => blocks.add(el));
216
+ }
217
+ root.querySelectorAll("input,textarea,select,[contenteditable]:not([contenteditable=false])").forEach((el) => {
218
+ if (blocks.has(el)) return;
219
+ const t = inputType(el);
220
+ if (t === "hidden" || t === "checkbox" || t === "radio" || t === "button" || t === "submit" || t === "reset") return;
221
+ if (isSensitiveField(el, mask)) blocks.add(el);
222
+ else if ((el.tagName === "INPUT" || el.tagName === "TEXTAREA") && findSensitiveText(el.value || "").length) blocks.add(el);
223
+ });
224
+ blocks.forEach((el) => push(el.getBoundingClientRect()));
225
+ const max = options.maxTextNodes ?? 5e3;
226
+ const walker = doc.createTreeWalker(
227
+ root,
228
+ 4
229
+ /* NodeFilter.SHOW_TEXT */
230
+ );
231
+ let range = null;
232
+ let seen = 0;
233
+ for (let node = walker.nextNode(); node && seen < max; node = walker.nextNode()) {
234
+ const text = node.nodeValue || "";
235
+ if (text.length < 7) continue;
236
+ seen++;
237
+ const parent = node.parentElement;
238
+ if (!parent || SKIP_TEXT_PARENTS.test(parent.tagName)) continue;
239
+ const hits = findSensitiveText(text);
240
+ if (!hits.length) continue;
241
+ if (Array.from(blocks).some((b) => b.contains(parent))) continue;
242
+ range ?? (range = doc.createRange());
243
+ for (const [start, end] of hits) {
244
+ try {
245
+ range.setStart(node, start);
246
+ range.setEnd(node, end);
247
+ Array.from(range.getClientRects()).forEach(push);
248
+ } catch {
249
+ }
250
+ }
251
+ }
252
+ return out;
253
+ }
254
+
255
+ // src/capture-screen.ts
256
+ function canCaptureScreen() {
257
+ return typeof navigator !== "undefined" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === "function";
258
+ }
259
+ async function requestTabCapture(container, doc = document) {
260
+ if (!canCaptureScreen()) return { ok: false, reason: "unsupported" };
261
+ let stream;
262
+ try {
263
+ stream = await navigator.mediaDevices.getDisplayMedia({
264
+ video: { displaySurface: "browser", frameRate: 5 },
265
+ audio: false,
266
+ preferCurrentTab: true,
267
+ selfBrowserSurface: "include",
268
+ surfaceSwitching: "exclude"
269
+ });
270
+ } catch (err) {
271
+ return { ok: false, reason: err?.name === "NotAllowedError" ? "denied" : "error" };
272
+ }
273
+ const track = stream.getVideoTracks()[0];
274
+ if (!track) {
275
+ stream.getTracks().forEach((t) => t.stop());
276
+ return { ok: false, reason: "error" };
277
+ }
278
+ const surface = track.getSettings?.()?.displaySurface;
279
+ if (surface && surface !== "browser") {
280
+ stream.getTracks().forEach((t) => t.stop());
281
+ return { ok: false, reason: "wrong-surface" };
282
+ }
283
+ const video = doc.createElement("video");
284
+ video.muted = true;
285
+ video.playsInline = true;
286
+ video.setAttribute("aria-hidden", "true");
287
+ video.style.cssText = "position:fixed;left:0;top:0;width:2px;height:2px;opacity:0;pointer-events:none";
288
+ try {
289
+ video.srcObject = stream;
290
+ container.appendChild(video);
291
+ } catch {
292
+ stream.getTracks().forEach((t) => t.stop());
293
+ return { ok: false, reason: "error" };
294
+ }
295
+ try {
296
+ await video.play();
297
+ if (!video.videoWidth) await new Promise((resolve) => {
298
+ const done = () => resolve();
299
+ video.addEventListener("loadeddata", done, { once: true });
300
+ setTimeout(done, 1500);
301
+ });
302
+ } catch {
303
+ }
304
+ let ended = false;
305
+ const listeners = [];
306
+ const fv = video;
307
+ const rvfc = typeof fv.requestVideoFrameCallback === "function";
308
+ let frameAt = 0;
309
+ let waiters = [];
310
+ const onFrame = (now) => {
311
+ frameAt = now;
312
+ waiters.splice(0).forEach((w) => w());
313
+ if (!ended) fv.requestVideoFrameCallback(onFrame);
314
+ };
315
+ if (rvfc) fv.requestVideoFrameCallback(onFrame);
316
+ const end = () => {
317
+ if (ended) return;
318
+ ended = true;
319
+ stream.getTracks().forEach((t) => t.stop());
320
+ video.srcObject = null;
321
+ video.remove();
322
+ listeners.splice(0).forEach((cb) => cb());
323
+ };
324
+ track.addEventListener("ended", end);
325
+ return {
326
+ ok: true,
327
+ screen: {
328
+ live: () => !ended && track.readyState === "live",
329
+ onEnded: (cb) => ended ? cb() : listeners.push(cb),
330
+ grab: () => ended ? null : grabFrame(video, doc),
331
+ freshSince: (changedAt) => rvfc ? frameAt > changedAt : performance.now() - changedAt > 250,
332
+ nextFrame: (timeoutMs = 500) => new Promise((resolve) => {
333
+ const t = setTimeout(done, rvfc ? timeoutMs : 250);
334
+ function done() {
335
+ clearTimeout(t);
336
+ waiters = waiters.filter((w) => w !== done);
337
+ resolve();
338
+ }
339
+ if (rvfc) waiters.push(done);
340
+ }),
341
+ stop: () => {
342
+ listeners.length = 0;
343
+ end();
344
+ }
345
+ }
346
+ };
347
+ }
348
+ function grabFrame(video, doc = document) {
349
+ const w = video.videoWidth;
350
+ const h = video.videoHeight;
351
+ if (!w || !h) return null;
352
+ const canvas = doc.createElement("canvas");
353
+ canvas.width = w;
354
+ canvas.height = h;
355
+ const ctx = canvas.getContext("2d");
356
+ if (!ctx) return null;
357
+ ctx.drawImage(video, 0, 0, w, h);
358
+ return canvas;
359
+ }
360
+ function redactCanvas(canvas, rects, viewport) {
361
+ const ctx = canvas.getContext("2d");
362
+ if (!ctx || !rects.length || !viewport.width || !viewport.height) return 0;
363
+ const sx = canvas.width / viewport.width;
364
+ const sy = canvas.height / viewport.height;
365
+ const tmp = canvas.ownerDocument.createElement("canvas");
366
+ const tctx = tmp.getContext("2d");
367
+ if (!tctx) return 0;
368
+ let count = 0;
369
+ for (const rect of rects) {
370
+ const r = toFrameRect(rect, sx, sy, canvas, 4);
371
+ if (!r) continue;
372
+ const block = Math.max(16 * sy, Math.min(r.height, 48 * sy));
373
+ tmp.width = Math.max(1, Math.round(r.width / block));
374
+ tmp.height = Math.max(1, Math.round(r.height / block));
375
+ tctx.imageSmoothingEnabled = true;
376
+ tctx.drawImage(canvas, r.x, r.y, r.width, r.height, 0, 0, tmp.width, tmp.height);
377
+ ctx.imageSmoothingEnabled = true;
378
+ ctx.drawImage(tmp, 0, 0, tmp.width, tmp.height, r.x, r.y, r.width, r.height);
379
+ ctx.fillStyle = "rgba(120,120,120,.28)";
380
+ ctx.fillRect(r.x, r.y, r.width, r.height);
381
+ count++;
382
+ }
383
+ return count;
384
+ }
385
+ function scaled(canvas, maxWidth) {
386
+ if (canvas.width <= maxWidth) return canvas;
387
+ const out = canvas.ownerDocument.createElement("canvas");
388
+ out.width = maxWidth;
389
+ out.height = Math.round(canvas.height * maxWidth / canvas.width);
390
+ const ctx = out.getContext("2d");
391
+ if (!ctx) return canvas;
392
+ ctx.imageSmoothingQuality = "high";
393
+ ctx.drawImage(canvas, 0, 0, out.width, out.height);
394
+ return out;
395
+ }
396
+ var toBlob = (canvas, type, quality) => new Promise((resolve) => {
397
+ try {
398
+ canvas.toBlob((b) => resolve(b), type, quality);
399
+ } catch {
400
+ resolve(null);
401
+ }
402
+ });
403
+ async function encodeScreenshot(canvas, options = {}) {
404
+ const src = scaled(canvas, options.maxWidth ?? 2560);
405
+ const maxBytes = options.maxBytes ?? 8 * 1024 * 1024;
406
+ let blob = await toBlob(src, "image/webp", 0.85);
407
+ if (!blob || blob.type !== "image/webp") blob = await toBlob(src, "image/jpeg", 0.85);
408
+ if (blob && blob.size > maxBytes) blob = await toBlob(src, "image/jpeg", 0.6);
409
+ return blob && blob.size <= maxBytes ? blob : null;
410
+ }
411
+ function screenshotDataUrl(canvas) {
412
+ try {
413
+ return scaled(canvas, 1600).toDataURL("image/jpeg", 0.7);
414
+ } catch {
415
+ return null;
416
+ }
417
+ }
418
+
419
+ export {
420
+ CAPTURE_INTERACTIVE_SELECTOR,
421
+ REDACT_SELECTOR,
422
+ isTextEntry,
423
+ actionTarget,
424
+ labelControl,
425
+ isMaskedElement,
426
+ isSensitiveField,
427
+ classifyNumber,
428
+ findSensitiveText,
429
+ looksLikeSecretValue,
430
+ captureRole,
431
+ captureLabel,
432
+ describeCapturedElement,
433
+ fieldValue,
434
+ selectedOptionText,
435
+ normalizeRect,
436
+ toFrameRect,
437
+ collectRedactions,
438
+ canCaptureScreen,
439
+ requestTabCapture,
440
+ grabFrame,
441
+ redactCanvas,
442
+ encodeScreenshot,
443
+ screenshotDataUrl
444
+ };
445
+ //# sourceMappingURL=chunk-THGKUOHM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/capture-dom.ts","../src/capture-screen.ts"],"sourcesContent":["import type { CapturedElement, NormalizedRect } from \"@guidelytech/shared\";\nimport { normalizeText } from \"./selectors\";\n\n/**\n * Pure DOM helpers for how-to capture: which element an interaction is about, how to name it for\n * people, which fields and on-screen text are sensitive, and where things sit in a screenshot.\n */\n\n/** Elements a click is \"about\" when it lands on them or inside them. */\nexport const CAPTURE_INTERACTIVE_SELECTOR =\n \"a,button,input,select,textarea,label,summary,[role=button],[role=link],[role=tab],[role=menuitem],[role=menuitemcheckbox],[role=menuitemradio],[role=option],[role=checkbox],[role=radio],[role=switch],[role=combobox],[contenteditable]:not([contenteditable=false])\";\n\n/** Always-redacted elements (on top of the project's mask selectors). */\nexport const REDACT_SELECTOR = \"[data-guidely-redact]\";\n\nconst TEXT_INPUT_TYPES = new Set([\"\", \"text\", \"email\", \"search\", \"tel\", \"url\", \"number\", \"password\", \"date\", \"datetime-local\", \"month\", \"week\", \"time\"]);\n\nfunction closestSafe(el: Element, selector: string): Element | null {\n if (!selector) return null;\n try {\n return el.closest(selector);\n } catch {\n return null;\n }\n}\n\nfunction inputType(el: Element): string {\n return el.tagName === \"INPUT\" ? (el.getAttribute(\"type\") || \"\").toLowerCase() : \"\";\n}\n\nfunction isEditableHost(el: Element): boolean {\n const v = el.getAttribute(\"contenteditable\");\n return v != null && v.toLowerCase() !== \"false\";\n}\n\n/** Fields whose typed text becomes a `type` step (instead of a click). */\nexport function isTextEntry(el: Element): boolean {\n if (el.tagName === \"TEXTAREA\") return true;\n if (el.tagName === \"INPUT\") return TEXT_INPUT_TYPES.has(inputType(el));\n return isEditableHost(el) || !!closestSafe(el, \"[contenteditable]:not([contenteditable=false])\");\n}\n\n/** The element a pointer interaction is about: the closest interactive ancestor, else the target itself. */\nexport function actionTarget(target: Element): Element {\n const hit = closestSafe(target, CAPTURE_INTERACTIVE_SELECTOR) ?? target;\n // A contenteditable descendant belongs to its editing host.\n if (!isEditableHost(hit) && isTextEntry(hit) && hit.tagName !== \"INPUT\" && hit.tagName !== \"TEXTAREA\") return closestSafe(hit, \"[contenteditable]:not([contenteditable=false])\") ?? hit;\n return hit;\n}\n\n/** The control a `<label>` is for (clicking the label acts on it), if any. */\nexport function labelControl(el: Element): Element | null {\n if (el.tagName !== \"LABEL\") return null;\n const label = el as HTMLLabelElement;\n if (label.control) return label.control;\n const forId = label.getAttribute(\"for\");\n if (forId) return label.ownerDocument.getElementById(forId);\n return label.querySelector(\"input,select,textarea\");\n}\n\n/** Is the element (or an ancestor) covered by the mask selectors or `[data-guidely-redact]`? */\nexport function isMaskedElement(el: Element, maskSelector: string): boolean {\n return !!closestSafe(el, REDACT_SELECTOR) || !!closestSafe(el, maskSelector);\n}\n\nconst SENSITIVE_NAME = /pass|card|cvv|cvc|ssn|secret|token|otp/i;\nconst SENSITIVE_AUTOCOMPLETE = /cc-|one-time-code|current-password|new-password/i;\n\n/** Fields whose value is never recorded and whose pixels are redacted from screenshots. */\nexport function isSensitiveField(el: Element, maskSelector = \"\"): boolean {\n if (inputType(el) === \"password\") return true;\n if (SENSITIVE_AUTOCOMPLETE.test(el.getAttribute(\"autocomplete\") || \"\")) return true;\n if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (SENSITIVE_NAME.test(el.getAttribute(\"name\") || \"\") || SENSITIVE_NAME.test(el.id || \"\"))) return true;\n return isMaskedElement(el, maskSelector);\n}\n\n// ---------------------------------------------------------------------------\n// Sensitive text patterns\n// ---------------------------------------------------------------------------\n\nconst EMAIL = /[A-Z0-9._%+-]+@[A-Z0-9-]+(?:\\.[A-Z0-9-]+)*\\.[A-Z]{2,}/gi;\n/** A run of digit groups (optionally `+cc` and `(area)`) separated by single spaces, dots or dashes. */\nconst NUMBER_RUN = /(?:\\+\\d{1,3}[ .-]?)?(?:\\(\\d{2,4}\\)[ .-]?)?\\d+(?:[ .-]\\d+)*/g;\n\nexport type SensitiveNumberKind = \"card\" | \"ssn\" | \"phone\";\n\n/**\n * Classifies a number run: card (13–19 digits in groups of 4+), US SSN (3-2-4) or phone\n * (10–13 digits written with a country code, area-code parentheses or 3+ groups ending in 4 digits).\n * Dates, times, prices, versions and IP addresses do not match.\n */\nexport function classifyNumber(run: string): SensitiveNumberKind | null {\n const groups = run.match(/\\d+/g) ?? [];\n const lens = groups.map((g) => g.length);\n const digits = lens.reduce((a, b) => a + b, 0);\n const special = /[+(.]/.test(run);\n if (lens.length === 3 && lens[0] === 3 && lens[1] === 2 && lens[2] === 4 && /^\\d{3}[ -]\\d{2}[ -]\\d{4}$/.test(run)) return \"ssn\";\n if (digits >= 13 && digits <= 19 && !special && lens.every((l) => l >= 4)) return \"card\";\n if (digits >= 10 && digits <= 13 && (/[+(]/.test(run) || (lens.length >= 3 && lens[lens.length - 1] === 4 && lens.every((l) => l >= 2) && !run.includes(\".\")))) return \"phone\";\n return null;\n}\n\n/** Character ranges `[start, end)` of emails, card numbers, SSNs and phone numbers in `text`. */\nexport function findSensitiveText(text: string): Array<[number, number]> {\n const out: Array<[number, number]> = [];\n if (text.length < 7) return out;\n if (text.includes(\"@\")) {\n EMAIL.lastIndex = 0;\n for (let m = EMAIL.exec(text); m && out.length < 200; m = EMAIL.exec(text)) out.push([m.index, m.index + m[0].length]);\n }\n if (/\\d{3}/.test(text)) {\n NUMBER_RUN.lastIndex = 0;\n for (let m = NUMBER_RUN.exec(text); m && out.length < 200; m = NUMBER_RUN.exec(text)) {\n if (m[0].length >= 9 && classifyNumber(m[0])) out.push([m.index, m.index + m[0].length]);\n }\n }\n return out;\n}\n\n/** Typed values are recorded verbatim unless they contain something like a card number or SSN. */\nexport function looksLikeSecretValue(value: string): boolean {\n NUMBER_RUN.lastIndex = 0;\n for (let m = NUMBER_RUN.exec(value); m; m = NUMBER_RUN.exec(value)) {\n const kind = classifyNumber(m[0]);\n if (kind === \"card\" || kind === \"ssn\") return true;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Element description\n// ---------------------------------------------------------------------------\n\nconst ROLE_MAP: Record<string, string> = {\n button: \"button\",\n link: \"link\",\n tab: \"tab\",\n menuitem: \"menuitem\",\n menuitemcheckbox: \"menuitem\",\n menuitemradio: \"menuitem\",\n checkbox: \"checkbox\",\n radio: \"radio\",\n switch: \"switch\",\n option: \"option\",\n combobox: \"dropdown\",\n listbox: \"dropdown\",\n textbox: \"field\",\n searchbox: \"field\",\n spinbutton: \"field\",\n};\n\n/** Normalised role: button, link, tab, menuitem, checkbox, radio, switch, field, dropdown, option or element. */\nexport function captureRole(el: Element): string {\n const explicit = (el.getAttribute(\"role\") || \"\").trim().split(/\\s+/)[0]!.toLowerCase();\n if (ROLE_MAP[explicit]) return ROLE_MAP[explicit]!;\n const tag = el.tagName.toLowerCase();\n if (tag === \"a\") return el.hasAttribute(\"href\") ? \"link\" : \"element\";\n if (tag === \"button\" || tag === \"summary\") return \"button\";\n if (tag === \"select\") return \"dropdown\";\n if (tag === \"option\") return \"option\";\n if (tag === \"textarea\") return \"field\";\n if (tag === \"input\") {\n const t = inputType(el);\n if (t === \"checkbox\") return \"checkbox\";\n if (t === \"radio\") return \"radio\";\n if (t === \"button\" || t === \"submit\" || t === \"reset\" || t === \"image\") return \"button\";\n return \"field\";\n }\n if (isEditableHost(el)) return \"field\";\n return \"element\";\n}\n\nconst clip = (s: string, max = 80) => (s.length > max ? `${s.slice(0, max - 1).trimEnd()}…` : s);\n\nfunction textOf(el: Element): string {\n return normalizeText((el as HTMLElement).innerText ?? el.textContent);\n}\n\nfunction labelledBy(el: Element): string {\n const ids = (el.getAttribute(\"aria-labelledby\") || \"\").split(/\\s+/).filter(Boolean);\n return normalizeText(ids.map((id) => el.ownerDocument.getElementById(id)).filter((x): x is HTMLElement => !!x).map(textOf).join(\" \"));\n}\n\nfunction associatedLabel(el: Element): string {\n const labels = (el as HTMLInputElement).labels;\n if (labels && labels.length) return normalizeText(Array.from(labels).map(textOf).join(\" \"));\n if (el.id) {\n try {\n const l = el.ownerDocument.querySelector(`label[for=\"${el.id.replace(/[\"\\\\]/g, \"\\\\$&\")}\"]`);\n if (l) return textOf(l);\n } catch {\n /* ignore */\n }\n }\n const wrap = el.parentElement?.closest(\"label\");\n return wrap ? textOf(wrap) : \"\";\n}\n\n/**\n * Visible name of an element for people: aria-label, aria-labelledby, its `<label>`, its text\n * (not for fields, whose value is user data), placeholder, alt, title or name. Max 80 characters.\n */\nexport function captureLabel(el: Element): string {\n const attr = (name: string) => normalizeText(el.getAttribute(name));\n const tag = el.tagName;\n const field = tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\" || isEditableHost(el);\n const candidates: Array<() => string> = [\n () => attr(\"aria-label\"),\n () => labelledBy(el),\n () => (/^(INPUT|TEXTAREA|SELECT|METER|PROGRESS|OUTPUT)$/.test(tag) ? associatedLabel(el) : \"\"),\n () => (tag === \"INPUT\" && /^(button|submit|reset)$/.test(inputType(el)) ? normalizeText((el as HTMLInputElement).value) : \"\"),\n () => (field ? \"\" : textOf(el)),\n () => attr(\"placeholder\"),\n () => attr(\"alt\"),\n () => normalizeText(el.querySelector?.(\"img[alt]\")?.getAttribute(\"alt\")),\n () => attr(\"title\"),\n () => normalizeText(el.querySelector?.(\"svg title\")?.textContent),\n () => attr(\"name\"),\n ];\n for (const c of candidates) {\n const v = c();\n if (v) return clip(v);\n }\n return \"\";\n}\n\n/** People-facing description of the element; masked elements get no label (never their text). */\nexport function describeCapturedElement(el: Element, maskSelector = \"\"): CapturedElement {\n return { label: isMaskedElement(el, maskSelector) ? \"\" : captureLabel(el), role: captureRole(el), tag: el.tagName.toLowerCase() };\n}\n\n/** The value a `type` step records: trimmed, at most 200 characters. */\nexport function fieldValue(el: Element): string {\n const raw = el.tagName === \"INPUT\" || el.tagName === \"TEXTAREA\" ? (el as HTMLInputElement).value : textOf(el);\n return (raw || \"\").trim().slice(0, 200);\n}\n\n/** Text of the selected option(s) of a `<select>`. */\nexport function selectedOptionText(el: HTMLSelectElement): string {\n return normalizeText(Array.from(el.selectedOptions ?? []).map((o) => o.text || o.value).join(\", \")).slice(0, 200);\n}\n\n// ---------------------------------------------------------------------------\n// Geometry\n// ---------------------------------------------------------------------------\n\nexport interface Box {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nconst clamp01 = (n: number) => Math.min(1, Math.max(0, n));\nconst round = (n: number) => Math.round(n * 10000) / 10000;\n\n/**\n * Maps a viewport rect (CSS px) to fractions of a screenshot of that viewport, padded by `pad`\n * CSS px and clamped to the frame. Returns null when nothing of it is on screen.\n */\nexport function normalizeRect(rect: Box, viewport: { width: number; height: number }, pad = 4): NormalizedRect | null {\n if (!viewport.width || !viewport.height || rect.width <= 0 || rect.height <= 0) return null;\n const x0 = clamp01((rect.x - pad) / viewport.width);\n const y0 = clamp01((rect.y - pad) / viewport.height);\n const x1 = clamp01((rect.x + rect.width + pad) / viewport.width);\n const y1 = clamp01((rect.y + rect.height + pad) / viewport.height);\n if (x1 <= x0 || y1 <= y0) return null;\n return { x: round(x0), y: round(y0), width: round(x1 - x0), height: round(y1 - y0) };\n}\n\n/** Scales a viewport rect (CSS px) to frame pixels, clipped to the frame. */\nexport function toFrameRect(rect: Box, scaleX: number, scaleY: number, frame: { width: number; height: number }, pad = 0): Box | null {\n const x0 = Math.max(0, Math.floor((rect.x - pad) * scaleX));\n const y0 = Math.max(0, Math.floor((rect.y - pad) * scaleY));\n const x1 = Math.min(frame.width, Math.ceil((rect.x + rect.width + pad) * scaleX));\n const y1 = Math.min(frame.height, Math.ceil((rect.y + rect.height + pad) * scaleY));\n return x1 > x0 && y1 > y0 ? { x: x0, y: y0, width: x1 - x0, height: y1 - y0 } : null;\n}\n\nfunction intersectsViewport(r: Box, vw: number, vh: number): boolean {\n return r.width > 0 && r.height > 0 && r.x < vw && r.y < vh && r.x + r.width > 0 && r.y + r.height > 0;\n}\n\nconst SKIP_TEXT_PARENTS = /^(SCRIPT|STYLE|NOSCRIPT|TEMPLATE|TEXTAREA|OPTION)$/;\n\nexport interface RedactionOptions {\n maskSelector?: string;\n /** Stop after this many text nodes (keeps the synchronous scan cheap on huge pages). */\n maxTextNodes?: number;\n}\n\n/**\n * Viewport rects (CSS px) to blur in a screenshot: password and other sensitive fields, masked and\n * `[data-guidely-redact]` elements, field values and visible text that look like emails, card\n * numbers, SSNs or phone numbers. Runs synchronously so it matches the frame grabbed with it.\n */\nexport function collectRedactions(doc: Document, options: RedactionOptions = {}): Box[] {\n const win = doc.defaultView;\n const vw = win?.innerWidth ?? 0;\n const vh = win?.innerHeight ?? 0;\n const out: Box[] = [];\n const push = (r: Box) => {\n if (intersectsViewport(r, vw, vh)) out.push({ x: r.x, y: r.y, width: r.width, height: r.height });\n };\n const root = doc.body;\n if (!root) return out;\n const mask = options.maskSelector || \"\";\n const blocks = new Set<Element>();\n try {\n root.querySelectorAll(mask ? `${REDACT_SELECTOR},${mask}` : REDACT_SELECTOR).forEach((el) => blocks.add(el));\n } catch {\n root.querySelectorAll(REDACT_SELECTOR).forEach((el) => blocks.add(el));\n }\n root.querySelectorAll(\"input,textarea,select,[contenteditable]:not([contenteditable=false])\").forEach((el) => {\n if (blocks.has(el)) return;\n const t = inputType(el);\n if (t === \"hidden\" || t === \"checkbox\" || t === \"radio\" || t === \"button\" || t === \"submit\" || t === \"reset\") return;\n if (isSensitiveField(el, mask)) blocks.add(el);\n else if ((el.tagName === \"INPUT\" || el.tagName === \"TEXTAREA\") && findSensitiveText((el as HTMLInputElement).value || \"\").length) blocks.add(el);\n });\n blocks.forEach((el) => push(el.getBoundingClientRect()));\n\n const max = options.maxTextNodes ?? 5000;\n const walker = doc.createTreeWalker(root, 4 /* NodeFilter.SHOW_TEXT */);\n let range: Range | null = null;\n let seen = 0;\n for (let node = walker.nextNode(); node && seen < max; node = walker.nextNode()) {\n const text = node.nodeValue || \"\";\n if (text.length < 7) continue;\n seen++;\n const parent = node.parentElement;\n if (!parent || SKIP_TEXT_PARENTS.test(parent.tagName)) continue;\n const hits = findSensitiveText(text);\n if (!hits.length) continue;\n // Text inside an element that is already blurred as a whole needs no second pass.\n if (Array.from(blocks).some((b) => b.contains(parent))) continue;\n range ??= doc.createRange();\n for (const [start, end] of hits) {\n try {\n range.setStart(node, start);\n range.setEnd(node, end);\n Array.from(range.getClientRects()).forEach(push);\n } catch {\n /* node changed under us */\n }\n }\n }\n return out;\n}\n","import { toFrameRect, type Box } from \"./capture-dom\";\n\n/**\n * Screenshots for how-to capture: a tab-capture stream (`getDisplayMedia`) played into a hidden\n * video, frames grabbed synchronously into a canvas, sensitive regions blurred into the pixels and\n * the result encoded — all in the browser, so raw pixels of redacted regions never leave it.\n */\n\nexport type ScreenResult = { ok: true; screen: ScreenSource } | { ok: false; reason: \"unsupported\" | \"denied\" | \"wrong-surface\" | \"error\" };\n\nexport interface ScreenSource {\n /** True while the stream delivers frames. */\n live(): boolean;\n /** Called once when the user stops sharing (or the stream ends otherwise). */\n onEnded(cb: () => void): void;\n /** Draws the current frame into a new canvas (synchronously). Null when no frame is available. */\n grab(): HTMLCanvasElement | null;\n /** Does the current frame show the page as it is after `changedAt` (a `performance.now()` time)? */\n freshSince(changedAt: number): boolean;\n /** Resolves when the next frame arrives (or after `timeoutMs`). */\n nextFrame(timeoutMs?: number): Promise<void>;\n stop(): void;\n}\n\n/** Can this browser capture the tab at all? */\nexport function canCaptureScreen(): boolean {\n return typeof navigator !== \"undefined\" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === \"function\";\n}\n\n/**\n * Asks the user to share this tab (must run from a user gesture) and plays the stream into a\n * hidden `<video>` appended to `container`. Rejects windows and screens: their frames do not\n * match the page's viewport, so highlights and redactions would land in the wrong place.\n */\nexport async function requestTabCapture(container: Node, doc: Document = document): Promise<ScreenResult> {\n if (!canCaptureScreen()) return { ok: false, reason: \"unsupported\" };\n let stream: MediaStream;\n try {\n stream = await navigator.mediaDevices.getDisplayMedia({\n video: { displaySurface: \"browser\", frameRate: 5 },\n audio: false,\n preferCurrentTab: true,\n selfBrowserSurface: \"include\",\n surfaceSwitching: \"exclude\",\n } as DisplayMediaStreamOptions);\n } catch (err) {\n return { ok: false, reason: (err as { name?: string })?.name === \"NotAllowedError\" ? \"denied\" : \"error\" };\n }\n const track = stream.getVideoTracks()[0];\n if (!track) {\n stream.getTracks().forEach((t) => t.stop());\n return { ok: false, reason: \"error\" };\n }\n const surface = (track.getSettings?.() as { displaySurface?: string } | undefined)?.displaySurface;\n if (surface && surface !== \"browser\") {\n stream.getTracks().forEach((t) => t.stop());\n return { ok: false, reason: \"wrong-surface\" };\n }\n const video = doc.createElement(\"video\");\n video.muted = true;\n video.playsInline = true;\n video.setAttribute(\"aria-hidden\", \"true\");\n // Kept rendered (not display:none) so browsers keep decoding frames.\n video.style.cssText = \"position:fixed;left:0;top:0;width:2px;height:2px;opacity:0;pointer-events:none\";\n try {\n video.srcObject = stream;\n container.appendChild(video);\n } catch {\n stream.getTracks().forEach((t) => t.stop());\n return { ok: false, reason: \"error\" };\n }\n try {\n await video.play();\n if (!video.videoWidth) await new Promise<void>((resolve) => {\n const done = () => resolve();\n video.addEventListener(\"loadeddata\", done, { once: true });\n setTimeout(done, 1500);\n });\n } catch {\n /* frames may still arrive; grab() returns null until they do */\n }\n let ended = false;\n const listeners: Array<() => void> = [];\n // Frame timing: `requestVideoFrameCallback` where supported, else assume one frame period (5 fps) of lag.\n type FrameVideo = HTMLVideoElement & { requestVideoFrameCallback?: (cb: (now: number) => void) => number };\n const fv = video as FrameVideo;\n const rvfc = typeof fv.requestVideoFrameCallback === \"function\";\n let frameAt = 0;\n let waiters: Array<() => void> = [];\n const onFrame = (now: number) => {\n frameAt = now;\n waiters.splice(0).forEach((w) => w());\n if (!ended) fv.requestVideoFrameCallback!(onFrame);\n };\n if (rvfc) fv.requestVideoFrameCallback!(onFrame);\n const end = () => {\n if (ended) return;\n ended = true;\n stream.getTracks().forEach((t) => t.stop());\n video.srcObject = null;\n video.remove();\n listeners.splice(0).forEach((cb) => cb());\n };\n track.addEventListener(\"ended\", end);\n return {\n ok: true,\n screen: {\n live: () => !ended && track.readyState === \"live\",\n onEnded: (cb) => (ended ? cb() : listeners.push(cb)),\n grab: () => (ended ? null : grabFrame(video, doc)),\n freshSince: (changedAt) => (rvfc ? frameAt > changedAt : performance.now() - changedAt > 250),\n nextFrame: (timeoutMs = 500) =>\n new Promise<void>((resolve) => {\n const t = setTimeout(done, rvfc ? timeoutMs : 250);\n function done() {\n clearTimeout(t);\n waiters = waiters.filter((w) => w !== done);\n resolve();\n }\n if (rvfc) waiters.push(done);\n }),\n stop: () => {\n listeners.length = 0;\n end();\n },\n },\n };\n}\n\n/** Copies the video's current frame into a new canvas. */\nexport function grabFrame(video: HTMLVideoElement, doc: Document = document): HTMLCanvasElement | null {\n const w = video.videoWidth;\n const h = video.videoHeight;\n if (!w || !h) return null;\n const canvas = doc.createElement(\"canvas\");\n canvas.width = w;\n canvas.height = h;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return null;\n ctx.drawImage(video, 0, 0, w, h);\n return canvas;\n}\n\n/**\n * Blurs viewport rects (CSS px) into the canvas by averaging each region down to a few samples per\n * line of text and scaling it back up, which leaves nothing legible to recover. Returns the number\n * of regions blurred.\n */\nexport function redactCanvas(canvas: HTMLCanvasElement, rects: Box[], viewport: { width: number; height: number }): number {\n const ctx = canvas.getContext(\"2d\");\n if (!ctx || !rects.length || !viewport.width || !viewport.height) return 0;\n const sx = canvas.width / viewport.width;\n const sy = canvas.height / viewport.height;\n const tmp = canvas.ownerDocument.createElement(\"canvas\");\n const tctx = tmp.getContext(\"2d\");\n if (!tctx) return 0;\n let count = 0;\n for (const rect of rects) {\n const r = toFrameRect(rect, sx, sy, canvas, 4);\n if (!r) continue;\n // One sample per ~line height (at least 16 CSS px) in both directions.\n const block = Math.max(16 * sy, Math.min(r.height, 48 * sy));\n tmp.width = Math.max(1, Math.round(r.width / block));\n tmp.height = Math.max(1, Math.round(r.height / block));\n tctx.imageSmoothingEnabled = true;\n tctx.drawImage(canvas, r.x, r.y, r.width, r.height, 0, 0, tmp.width, tmp.height);\n ctx.imageSmoothingEnabled = true;\n ctx.drawImage(tmp, 0, 0, tmp.width, tmp.height, r.x, r.y, r.width, r.height);\n // A light tint so readers can tell the region was hidden on purpose.\n ctx.fillStyle = \"rgba(120,120,120,.28)\";\n ctx.fillRect(r.x, r.y, r.width, r.height);\n count++;\n }\n return count;\n}\n\nfunction scaled(canvas: HTMLCanvasElement, maxWidth: number): HTMLCanvasElement {\n if (canvas.width <= maxWidth) return canvas;\n const out = canvas.ownerDocument.createElement(\"canvas\");\n out.width = maxWidth;\n out.height = Math.round((canvas.height * maxWidth) / canvas.width);\n const ctx = out.getContext(\"2d\");\n if (!ctx) return canvas;\n ctx.imageSmoothingQuality = \"high\";\n ctx.drawImage(canvas, 0, 0, out.width, out.height);\n return out;\n}\n\nconst toBlob = (canvas: HTMLCanvasElement, type: string, quality: number) =>\n new Promise<Blob | null>((resolve) => {\n try {\n canvas.toBlob((b) => resolve(b), type, quality);\n } catch {\n resolve(null);\n }\n });\n\n/** Encodes as WebP (JPEG where WebP encoding is unsupported), at most `maxWidth` wide and `maxBytes` big. */\nexport async function encodeScreenshot(canvas: HTMLCanvasElement, options: { maxWidth?: number; maxBytes?: number } = {}): Promise<Blob | null> {\n const src = scaled(canvas, options.maxWidth ?? 2560);\n const maxBytes = options.maxBytes ?? 8 * 1024 * 1024;\n let blob = await toBlob(src, \"image/webp\", 0.85);\n // Browsers without a WebP encoder silently return PNG.\n if (!blob || blob.type !== \"image/webp\") blob = await toBlob(src, \"image/jpeg\", 0.85);\n if (blob && blob.size > maxBytes) blob = await toBlob(src, \"image/jpeg\", 0.6);\n return blob && blob.size <= maxBytes ? blob : null;\n}\n\n/** Synchronous JPEG data URL (≤ 1600 px wide), for persisting an unsent screenshot while the page unloads. */\nexport function screenshotDataUrl(canvas: HTMLCanvasElement): string | null {\n try {\n return scaled(canvas, 1600).toDataURL(\"image/jpeg\", 0.7);\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;AASO,IAAM,+BACX;AAGK,IAAM,kBAAkB;AAE/B,IAAM,mBAAmB,oBAAI,IAAI,CAAC,IAAI,QAAQ,SAAS,UAAU,OAAO,OAAO,UAAU,YAAY,QAAQ,kBAAkB,SAAS,QAAQ,MAAM,CAAC;AAEvJ,SAAS,YAAY,IAAa,UAAkC;AAClE,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI;AACF,WAAO,GAAG,QAAQ,QAAQ;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,UAAU,IAAqB;AACtC,SAAO,GAAG,YAAY,WAAW,GAAG,aAAa,MAAM,KAAK,IAAI,YAAY,IAAI;AAClF;AAEA,SAAS,eAAe,IAAsB;AAC5C,QAAM,IAAI,GAAG,aAAa,iBAAiB;AAC3C,SAAO,KAAK,QAAQ,EAAE,YAAY,MAAM;AAC1C;AAGO,SAAS,YAAY,IAAsB;AAChD,MAAI,GAAG,YAAY,WAAY,QAAO;AACtC,MAAI,GAAG,YAAY,QAAS,QAAO,iBAAiB,IAAI,UAAU,EAAE,CAAC;AACrE,SAAO,eAAe,EAAE,KAAK,CAAC,CAAC,YAAY,IAAI,gDAAgD;AACjG;AAGO,SAAS,aAAa,QAA0B;AACrD,QAAM,MAAM,YAAY,QAAQ,4BAA4B,KAAK;AAEjE,MAAI,CAAC,eAAe,GAAG,KAAK,YAAY,GAAG,KAAK,IAAI,YAAY,WAAW,IAAI,YAAY,WAAY,QAAO,YAAY,KAAK,gDAAgD,KAAK;AACpL,SAAO;AACT;AAGO,SAAS,aAAa,IAA6B;AACxD,MAAI,GAAG,YAAY,QAAS,QAAO;AACnC,QAAM,QAAQ;AACd,MAAI,MAAM,QAAS,QAAO,MAAM;AAChC,QAAM,QAAQ,MAAM,aAAa,KAAK;AACtC,MAAI,MAAO,QAAO,MAAM,cAAc,eAAe,KAAK;AAC1D,SAAO,MAAM,cAAc,uBAAuB;AACpD;AAGO,SAAS,gBAAgB,IAAa,cAA+B;AAC1E,SAAO,CAAC,CAAC,YAAY,IAAI,eAAe,KAAK,CAAC,CAAC,YAAY,IAAI,YAAY;AAC7E;AAEA,IAAM,iBAAiB;AACvB,IAAM,yBAAyB;AAGxB,SAAS,iBAAiB,IAAa,eAAe,IAAa;AACxE,MAAI,UAAU,EAAE,MAAM,WAAY,QAAO;AACzC,MAAI,uBAAuB,KAAK,GAAG,aAAa,cAAc,KAAK,EAAE,EAAG,QAAO;AAC/E,MAAI,4BAA4B,KAAK,GAAG,OAAO,MAAM,eAAe,KAAK,GAAG,aAAa,MAAM,KAAK,EAAE,KAAK,eAAe,KAAK,GAAG,MAAM,EAAE,GAAI,QAAO;AACrJ,SAAO,gBAAgB,IAAI,YAAY;AACzC;AAMA,IAAM,QAAQ;AAEd,IAAM,aAAa;AASZ,SAAS,eAAe,KAAyC;AACtE,QAAM,SAAS,IAAI,MAAM,MAAM,KAAK,CAAC;AACrC,QAAM,OAAO,OAAO,IAAI,CAAC,MAAM,EAAE,MAAM;AACvC,QAAM,SAAS,KAAK,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAC7C,QAAM,UAAU,QAAQ,KAAK,GAAG;AAChC,MAAI,KAAK,WAAW,KAAK,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,KAAK,4BAA4B,KAAK,GAAG,EAAG,QAAO;AAC1H,MAAI,UAAU,MAAM,UAAU,MAAM,CAAC,WAAW,KAAK,MAAM,CAAC,MAAM,KAAK,CAAC,EAAG,QAAO;AAClF,MAAI,UAAU,MAAM,UAAU,OAAO,OAAO,KAAK,GAAG,KAAM,KAAK,UAAU,KAAK,KAAK,KAAK,SAAS,CAAC,MAAM,KAAK,KAAK,MAAM,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,IAAI,SAAS,GAAG,GAAK,QAAO;AACvK,SAAO;AACT;AAGO,SAAS,kBAAkB,MAAuC;AACvE,QAAM,MAA+B,CAAC;AACtC,MAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,MAAI,KAAK,SAAS,GAAG,GAAG;AACtB,UAAM,YAAY;AAClB,aAAS,IAAI,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,SAAS,KAAK,IAAI,MAAM,KAAK,IAAI,EAAG,KAAI,KAAK,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,CAAC;AAAA,EACvH;AACA,MAAI,QAAQ,KAAK,IAAI,GAAG;AACtB,eAAW,YAAY;AACvB,aAAS,IAAI,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,SAAS,KAAK,IAAI,WAAW,KAAK,IAAI,GAAG;AACpF,UAAI,EAAE,CAAC,EAAE,UAAU,KAAK,eAAe,EAAE,CAAC,CAAC,EAAG,KAAI,KAAK,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,CAAC;AAAA,IACzF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,qBAAqB,OAAwB;AAC3D,aAAW,YAAY;AACvB,WAAS,IAAI,WAAW,KAAK,KAAK,GAAG,GAAG,IAAI,WAAW,KAAK,KAAK,GAAG;AAClE,UAAM,OAAO,eAAe,EAAE,CAAC,CAAC;AAChC,QAAI,SAAS,UAAU,SAAS,MAAO,QAAO;AAAA,EAChD;AACA,SAAO;AACT;AAMA,IAAM,WAAmC;AAAA,EACvC,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,KAAK;AAAA,EACL,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,UAAU;AAAA,EACV,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,YAAY;AACd;AAGO,SAAS,YAAY,IAAqB;AAC/C,QAAM,YAAY,GAAG,aAAa,MAAM,KAAK,IAAI,KAAK,EAAE,MAAM,KAAK,EAAE,CAAC,EAAG,YAAY;AACrF,MAAI,SAAS,QAAQ,EAAG,QAAO,SAAS,QAAQ;AAChD,QAAM,MAAM,GAAG,QAAQ,YAAY;AACnC,MAAI,QAAQ,IAAK,QAAO,GAAG,aAAa,MAAM,IAAI,SAAS;AAC3D,MAAI,QAAQ,YAAY,QAAQ,UAAW,QAAO;AAClD,MAAI,QAAQ,SAAU,QAAO;AAC7B,MAAI,QAAQ,SAAU,QAAO;AAC7B,MAAI,QAAQ,WAAY,QAAO;AAC/B,MAAI,QAAQ,SAAS;AACnB,UAAM,IAAI,UAAU,EAAE;AACtB,QAAI,MAAM,WAAY,QAAO;AAC7B,QAAI,MAAM,QAAS,QAAO;AAC1B,QAAI,MAAM,YAAY,MAAM,YAAY,MAAM,WAAW,MAAM,QAAS,QAAO;AAC/E,WAAO;AAAA,EACT;AACA,MAAI,eAAe,EAAE,EAAG,QAAO;AAC/B,SAAO;AACT;AAEA,IAAM,OAAO,CAAC,GAAW,MAAM,OAAQ,EAAE,SAAS,MAAM,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAM;AAE9F,SAAS,OAAO,IAAqB;AACnC,SAAO,cAAe,GAAmB,aAAa,GAAG,WAAW;AACtE;AAEA,SAAS,WAAW,IAAqB;AACvC,QAAM,OAAO,GAAG,aAAa,iBAAiB,KAAK,IAAI,MAAM,KAAK,EAAE,OAAO,OAAO;AAClF,SAAO,cAAc,IAAI,IAAI,CAAC,OAAO,GAAG,cAAc,eAAe,EAAE,CAAC,EAAE,OAAO,CAAC,MAAwB,CAAC,CAAC,CAAC,EAAE,IAAI,MAAM,EAAE,KAAK,GAAG,CAAC;AACtI;AAEA,SAAS,gBAAgB,IAAqB;AAC5C,QAAM,SAAU,GAAwB;AACxC,MAAI,UAAU,OAAO,OAAQ,QAAO,cAAc,MAAM,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,KAAK,GAAG,CAAC;AAC1F,MAAI,GAAG,IAAI;AACT,QAAI;AACF,YAAM,IAAI,GAAG,cAAc,cAAc,cAAc,GAAG,GAAG,QAAQ,UAAU,MAAM,CAAC,IAAI;AAC1F,UAAI,EAAG,QAAO,OAAO,CAAC;AAAA,IACxB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,QAAM,OAAO,GAAG,eAAe,QAAQ,OAAO;AAC9C,SAAO,OAAO,OAAO,IAAI,IAAI;AAC/B;AAMO,SAAS,aAAa,IAAqB;AAChD,QAAM,OAAO,CAAC,SAAiB,cAAc,GAAG,aAAa,IAAI,CAAC;AAClE,QAAM,MAAM,GAAG;AACf,QAAM,QAAQ,QAAQ,WAAW,QAAQ,cAAc,QAAQ,YAAY,eAAe,EAAE;AAC5F,QAAM,aAAkC;AAAA,IACtC,MAAM,KAAK,YAAY;AAAA,IACvB,MAAM,WAAW,EAAE;AAAA,IACnB,MAAO,kDAAkD,KAAK,GAAG,IAAI,gBAAgB,EAAE,IAAI;AAAA,IAC3F,MAAO,QAAQ,WAAW,0BAA0B,KAAK,UAAU,EAAE,CAAC,IAAI,cAAe,GAAwB,KAAK,IAAI;AAAA,IAC1H,MAAO,QAAQ,KAAK,OAAO,EAAE;AAAA,IAC7B,MAAM,KAAK,aAAa;AAAA,IACxB,MAAM,KAAK,KAAK;AAAA,IAChB,MAAM,cAAc,GAAG,gBAAgB,UAAU,GAAG,aAAa,KAAK,CAAC;AAAA,IACvE,MAAM,KAAK,OAAO;AAAA,IAClB,MAAM,cAAc,GAAG,gBAAgB,WAAW,GAAG,WAAW;AAAA,IAChE,MAAM,KAAK,MAAM;AAAA,EACnB;AACA,aAAW,KAAK,YAAY;AAC1B,UAAM,IAAI,EAAE;AACZ,QAAI,EAAG,QAAO,KAAK,CAAC;AAAA,EACtB;AACA,SAAO;AACT;AAGO,SAAS,wBAAwB,IAAa,eAAe,IAAqB;AACvF,SAAO,EAAE,OAAO,gBAAgB,IAAI,YAAY,IAAI,KAAK,aAAa,EAAE,GAAG,MAAM,YAAY,EAAE,GAAG,KAAK,GAAG,QAAQ,YAAY,EAAE;AAClI;AAGO,SAAS,WAAW,IAAqB;AAC9C,QAAM,MAAM,GAAG,YAAY,WAAW,GAAG,YAAY,aAAc,GAAwB,QAAQ,OAAO,EAAE;AAC5G,UAAQ,OAAO,IAAI,KAAK,EAAE,MAAM,GAAG,GAAG;AACxC;AAGO,SAAS,mBAAmB,IAA+B;AAChE,SAAO,cAAc,MAAM,KAAK,GAAG,mBAAmB,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC,EAAE,MAAM,GAAG,GAAG;AAClH;AAaA,IAAM,UAAU,CAAC,MAAc,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC;AACzD,IAAM,QAAQ,CAAC,MAAc,KAAK,MAAM,IAAI,GAAK,IAAI;AAM9C,SAAS,cAAc,MAAW,UAA6C,MAAM,GAA0B;AACpH,MAAI,CAAC,SAAS,SAAS,CAAC,SAAS,UAAU,KAAK,SAAS,KAAK,KAAK,UAAU,EAAG,QAAO;AACvF,QAAM,KAAK,SAAS,KAAK,IAAI,OAAO,SAAS,KAAK;AAClD,QAAM,KAAK,SAAS,KAAK,IAAI,OAAO,SAAS,MAAM;AACnD,QAAM,KAAK,SAAS,KAAK,IAAI,KAAK,QAAQ,OAAO,SAAS,KAAK;AAC/D,QAAM,KAAK,SAAS,KAAK,IAAI,KAAK,SAAS,OAAO,SAAS,MAAM;AACjE,MAAI,MAAM,MAAM,MAAM,GAAI,QAAO;AACjC,SAAO,EAAE,GAAG,MAAM,EAAE,GAAG,GAAG,MAAM,EAAE,GAAG,OAAO,MAAM,KAAK,EAAE,GAAG,QAAQ,MAAM,KAAK,EAAE,EAAE;AACrF;AAGO,SAAS,YAAY,MAAW,QAAgB,QAAgB,OAA0C,MAAM,GAAe;AACpI,QAAM,KAAK,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,IAAI,OAAO,MAAM,CAAC;AAC1D,QAAM,KAAK,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,IAAI,OAAO,MAAM,CAAC;AAC1D,QAAM,KAAK,KAAK,IAAI,MAAM,OAAO,KAAK,MAAM,KAAK,IAAI,KAAK,QAAQ,OAAO,MAAM,CAAC;AAChF,QAAM,KAAK,KAAK,IAAI,MAAM,QAAQ,KAAK,MAAM,KAAK,IAAI,KAAK,SAAS,OAAO,MAAM,CAAC;AAClF,SAAO,KAAK,MAAM,KAAK,KAAK,EAAE,GAAG,IAAI,GAAG,IAAI,OAAO,KAAK,IAAI,QAAQ,KAAK,GAAG,IAAI;AAClF;AAEA,SAAS,mBAAmB,GAAQ,IAAY,IAAqB;AACnE,SAAO,EAAE,QAAQ,KAAK,EAAE,SAAS,KAAK,EAAE,IAAI,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,EAAE,QAAQ,KAAK,EAAE,IAAI,EAAE,SAAS;AACtG;AAEA,IAAM,oBAAoB;AAanB,SAAS,kBAAkB,KAAe,UAA4B,CAAC,GAAU;AACtF,QAAM,MAAM,IAAI;AAChB,QAAM,KAAK,KAAK,cAAc;AAC9B,QAAM,KAAK,KAAK,eAAe;AAC/B,QAAM,MAAa,CAAC;AACpB,QAAM,OAAO,CAAC,MAAW;AACvB,QAAI,mBAAmB,GAAG,IAAI,EAAE,EAAG,KAAI,KAAK,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,OAAO,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;AAAA,EAClG;AACA,QAAM,OAAO,IAAI;AACjB,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,OAAO,QAAQ,gBAAgB;AACrC,QAAM,SAAS,oBAAI,IAAa;AAChC,MAAI;AACF,SAAK,iBAAiB,OAAO,GAAG,eAAe,IAAI,IAAI,KAAK,eAAe,EAAE,QAAQ,CAAC,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,EAC7G,QAAQ;AACN,SAAK,iBAAiB,eAAe,EAAE,QAAQ,CAAC,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,EACvE;AACA,OAAK,iBAAiB,sEAAsE,EAAE,QAAQ,CAAC,OAAO;AAC5G,QAAI,OAAO,IAAI,EAAE,EAAG;AACpB,UAAM,IAAI,UAAU,EAAE;AACtB,QAAI,MAAM,YAAY,MAAM,cAAc,MAAM,WAAW,MAAM,YAAY,MAAM,YAAY,MAAM,QAAS;AAC9G,QAAI,iBAAiB,IAAI,IAAI,EAAG,QAAO,IAAI,EAAE;AAAA,cACnC,GAAG,YAAY,WAAW,GAAG,YAAY,eAAe,kBAAmB,GAAwB,SAAS,EAAE,EAAE,OAAQ,QAAO,IAAI,EAAE;AAAA,EACjJ,CAAC;AACD,SAAO,QAAQ,CAAC,OAAO,KAAK,GAAG,sBAAsB,CAAC,CAAC;AAEvD,QAAM,MAAM,QAAQ,gBAAgB;AACpC,QAAM,SAAS,IAAI;AAAA,IAAiB;AAAA,IAAM;AAAA;AAAA,EAA4B;AACtE,MAAI,QAAsB;AAC1B,MAAI,OAAO;AACX,WAAS,OAAO,OAAO,SAAS,GAAG,QAAQ,OAAO,KAAK,OAAO,OAAO,SAAS,GAAG;AAC/E,UAAM,OAAO,KAAK,aAAa;AAC/B,QAAI,KAAK,SAAS,EAAG;AACrB;AACA,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,UAAU,kBAAkB,KAAK,OAAO,OAAO,EAAG;AACvD,UAAM,OAAO,kBAAkB,IAAI;AACnC,QAAI,CAAC,KAAK,OAAQ;AAElB,QAAI,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,CAAC,EAAG;AACxD,sBAAU,IAAI,YAAY;AAC1B,eAAW,CAAC,OAAO,GAAG,KAAK,MAAM;AAC/B,UAAI;AACF,cAAM,SAAS,MAAM,KAAK;AAC1B,cAAM,OAAO,MAAM,GAAG;AACtB,cAAM,KAAK,MAAM,eAAe,CAAC,EAAE,QAAQ,IAAI;AAAA,MACjD,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;ACnUO,SAAS,mBAA4B;AAC1C,SAAO,OAAO,cAAc,eAAe,CAAC,CAAC,UAAU,gBAAgB,OAAO,UAAU,aAAa,oBAAoB;AAC3H;AAOA,eAAsB,kBAAkB,WAAiB,MAAgB,UAAiC;AACxG,MAAI,CAAC,iBAAiB,EAAG,QAAO,EAAE,IAAI,OAAO,QAAQ,cAAc;AACnE,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,UAAU,aAAa,gBAAgB;AAAA,MACpD,OAAO,EAAE,gBAAgB,WAAW,WAAW,EAAE;AAAA,MACjD,OAAO;AAAA,MACP,kBAAkB;AAAA,MAClB,oBAAoB;AAAA,MACpB,kBAAkB;AAAA,IACpB,CAA8B;AAAA,EAChC,SAAS,KAAK;AACZ,WAAO,EAAE,IAAI,OAAO,QAAS,KAA2B,SAAS,oBAAoB,WAAW,QAAQ;AAAA,EAC1G;AACA,QAAM,QAAQ,OAAO,eAAe,EAAE,CAAC;AACvC,MAAI,CAAC,OAAO;AACV,WAAO,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;AAC1C,WAAO,EAAE,IAAI,OAAO,QAAQ,QAAQ;AAAA,EACtC;AACA,QAAM,UAAW,MAAM,cAAc,GAA+C;AACpF,MAAI,WAAW,YAAY,WAAW;AACpC,WAAO,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;AAC1C,WAAO,EAAE,IAAI,OAAO,QAAQ,gBAAgB;AAAA,EAC9C;AACA,QAAM,QAAQ,IAAI,cAAc,OAAO;AACvC,QAAM,QAAQ;AACd,QAAM,cAAc;AACpB,QAAM,aAAa,eAAe,MAAM;AAExC,QAAM,MAAM,UAAU;AACtB,MAAI;AACF,UAAM,YAAY;AAClB,cAAU,YAAY,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;AAC1C,WAAO,EAAE,IAAI,OAAO,QAAQ,QAAQ;AAAA,EACtC;AACA,MAAI;AACF,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,MAAM,WAAY,OAAM,IAAI,QAAc,CAAC,YAAY;AAC1D,YAAM,OAAO,MAAM,QAAQ;AAC3B,YAAM,iBAAiB,cAAc,MAAM,EAAE,MAAM,KAAK,CAAC;AACzD,iBAAW,MAAM,IAAI;AAAA,IACvB,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACA,MAAI,QAAQ;AACZ,QAAM,YAA+B,CAAC;AAGtC,QAAM,KAAK;AACX,QAAM,OAAO,OAAO,GAAG,8BAA8B;AACrD,MAAI,UAAU;AACd,MAAI,UAA6B,CAAC;AAClC,QAAM,UAAU,CAAC,QAAgB;AAC/B,cAAU;AACV,YAAQ,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;AACpC,QAAI,CAAC,MAAO,IAAG,0BAA2B,OAAO;AAAA,EACnD;AACA,MAAI,KAAM,IAAG,0BAA2B,OAAO;AAC/C,QAAM,MAAM,MAAM;AAChB,QAAI,MAAO;AACX,YAAQ;AACR,WAAO,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;AAC1C,UAAM,YAAY;AAClB,UAAM,OAAO;AACb,cAAU,OAAO,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,CAAC;AAAA,EAC1C;AACA,QAAM,iBAAiB,SAAS,GAAG;AACnC,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,QAAQ;AAAA,MACN,MAAM,MAAM,CAAC,SAAS,MAAM,eAAe;AAAA,MAC3C,SAAS,CAAC,OAAQ,QAAQ,GAAG,IAAI,UAAU,KAAK,EAAE;AAAA,MAClD,MAAM,MAAO,QAAQ,OAAO,UAAU,OAAO,GAAG;AAAA,MAChD,YAAY,CAAC,cAAe,OAAO,UAAU,YAAY,YAAY,IAAI,IAAI,YAAY;AAAA,MACzF,WAAW,CAAC,YAAY,QACtB,IAAI,QAAc,CAAC,YAAY;AAC7B,cAAM,IAAI,WAAW,MAAM,OAAO,YAAY,GAAG;AACjD,iBAAS,OAAO;AACd,uBAAa,CAAC;AACd,oBAAU,QAAQ,OAAO,CAAC,MAAM,MAAM,IAAI;AAC1C,kBAAQ;AAAA,QACV;AACA,YAAI,KAAM,SAAQ,KAAK,IAAI;AAAA,MAC7B,CAAC;AAAA,MACH,MAAM,MAAM;AACV,kBAAU,SAAS;AACnB,YAAI;AAAA,MACN;AAAA,IACF;AAAA,EACF;AACF;AAGO,SAAS,UAAU,OAAyB,MAAgB,UAAoC;AACrG,QAAM,IAAI,MAAM;AAChB,QAAM,IAAI,MAAM;AAChB,MAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,QAAM,SAAS,IAAI,cAAc,QAAQ;AACzC,SAAO,QAAQ;AACf,SAAO,SAAS;AAChB,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,UAAU,OAAO,GAAG,GAAG,GAAG,CAAC;AAC/B,SAAO;AACT;AAOO,SAAS,aAAa,QAA2B,OAAc,UAAqD;AACzH,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,CAAC,OAAO,CAAC,MAAM,UAAU,CAAC,SAAS,SAAS,CAAC,SAAS,OAAQ,QAAO;AACzE,QAAM,KAAK,OAAO,QAAQ,SAAS;AACnC,QAAM,KAAK,OAAO,SAAS,SAAS;AACpC,QAAM,MAAM,OAAO,cAAc,cAAc,QAAQ;AACvD,QAAM,OAAO,IAAI,WAAW,IAAI;AAChC,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,QAAQ;AACZ,aAAW,QAAQ,OAAO;AACxB,UAAM,IAAI,YAAY,MAAM,IAAI,IAAI,QAAQ,CAAC;AAC7C,QAAI,CAAC,EAAG;AAER,UAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,EAAE,QAAQ,KAAK,EAAE,CAAC;AAC3D,QAAI,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,EAAE,QAAQ,KAAK,CAAC;AACnD,QAAI,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,EAAE,SAAS,KAAK,CAAC;AACrD,SAAK,wBAAwB;AAC7B,SAAK,UAAU,QAAQ,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,GAAG,GAAG,IAAI,OAAO,IAAI,MAAM;AAC/E,QAAI,wBAAwB;AAC5B,QAAI,UAAU,KAAK,GAAG,GAAG,IAAI,OAAO,IAAI,QAAQ,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM;AAE3E,QAAI,YAAY;AAChB,QAAI,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM;AACxC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,OAAO,QAA2B,UAAqC;AAC9E,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,QAAM,MAAM,OAAO,cAAc,cAAc,QAAQ;AACvD,MAAI,QAAQ;AACZ,MAAI,SAAS,KAAK,MAAO,OAAO,SAAS,WAAY,OAAO,KAAK;AACjE,QAAM,MAAM,IAAI,WAAW,IAAI;AAC/B,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,wBAAwB;AAC5B,MAAI,UAAU,QAAQ,GAAG,GAAG,IAAI,OAAO,IAAI,MAAM;AACjD,SAAO;AACT;AAEA,IAAM,SAAS,CAAC,QAA2B,MAAc,YACvD,IAAI,QAAqB,CAAC,YAAY;AACpC,MAAI;AACF,WAAO,OAAO,CAAC,MAAM,QAAQ,CAAC,GAAG,MAAM,OAAO;AAAA,EAChD,QAAQ;AACN,YAAQ,IAAI;AAAA,EACd;AACF,CAAC;AAGH,eAAsB,iBAAiB,QAA2B,UAAoD,CAAC,GAAyB;AAC9I,QAAM,MAAM,OAAO,QAAQ,QAAQ,YAAY,IAAI;AACnD,QAAM,WAAW,QAAQ,YAAY,IAAI,OAAO;AAChD,MAAI,OAAO,MAAM,OAAO,KAAK,cAAc,IAAI;AAE/C,MAAI,CAAC,QAAQ,KAAK,SAAS,aAAc,QAAO,MAAM,OAAO,KAAK,cAAc,IAAI;AACpF,MAAI,QAAQ,KAAK,OAAO,SAAU,QAAO,MAAM,OAAO,KAAK,cAAc,GAAG;AAC5E,SAAO,QAAQ,KAAK,QAAQ,WAAW,OAAO;AAChD;AAGO,SAAS,kBAAkB,QAA0C;AAC1E,MAAI;AACF,WAAO,OAAO,QAAQ,IAAI,EAAE,UAAU,cAAc,GAAG;AAAA,EACzD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,8 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ export {
6
+ __publicField
7
+ };
8
+ //# sourceMappingURL=chunk-V6TY7KAL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/index.js CHANGED
@@ -5,7 +5,18 @@ import {
5
5
  sanitizeHtml,
6
6
  sanitizeToFragment,
7
7
  setSanitizedHtml
8
- } from "./chunk-KEQ2VEEC.js";
8
+ } from "./chunk-QFKMXMM4.js";
9
+ import {
10
+ DEFAULT_THEME,
11
+ HOST_ID,
12
+ STYLES,
13
+ applyTheme,
14
+ createScope,
15
+ destroyHost,
16
+ ensureHost,
17
+ prefersReducedMotion,
18
+ resolveTheme
19
+ } from "./chunk-63M4VHQG.js";
9
20
  import {
10
21
  SHADOW_SEPARATOR,
11
22
  TargetTimeoutError,
@@ -29,18 +40,8 @@ import {
29
40
  urlMatches,
30
41
  visualRect,
31
42
  waitForTarget
32
- } from "./chunk-YU6SKJK4.js";
33
- import {
34
- DEFAULT_THEME,
35
- HOST_ID,
36
- STYLES,
37
- applyTheme,
38
- createScope,
39
- destroyHost,
40
- ensureHost,
41
- prefersReducedMotion,
42
- resolveTheme
43
- } from "./chunk-KQGSHCNW.js";
43
+ } from "./chunk-EPR4LWA2.js";
44
+ import "./chunk-V6TY7KAL.js";
44
45
 
45
46
  // src/position.ts
46
47
  import { arrow, autoPlacement, autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom";