@qaiddev/thumbs-embed 1.3.0 → 1.4.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.
Files changed (39) hide show
  1. package/dist/bootstrap.d.ts +15 -0
  2. package/dist/chunks/annotate-BNibaDg3.js +367 -0
  3. package/dist/chunks/annotate-BNibaDg3.js.map +1 -0
  4. package/dist/chunks/bootstrap-U4yiPm5-.js +1099 -0
  5. package/dist/chunks/bootstrap-U4yiPm5-.js.map +1 -0
  6. package/dist/chunks/modal-BX5wqy4s.js +119 -0
  7. package/dist/chunks/modal-BX5wqy4s.js.map +1 -0
  8. package/dist/chunks/network-capture-DMbMwwwR.js +63 -0
  9. package/dist/chunks/network-capture-DMbMwwwR.js.map +1 -0
  10. package/dist/chunks/recording--IwOtRL2.js +256 -0
  11. package/dist/chunks/recording--IwOtRL2.js.map +1 -0
  12. package/dist/chunks/screenshot-BkOCLuS2.js +33 -0
  13. package/dist/chunks/screenshot-BkOCLuS2.js.map +1 -0
  14. package/dist/chunks/screenshot-dom-CME7tYRE.js +63 -0
  15. package/dist/chunks/screenshot-dom-CME7tYRE.js.map +1 -0
  16. package/dist/chunks/targeting-DwdDr9Cm.js +295 -0
  17. package/dist/chunks/targeting-DwdDr9Cm.js.map +1 -0
  18. package/dist/chunks/video-BOYqf2Im.js +209 -0
  19. package/dist/chunks/video-BOYqf2Im.js.map +1 -0
  20. package/dist/dom.d.ts +36 -0
  21. package/dist/embed.d.ts +53 -68
  22. package/dist/index.d.ts +10 -12
  23. package/dist/index.js +22 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/loader.d.ts +18 -0
  26. package/dist/loader.js +7 -0
  27. package/dist/loader.js.map +1 -0
  28. package/dist/modal.d.ts +49 -0
  29. package/dist/qaid.umd.cjs +23 -23
  30. package/dist/qaid.umd.cjs.map +1 -1
  31. package/dist/recording.d.ts +74 -0
  32. package/dist/targeting.d.ts +72 -0
  33. package/dist/types.d.ts +7 -1
  34. package/dist/video-capture.d.ts +7 -0
  35. package/dist/video-orientation.d.ts +2 -0
  36. package/dist/video-redaction.d.ts +58 -0
  37. package/package.json +9 -5
  38. package/dist/qaid.js +0 -2013
  39. package/dist/qaid.js.map +0 -1
@@ -0,0 +1,256 @@
1
+ import { b as h } from "./annotate-BNibaDg3.js";
2
+ import { c as l } from "./network-capture-DMbMwwwR.js";
3
+ const u = document;
4
+ function s(d, t, ...i) {
5
+ const e = u.createElement(d);
6
+ if (t) {
7
+ if (t.class && (e.className = t.class), t.style && (e.style.cssText = t.style), t.html != null && (e.innerHTML = t.html), t.text != null && (e.textContent = t.text), t.attrs)
8
+ for (const o in t.attrs) e.setAttribute(o, t.attrs[o]);
9
+ if (t.on)
10
+ for (const o in t.on) e.addEventListener(o, t.on[o]);
11
+ }
12
+ for (const o of i)
13
+ o != null && o !== !1 && e.append(o);
14
+ return e;
15
+ }
16
+ class g {
17
+ constructor(t) {
18
+ this.host = t;
19
+ }
20
+ videoRecorder = null;
21
+ networkCapture = null;
22
+ recordedBlob = null;
23
+ recordingIndicator = null;
24
+ videoPreview = null;
25
+ isRecording = !1;
26
+ isSendingVideo = !1;
27
+ // Pre-recording redaction picking.
28
+ redactPicks = [];
29
+ redactPickerRoot = null;
30
+ redactRafId = 0;
31
+ boundRedactClick = (t) => this.handleRedactPickClick(t);
32
+ /** Escape-key handler; returns true when it consumed the key. */
33
+ handleEscape() {
34
+ return this.isRecording ? (this.stopRecording(), !0) : this.videoPreview ? (this.cancelRecordingPreview(), !0) : this.redactPickerRoot ? (this.finishRedactionPicking(!1), !0) : !1;
35
+ }
36
+ destroy() {
37
+ this.redactRafId && cancelAnimationFrame(this.redactRafId), this.redactRafId = 0, document.removeEventListener("click", this.boundRedactClick, !0), this.redactPickerRoot && (this.redactPickerRoot.remove(), this.redactPickerRoot = null), this.redactPicks = [], this.cleanupRecording(), this.removeVideoPreview();
38
+ }
39
+ async startRecording(t = []) {
40
+ if (!(this.isRecording || this.host.state !== "IDLE"))
41
+ try {
42
+ this.networkCapture = l();
43
+ const { createVideoRecorder: i } = await import("./video-BOYqf2Im.js");
44
+ this.videoRecorder = i({
45
+ maxDuration: this.host.config.videoOptions.maxDuration,
46
+ redactionElements: t
47
+ }), this.videoRecorder.onTick((e) => {
48
+ this.updateRecordingTimer(e);
49
+ }), this.videoRecorder.onStop((e) => {
50
+ this.isRecording && (this.recordedBlob = e, this.isRecording = !1, this.host.announceMsg("Recording stopped"), this.removeRecordingIndicator(), document.removeEventListener("keydown", this.host.boundKeyDown), this.host.setButtonsDisabled(!1), e && e.size > 0 ? this.showRecordingPreview() : this.cleanupRecording());
51
+ }), await this.videoRecorder.start(), this.isRecording = !0, this.host.announceMsg("Recording started"), this.host.setButtonsDisabled(!0), this.showRecordingIndicator(), document.addEventListener("keydown", this.host.boundKeyDown);
52
+ } catch (i) {
53
+ i instanceof Error && /current tab/i.test(i.message) && this.host.announceMsg(i.message), this.cleanupRecording();
54
+ }
55
+ }
56
+ async stopRecording() {
57
+ try {
58
+ this.recordedBlob = await this.videoRecorder.stop();
59
+ } catch {
60
+ this.recordedBlob = null;
61
+ }
62
+ this.isRecording = !1, this.host.announceMsg("Recording stopped"), this.removeRecordingIndicator(), document.removeEventListener("keydown", this.host.boundKeyDown), this.recordedBlob && this.recordedBlob.size > 0 ? this.showRecordingPreview() : this.cleanupRecording();
63
+ }
64
+ /**
65
+ * Pre-recording redaction picker. The user clicks page areas to blur; each
66
+ * gets an outline that tracks its position, then "Start recording" hands the
67
+ * picks to the recorder, which blurs their live bounding boxes so the
68
+ * redaction follows the content as the page scrolls. "Cancel"/Escape aborts.
69
+ */
70
+ startPicking() {
71
+ if (this.isRecording || this.host.state !== "IDLE") return;
72
+ this.host.setState("REDACT_PICKING"), this.redactPicks = [];
73
+ const t = this.host.ensureOverlayHost(), i = s("div", { style: "position:absolute;inset:0;" }), e = s(
74
+ "div",
75
+ { class: "qaid-redact-picker", style: "position:fixed;inset:0;pointer-events:none;" },
76
+ i,
77
+ // Control bar — the only pointer-interactive part of the overlay.
78
+ s(
79
+ "div",
80
+ {
81
+ style: "position:fixed;left:50%;bottom:24px;transform:translateX(-50%);display:flex;gap:12px;align-items:center;pointer-events:auto;background:#0b1220;color:#fff;border:1px solid #ff6b6b;border-radius:9999px;padding:10px 16px;font:600 13px system-ui,sans-serif;box-shadow:0 8px 30px rgba(0,0,0,.5);"
82
+ },
83
+ s("span", {
84
+ text: "Click sensitive areas to blur",
85
+ attrs: { "data-qaid-redact-label": "" }
86
+ }),
87
+ s("button", {
88
+ text: "Start recording",
89
+ attrs: { type: "button" },
90
+ style: "cursor:pointer;border:0;border-radius:9999px;padding:8px 14px;background:#ff6b6b;color:#0b1220;font:inherit;",
91
+ on: { click: () => this.finishRedactionPicking(!0) }
92
+ }),
93
+ s("button", {
94
+ text: "Cancel",
95
+ attrs: { type: "button" },
96
+ style: "cursor:pointer;border:0;background:transparent;color:#9fb3c8;font:inherit;",
97
+ on: { click: () => this.finishRedactionPicking(!1) }
98
+ })
99
+ )
100
+ );
101
+ t.appendChild(e), this.redactPickerRoot = e;
102
+ const o = () => {
103
+ this.renderRedactPicks(i), this.redactRafId = requestAnimationFrame(o);
104
+ };
105
+ o(), document.addEventListener("click", this.boundRedactClick, !0), document.addEventListener("keydown", this.host.boundKeyDown), this.host.announceMsg("Click sensitive areas to blur, then start recording");
106
+ }
107
+ handleRedactPickClick(t) {
108
+ const i = t.target instanceof Element ? t.target : null;
109
+ if (!i || h(i) || this.redactPickerRoot?.contains(i)) return;
110
+ t.preventDefault(), t.stopPropagation();
111
+ const e = this.redactPicks.indexOf(i);
112
+ e >= 0 ? this.redactPicks.splice(e, 1) : this.redactPicks.push(i);
113
+ const o = this.redactPickerRoot?.querySelector("[data-qaid-redact-label]");
114
+ if (o) {
115
+ const r = this.redactPicks.length;
116
+ o.textContent = r === 0 ? "Click sensitive areas to blur" : `${r} area${r === 1 ? "" : "s"} will be blurred`;
117
+ }
118
+ }
119
+ renderRedactPicks(t) {
120
+ t.textContent = "";
121
+ for (const i of this.redactPicks) {
122
+ const e = i.getBoundingClientRect();
123
+ e.width <= 0 || e.height <= 0 || t.appendChild(
124
+ s("div", {
125
+ style: `position:fixed;left:${e.left}px;top:${e.top}px;width:${e.width}px;height:${e.height}px;border:2px solid #ff6b6b;border-radius:4px;background:rgba(255,107,107,.18);pointer-events:none;`
126
+ })
127
+ );
128
+ }
129
+ }
130
+ finishRedactionPicking(t) {
131
+ this.redactRafId && cancelAnimationFrame(this.redactRafId), this.redactRafId = 0, document.removeEventListener("click", this.boundRedactClick, !0), document.removeEventListener("keydown", this.host.boundKeyDown), this.redactPickerRoot && (this.redactPickerRoot.remove(), this.redactPickerRoot = null);
132
+ const i = this.redactPicks;
133
+ this.redactPicks = [], this.host.setState("IDLE"), t && this.startRecording(i);
134
+ }
135
+ showRecordingIndicator() {
136
+ const t = this.host.ensureOverlayHost();
137
+ this.recordingIndicator = s(
138
+ "div",
139
+ { class: "qaid-recording-indicator", style: `z-index:${this.host.config.zIndex + 100}` },
140
+ s("div", { class: "qaid-recording-dot" }),
141
+ s("span", {
142
+ class: "qaid-recording-time",
143
+ text: this.formatTime(this.host.config.videoOptions.maxDuration)
144
+ }),
145
+ s("button", {
146
+ class: "qaid-recording-stop",
147
+ text: "Stop",
148
+ attrs: { type: "button" },
149
+ on: { click: () => this.stopRecording() }
150
+ })
151
+ ), this.host.applyVars(this.recordingIndicator), this.host.overlayShadowHost && (this.host.overlayShadowHost.style.pointerEvents = "auto"), t.appendChild(this.recordingIndicator);
152
+ }
153
+ updateRecordingTimer(t) {
154
+ if (!this.recordingIndicator) return;
155
+ const i = this.recordingIndicator.querySelector(".qaid-recording-time");
156
+ if (i) {
157
+ const e = Math.max(0, this.host.config.videoOptions.maxDuration - t);
158
+ i.textContent = this.formatTime(e), e > 0 && e <= 5 && this.host.announceMsg(`${e} second${e === 1 ? "" : "s"} remaining`);
159
+ }
160
+ }
161
+ formatTime(t) {
162
+ const i = Math.floor(t / 60), e = t % 60;
163
+ return `${i}:${e.toString().padStart(2, "0")}`;
164
+ }
165
+ removeRecordingIndicator() {
166
+ this.recordingIndicator && (this.recordingIndicator.remove(), this.recordingIndicator = null);
167
+ }
168
+ showRecordingPreview() {
169
+ const t = this.host.ensureOverlayHost(), i = URL.createObjectURL(this.recordedBlob), e = `qaid-video-title-${this.host.uid}`, o = s("h3", { text: "Review your recording", attrs: { id: e } }), r = s("video");
170
+ r.src = i, r.controls = !0, r.autoplay = !0, r.muted = !0;
171
+ const n = s("textarea", {
172
+ attrs: {
173
+ placeholder: "Optional: Describe the issue you recorded...",
174
+ "aria-label": "Describe the issue you recorded"
175
+ }
176
+ }), c = s("button", {
177
+ class: "qaid-video-btn qaid-video-btn-send",
178
+ text: "Send",
179
+ attrs: { type: "button" },
180
+ on: { click: () => this.submitVideoFeedback(n.value.trim() || null, c) }
181
+ }), a = s(
182
+ "div",
183
+ { class: "qaid-video-preview-box" },
184
+ o,
185
+ r,
186
+ n,
187
+ s(
188
+ "div",
189
+ { class: "qaid-video-preview-actions" },
190
+ s("button", {
191
+ class: "qaid-video-btn qaid-video-btn-cancel",
192
+ text: "Cancel",
193
+ attrs: { type: "button" },
194
+ on: { click: () => this.cancelRecordingPreview() }
195
+ }),
196
+ s("button", {
197
+ class: "qaid-video-btn qaid-video-btn-rerecord",
198
+ text: "Re-record",
199
+ attrs: { type: "button" },
200
+ on: {
201
+ click: () => {
202
+ this.cancelRecordingPreview(), this.startRecording();
203
+ }
204
+ }
205
+ }),
206
+ c
207
+ )
208
+ );
209
+ this.videoPreview = s(
210
+ "div",
211
+ { class: "qaid-video-preview", style: `z-index:${this.host.config.zIndex + 100}` },
212
+ a
213
+ ), this.host.applyVars(this.videoPreview), this.host.overlayShadowHost && (this.host.overlayShadowHost.style.pointerEvents = "auto"), t.appendChild(this.videoPreview), this.host.openDialogA11y(a, { labelledbyId: o.id }), document.addEventListener("keydown", this.host.boundKeyDown);
214
+ }
215
+ cancelRecordingPreview() {
216
+ this.removeVideoPreview(), this.cleanupRecording();
217
+ }
218
+ removeVideoPreview() {
219
+ if (this.host.closeDialogA11y(), this.videoPreview) {
220
+ const t = this.videoPreview.querySelector("video");
221
+ t?.src && URL.revokeObjectURL(t.src), this.videoPreview.remove(), this.videoPreview = null;
222
+ }
223
+ document.removeEventListener("keydown", this.host.boundKeyDown);
224
+ }
225
+ async submitVideoFeedback(t, i) {
226
+ if (!this.recordedBlob || this.isSendingVideo) return;
227
+ this.isSendingVideo = !0, i.disabled = !0, i.textContent = "Sending...";
228
+ const e = new FormData();
229
+ e.append("video", this.recordedBlob, `recording.${this.recordedBlob.type.includes("mp4") ? "mp4" : "webm"}`), e.append("pageUrl", window.location.href), e.append("visitorId", this.host.visitorId), this.host.config.apiKey && e.append("apiKey", this.host.config.apiKey), t && e.append("message", t), this.host.consoleCapture && e.append("consoleErrors", JSON.stringify(this.host.consoleCapture.errors)), this.networkCapture && e.append("networkErrors", JSON.stringify(this.networkCapture.errors));
230
+ let o = null;
231
+ try {
232
+ const r = await fetch(`${this.host.config.endpoint}/video`, {
233
+ method: "POST",
234
+ body: e
235
+ });
236
+ if (r.ok) {
237
+ this.host.announceMsg("Recording sent");
238
+ try {
239
+ o = (await r.json())?.id ?? null;
240
+ } catch {
241
+ }
242
+ } else
243
+ console.error("Failed to submit video feedback:", await r.text()), this.host.announceMsg("Failed to send recording", !0);
244
+ } catch (r) {
245
+ console.error("Failed to submit video feedback:", r), this.host.announceMsg("Failed to send recording", !0);
246
+ }
247
+ this.isSendingVideo = !1, this.removeVideoPreview(), this.cleanupRecording(), await this.host.tryLaunchQuest("video", o);
248
+ }
249
+ cleanupRecording() {
250
+ this.isRecording = !1, this.removeRecordingIndicator(), this.videoRecorder && (this.videoRecorder.destroy(), this.videoRecorder = null), this.networkCapture && (this.networkCapture.restore(), this.networkCapture = null), this.recordedBlob && (this.recordedBlob = null), this.host.setButtonsDisabled(!1), this.host.overlayShadowHost && this.host.state === "IDLE" && (this.host.overlayShadowHost.style.pointerEvents = "none");
251
+ }
252
+ }
253
+ export {
254
+ g as RecordingController
255
+ };
256
+ //# sourceMappingURL=recording--IwOtRL2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording--IwOtRL2.js","sources":["../../src/dom.ts","../../src/recording.ts"],"sourcesContent":["/**\n * Nano DOM factory — a purpose-built, construction-only helper for this embed.\n *\n * The embed builds all of its UI imperatively inside a Shadow DOM. That means\n * the same create → set class/style/attrs → wire events → append dance repeats\n * across every button, overlay, modal, and recording control. `h()` collapses\n * that dance into a single declarative expression.\n *\n * It is deliberately NOT a framework: no reactivity, no virtual DOM, no runtime\n * state. It only constructs. That fits what the embed actually needs (one-shot\n * DOM construction plus a few imperative updates), adds no dependency, and\n * keeps the package zero-dependency. Aliasing `document` here also lets the\n * minifier mangle the call site, which the global `document.createElement`\n * can't be.\n */\n\nconst d = document;\n\nexport interface ElProps {\n /** className */\n class?: string;\n /** Inline styles as a cssText string. */\n style?: string;\n /** innerHTML — for icons / trusted SVG markup. */\n html?: string;\n /** textContent. */\n text?: string;\n /** Attributes set via setAttribute (aria-*, data-*, type, role, …). */\n attrs?: Record<string, string>;\n /** Event listeners keyed by type. */\n on?: Record<string, (e: Event) => void>;\n}\n\ntype Child = Node | string | null | undefined | false;\n\n/**\n * Create an element, apply props, and append children. Falsy children are\n * skipped so conditional content reads cleanly (`cond && h(...)`).\n */\nexport function h<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n props?: ElProps | null,\n ...children: Child[]\n): HTMLElementTagNameMap[K] {\n const el = d.createElement(tag);\n if (props) {\n if (props.class) el.className = props.class;\n if (props.style) el.style.cssText = props.style;\n if (props.html != null) el.innerHTML = props.html;\n if (props.text != null) el.textContent = props.text;\n if (props.attrs) {\n for (const k in props.attrs) el.setAttribute(k, props.attrs[k]);\n }\n if (props.on) {\n for (const k in props.on) el.addEventListener(k, props.on[k]);\n }\n }\n for (const child of children) {\n if (child != null && child !== false) el.append(child);\n }\n return el;\n}\n","/**\n * Video-recording subsystem — extracted from embed.ts so it lives in its own\n * lazily-loaded chunk. A thumbs-only visitor never downloads any of this; the\n * embed dynamically imports it the first time the record button is used (and\n * warms it on hover — see embed.ts prewarmVideo).\n *\n * The controller owns all recording state and UI (recorder, redaction picker,\n * indicator, preview, submit). It talks back to the embed through the narrow\n * `RecordingHost` interface, so embed internals stay private and the coupling\n * is explicit.\n */\n\nimport { h } from \"./dom\";\nimport { isEmbedElement } from \"./dom-utils\";\nimport { captureNetworkErrors, type NetworkCapture } from \"./network-capture\";\nimport type { VideoRecorder } from \"./video-capture\";\nimport type { ResolvedFeedbackConfig, EmbedState } from \"./types\";\nimport type { ConsoleCapture } from \"./console-capture\";\n\n/** The slice of the embed the recording subsystem needs. */\nexport interface RecordingHost {\n readonly config: ResolvedFeedbackConfig;\n readonly visitorId: string;\n readonly uid: string;\n readonly overlayShadowHost: HTMLDivElement | null;\n readonly consoleCapture: ConsoleCapture | null;\n readonly boundKeyDown: (e: KeyboardEvent) => void;\n readonly state: EmbedState;\n setState(state: EmbedState): void;\n ensureOverlayHost(): ShadowRoot;\n applyVars(el: HTMLElement): void;\n announceMsg(message: string, assertive?: boolean): void;\n openDialogA11y(\n container: HTMLElement,\n opts: { labelledbyId?: string; describedbyId?: string; label?: string }\n ): void;\n closeDialogA11y(): void;\n setButtonsDisabled(disabled: boolean): void;\n tryLaunchQuest(\n type: \"up\" | \"down\" | \"video\",\n feedbackId: number | string | null\n ): Promise<boolean>;\n}\n\nexport class RecordingController {\n private videoRecorder: VideoRecorder | null = null;\n private networkCapture: NetworkCapture | null = null;\n private recordedBlob: Blob | null = null;\n private recordingIndicator: HTMLDivElement | null = null;\n private videoPreview: HTMLDivElement | null = null;\n private isRecording = false;\n private isSendingVideo = false;\n\n // Pre-recording redaction picking.\n private redactPicks: Element[] = [];\n private redactPickerRoot: HTMLDivElement | null = null;\n private redactRafId = 0;\n private boundRedactClick = (e: MouseEvent): void => this.handleRedactPickClick(e);\n\n constructor(private host: RecordingHost) {}\n\n /** Escape-key handler; returns true when it consumed the key. */\n handleEscape(): boolean {\n if (this.isRecording) {\n void this.stopRecording();\n return true;\n }\n if (this.videoPreview) {\n this.cancelRecordingPreview();\n return true;\n }\n if (this.redactPickerRoot) {\n this.finishRedactionPicking(false);\n return true;\n }\n return false;\n }\n\n destroy(): void {\n if (this.redactRafId) cancelAnimationFrame(this.redactRafId);\n this.redactRafId = 0;\n document.removeEventListener(\"click\", this.boundRedactClick, true);\n if (this.redactPickerRoot) {\n this.redactPickerRoot.remove();\n this.redactPickerRoot = null;\n }\n this.redactPicks = [];\n this.cleanupRecording();\n this.removeVideoPreview();\n }\n\n async startRecording(redactionElements: Element[] = []): Promise<void> {\n // Don't start if already recording or in a targeting/modal flow.\n if (this.isRecording || this.host.state !== \"IDLE\") return;\n\n try {\n this.networkCapture = captureNetworkErrors();\n\n const { createVideoRecorder } = await import(\"./video-capture\");\n this.videoRecorder = createVideoRecorder({\n maxDuration: this.host.config.videoOptions.maxDuration,\n redactionElements,\n });\n\n this.videoRecorder.onTick((elapsed) => {\n this.updateRecordingTimer(elapsed);\n });\n\n // Handle unexpected stops (browser stop button, max duration).\n this.videoRecorder.onStop((blob) => {\n if (this.isRecording) {\n this.recordedBlob = blob;\n this.isRecording = false;\n this.host.announceMsg(\"Recording stopped\");\n this.removeRecordingIndicator();\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n this.host.setButtonsDisabled(false);\n\n if (blob && blob.size > 0) {\n this.showRecordingPreview();\n } else {\n this.cleanupRecording();\n }\n }\n });\n\n await this.videoRecorder.start();\n this.isRecording = true;\n this.host.announceMsg(\"Recording started\");\n this.host.setButtonsDisabled(true);\n this.showRecordingIndicator();\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n } catch (error) {\n // User denied screen share, shared a disallowed surface (window / whole\n // screen), or another error occurred. Surface the tab-only message so a\n // rejected non-tab share isn't a silent no-op.\n if (error instanceof Error && /current tab/i.test(error.message)) {\n this.host.announceMsg(error.message);\n }\n this.cleanupRecording();\n }\n }\n\n private async stopRecording(): Promise<void> {\n try {\n this.recordedBlob = await this.videoRecorder!.stop();\n } catch {\n this.recordedBlob = null;\n }\n\n this.isRecording = false;\n this.host.announceMsg(\"Recording stopped\");\n this.removeRecordingIndicator();\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n\n if (this.recordedBlob && this.recordedBlob.size > 0) {\n this.showRecordingPreview();\n } else {\n this.cleanupRecording();\n }\n }\n\n /**\n * Pre-recording redaction picker. The user clicks page areas to blur; each\n * gets an outline that tracks its position, then \"Start recording\" hands the\n * picks to the recorder, which blurs their live bounding boxes so the\n * redaction follows the content as the page scrolls. \"Cancel\"/Escape aborts.\n */\n startPicking(): void {\n if (this.isRecording || this.host.state !== \"IDLE\") return;\n this.host.setState(\"REDACT_PICKING\");\n this.redactPicks = [];\n\n const root = this.host.ensureOverlayHost();\n\n // Layer holding the per-pick outlines (repositioned every frame).\n const boxHost = h(\"div\", { style: \"position:absolute;inset:0;\" });\n\n const container = h(\n \"div\",\n { class: \"qaid-redact-picker\", style: \"position:fixed;inset:0;pointer-events:none;\" },\n boxHost,\n // Control bar — the only pointer-interactive part of the overlay.\n h(\n \"div\",\n {\n style:\n \"position:fixed;left:50%;bottom:24px;transform:translateX(-50%);\" +\n \"display:flex;gap:12px;align-items:center;pointer-events:auto;\" +\n \"background:#0b1220;color:#fff;border:1px solid #ff6b6b;border-radius:9999px;\" +\n \"padding:10px 16px;font:600 13px system-ui,sans-serif;box-shadow:0 8px 30px rgba(0,0,0,.5);\",\n },\n h(\"span\", {\n text: \"Click sensitive areas to blur\",\n attrs: { \"data-qaid-redact-label\": \"\" },\n }),\n h(\"button\", {\n text: \"Start recording\",\n attrs: { type: \"button\" },\n style:\n \"cursor:pointer;border:0;border-radius:9999px;padding:8px 14px;background:#ff6b6b;color:#0b1220;font:inherit;\",\n on: { click: () => this.finishRedactionPicking(true) },\n }),\n h(\"button\", {\n text: \"Cancel\",\n attrs: { type: \"button\" },\n style: \"cursor:pointer;border:0;background:transparent;color:#9fb3c8;font:inherit;\",\n on: { click: () => this.finishRedactionPicking(false) },\n }),\n ),\n );\n\n root.appendChild(container);\n this.redactPickerRoot = container;\n\n // Keep the outlines glued to their elements while the user scrolls/picks.\n const tick = (): void => {\n this.renderRedactPicks(boxHost);\n this.redactRafId = requestAnimationFrame(tick);\n };\n tick();\n\n document.addEventListener(\"click\", this.boundRedactClick, true);\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n this.host.announceMsg(\"Click sensitive areas to blur, then start recording\");\n }\n\n private handleRedactPickClick(e: MouseEvent): void {\n const target = e.target instanceof Element ? e.target : null;\n // Ignore clicks on the embed's own UI. isEmbedElement catches the retargeted\n // shadow host (real browsers compose click events to it); the contains()\n // check catches the picker's own control-bar buttons when the event isn't\n // retargeted, so their handlers still fire instead of being swallowed here.\n if (!target || isEmbedElement(target) || this.redactPickerRoot?.contains(target)) return;\n e.preventDefault();\n e.stopPropagation();\n\n const idx = this.redactPicks.indexOf(target);\n if (idx >= 0) {\n this.redactPicks.splice(idx, 1);\n } else {\n this.redactPicks.push(target);\n }\n\n const label = this.redactPickerRoot?.querySelector(\"[data-qaid-redact-label]\");\n if (label) {\n const n = this.redactPicks.length;\n label.textContent =\n n === 0 ? \"Click sensitive areas to blur\" : `${n} area${n === 1 ? \"\" : \"s\"} will be blurred`;\n }\n }\n\n private renderRedactPicks(layer: HTMLElement): void {\n // Rebuild the outline boxes to match current picks and their live positions.\n layer.textContent = \"\";\n for (const el of this.redactPicks) {\n const r = el.getBoundingClientRect();\n if (r.width <= 0 || r.height <= 0) continue;\n layer.appendChild(\n h(\"div\", {\n style:\n `position:fixed;left:${r.left}px;top:${r.top}px;width:${r.width}px;height:${r.height}px;` +\n \"border:2px solid #ff6b6b;border-radius:4px;background:rgba(255,107,107,.18);pointer-events:none;\",\n }),\n );\n }\n }\n\n private finishRedactionPicking(record: boolean): void {\n if (this.redactRafId) cancelAnimationFrame(this.redactRafId);\n this.redactRafId = 0;\n document.removeEventListener(\"click\", this.boundRedactClick, true);\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n if (this.redactPickerRoot) {\n this.redactPickerRoot.remove();\n this.redactPickerRoot = null;\n }\n const picks = this.redactPicks;\n this.redactPicks = [];\n this.host.setState(\"IDLE\");\n if (record) {\n void this.startRecording(picks);\n }\n }\n\n private showRecordingIndicator(): void {\n const root = this.host.ensureOverlayHost();\n\n this.recordingIndicator = h(\n \"div\",\n { class: \"qaid-recording-indicator\", style: `z-index:${this.host.config.zIndex + 100}` },\n h(\"div\", { class: \"qaid-recording-dot\" }),\n h(\"span\", {\n class: \"qaid-recording-time\",\n text: this.formatTime(this.host.config.videoOptions.maxDuration),\n }),\n h(\"button\", {\n class: \"qaid-recording-stop\",\n text: \"Stop\",\n attrs: { type: \"button\" },\n on: { click: () => this.stopRecording() },\n }),\n );\n\n this.host.applyVars(this.recordingIndicator);\n\n // Enable pointer events on the overlay host for the recording indicator.\n if (this.host.overlayShadowHost) {\n this.host.overlayShadowHost.style.pointerEvents = \"auto\";\n }\n\n root.appendChild(this.recordingIndicator);\n }\n\n private updateRecordingTimer(elapsed: number): void {\n if (!this.recordingIndicator) return;\n const timer = this.recordingIndicator.querySelector<HTMLSpanElement>(\".qaid-recording-time\");\n if (timer) {\n const remaining = Math.max(0, this.host.config.videoOptions.maxDuration - elapsed);\n timer.textContent = this.formatTime(remaining);\n // Announce the final countdown so it is perceivable non-visually.\n if (remaining > 0 && remaining <= 5) {\n this.host.announceMsg(`${remaining} second${remaining === 1 ? \"\" : \"s\"} remaining`);\n }\n }\n }\n\n private formatTime(seconds: number): string {\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return `${m}:${s.toString().padStart(2, \"0\")}`;\n }\n\n private removeRecordingIndicator(): void {\n if (this.recordingIndicator) {\n this.recordingIndicator.remove();\n this.recordingIndicator = null;\n }\n }\n\n private showRecordingPreview(): void {\n const root = this.host.ensureOverlayHost();\n\n const videoUrl = URL.createObjectURL(this.recordedBlob!);\n const titleId = `qaid-video-title-${this.host.uid}`;\n\n const title = h(\"h3\", { text: \"Review your recording\", attrs: { id: titleId } });\n\n // Boolean media props must be set as properties (the `muted` attribute is\n // unreliable for autoplay), so build the element then assign them.\n const videoEl = h(\"video\");\n videoEl.src = videoUrl;\n videoEl.controls = true;\n videoEl.autoplay = true;\n videoEl.muted = true;\n\n const textarea = h(\"textarea\", {\n attrs: {\n placeholder: \"Optional: Describe the issue you recorded...\",\n \"aria-label\": \"Describe the issue you recorded\",\n },\n });\n\n const sendBtn = h(\"button\", {\n class: \"qaid-video-btn qaid-video-btn-send\",\n text: \"Send\",\n attrs: { type: \"button\" },\n on: { click: () => this.submitVideoFeedback(textarea.value.trim() || null, sendBtn) },\n });\n\n const box = h(\n \"div\",\n { class: \"qaid-video-preview-box\" },\n title,\n videoEl,\n textarea,\n h(\n \"div\",\n { class: \"qaid-video-preview-actions\" },\n h(\"button\", {\n class: \"qaid-video-btn qaid-video-btn-cancel\",\n text: \"Cancel\",\n attrs: { type: \"button\" },\n on: { click: () => this.cancelRecordingPreview() },\n }),\n h(\"button\", {\n class: \"qaid-video-btn qaid-video-btn-rerecord\",\n text: \"Re-record\",\n attrs: { type: \"button\" },\n on: {\n click: () => {\n this.cancelRecordingPreview();\n this.startRecording();\n },\n },\n }),\n sendBtn,\n ),\n );\n\n this.videoPreview = h(\n \"div\",\n { class: \"qaid-video-preview\", style: `z-index:${this.host.config.zIndex + 100}` },\n box,\n );\n\n this.host.applyVars(this.videoPreview);\n\n // Enable pointer events on the overlay host for the video preview.\n if (this.host.overlayShadowHost) {\n this.host.overlayShadowHost.style.pointerEvents = \"auto\";\n }\n\n root.appendChild(this.videoPreview);\n\n // Dialog semantics + focus trap + background inert.\n this.host.openDialogA11y(box, { labelledbyId: title.id });\n\n // Listen for the escape key.\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n private cancelRecordingPreview(): void {\n this.removeVideoPreview();\n this.cleanupRecording();\n }\n\n private removeVideoPreview(): void {\n // Release focus trap / inert and restore focus before removing the DOM.\n this.host.closeDialogA11y();\n if (this.videoPreview) {\n const videoEl = this.videoPreview.querySelector<HTMLVideoElement>(\"video\");\n if (videoEl?.src) {\n URL.revokeObjectURL(videoEl.src);\n }\n this.videoPreview.remove();\n this.videoPreview = null;\n }\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n private async submitVideoFeedback(message: string | null, sendBtn: HTMLButtonElement): Promise<void> {\n if (!this.recordedBlob || this.isSendingVideo) return;\n\n this.isSendingVideo = true;\n sendBtn.disabled = true;\n sendBtn.textContent = \"Sending...\";\n\n const formData = new FormData();\n formData.append(\"video\", this.recordedBlob, `recording.${this.recordedBlob.type.includes(\"mp4\") ? \"mp4\" : \"webm\"}`);\n formData.append(\"pageUrl\", window.location.href);\n formData.append(\"visitorId\", this.host.visitorId);\n\n if (this.host.config.apiKey) {\n formData.append(\"apiKey\", this.host.config.apiKey);\n }\n if (message) {\n formData.append(\"message\", message);\n }\n if (this.host.consoleCapture) {\n formData.append(\"consoleErrors\", JSON.stringify(this.host.consoleCapture.errors));\n }\n if (this.networkCapture) {\n formData.append(\"networkErrors\", JSON.stringify(this.networkCapture.errors));\n }\n\n let videoFeedbackId: string | number | null = null;\n try {\n const response = await fetch(`${this.host.config.endpoint}/video`, {\n method: \"POST\",\n body: formData,\n });\n\n if (response.ok) {\n this.host.announceMsg(\"Recording sent\");\n try {\n const data = (await response.json()) as { id?: string | number };\n videoFeedbackId = data?.id ?? null;\n } catch {\n // Non-JSON / no id — the quest can still launch, just unlinked.\n }\n } else {\n console.error(\"Failed to submit video feedback:\", await response.text());\n this.host.announceMsg(\"Failed to send recording\", true);\n }\n } catch (error) {\n console.error(\"Failed to submit video feedback:\", error);\n this.host.announceMsg(\"Failed to send recording\", true);\n }\n\n this.isSendingVideo = false;\n this.removeVideoPreview();\n this.cleanupRecording();\n\n // Once the recording is safely sent, launch the linked quest (if any),\n // passing the new feedback record id so its response is joinable.\n await this.host.tryLaunchQuest(\"video\", videoFeedbackId);\n }\n\n private cleanupRecording(): void {\n this.isRecording = false;\n this.removeRecordingIndicator();\n\n if (this.videoRecorder) {\n this.videoRecorder.destroy();\n this.videoRecorder = null;\n }\n if (this.networkCapture) {\n this.networkCapture.restore();\n this.networkCapture = null;\n }\n if (this.recordedBlob) {\n this.recordedBlob = null;\n }\n\n this.host.setButtonsDisabled(false);\n\n // Reset overlay host pointer events if nothing else needs them.\n if (this.host.overlayShadowHost && this.host.state === \"IDLE\") {\n this.host.overlayShadowHost.style.pointerEvents = \"none\";\n }\n }\n}\n"],"names":["d","h","tag","props","children","el","k","child","RecordingController","host","e","redactionElements","captureNetworkErrors","createVideoRecorder","elapsed","blob","error","root","boxHost","container","tick","target","isEmbedElement","idx","label","n","layer","r","record","picks","timer","remaining","seconds","m","s","videoUrl","titleId","title","videoEl","textarea","sendBtn","box","message","formData","videoFeedbackId","response"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ async function v(m = {}) {
2
+ const { quality: g = 1, maxWidth: w = 1280, maxHeight: p = 800 } = m;
3
+ try {
4
+ if (!navigator.mediaDevices?.getDisplayMedia)
5
+ return console.warn("Screen Capture API not available"), null;
6
+ const a = await navigator.mediaDevices.getDisplayMedia({
7
+ preferCurrentTab: !0,
8
+ video: {
9
+ displaySurface: "browser"
10
+ }
11
+ }), i = a.getVideoTracks()[0], r = i.getSettings(), e = document.createElement("video");
12
+ e.srcObject = a, e.muted = !0, await new Promise((t) => {
13
+ e.onloadedmetadata = () => {
14
+ e.play(), t();
15
+ };
16
+ }), await new Promise((t) => {
17
+ const l = () => {
18
+ e.readyState >= 2 ? t() : requestAnimationFrame(l);
19
+ };
20
+ l();
21
+ }), await new Promise((t) => setTimeout(t, 100));
22
+ const o = r.width || e.videoWidth, c = r.height || e.videoHeight, s = Math.min(w / o, p / c, 1), d = Math.round(o * s), u = Math.round(c * s), n = document.createElement("canvas");
23
+ n.width = d, n.height = u;
24
+ const h = n.getContext("2d");
25
+ return h ? (h.drawImage(e, 0, 0, d, u), i.stop(), n.toDataURL("image/webp", g)) : (i.stop(), null);
26
+ } catch (a) {
27
+ return console.warn("Screenshot capture failed:", a), null;
28
+ }
29
+ }
30
+ export {
31
+ v as captureScreenshot
32
+ };
33
+ //# sourceMappingURL=screenshot-BkOCLuS2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot-BkOCLuS2.js","sources":["../../src/screenshot.ts"],"sourcesContent":["/**\n * Screenshot capture utility using native Screen Capture API\n */\n\nexport interface ScreenshotOptions {\n /** Quality of WebP compression (0-1). Default: 1.0 */\n quality?: number;\n /** Max width of the screenshot. Default: 1280 */\n maxWidth?: number;\n /** Max height of the screenshot. Default: 800 */\n maxHeight?: number;\n}\n\n/**\n * Captures a screenshot using the native Screen Capture API\n * Requires user permission but produces accurate results\n * Returns a base64-encoded WebP data URL\n */\nexport async function captureScreenshot(\n options: ScreenshotOptions = {}\n): Promise<string | null> {\n const { quality = 1.0, maxWidth = 1280, maxHeight = 800 } = options;\n\n try {\n // Check if API is available\n if (!navigator.mediaDevices?.getDisplayMedia) {\n console.warn('Screen Capture API not available');\n return null;\n }\n\n // Request screen capture - preferCurrentTab auto-selects current tab in supported browsers\n const stream = await navigator.mediaDevices.getDisplayMedia({\n preferCurrentTab: true,\n video: {\n displaySurface: 'browser',\n },\n } as DisplayMediaStreamOptions);\n\n // Get video track\n const track = stream.getVideoTracks()[0];\n const settings = track.getSettings();\n\n // Create video element\n const video = document.createElement('video');\n video.srcObject = stream;\n video.muted = true;\n\n await new Promise<void>((resolve) => {\n video.onloadedmetadata = () => {\n video.play();\n resolve();\n };\n });\n\n // Wait for video to have actual frame data\n await new Promise<void>((resolve) => {\n const checkFrame = () => {\n if (video.readyState >= 2) {\n resolve();\n } else {\n requestAnimationFrame(checkFrame);\n }\n };\n checkFrame();\n });\n\n // Extra delay to ensure frame is rendered\n await new Promise((resolve) => setTimeout(resolve, 100));\n\n // Calculate dimensions\n const srcWidth = settings.width || video.videoWidth;\n const srcHeight = settings.height || video.videoHeight;\n const scale = Math.min(maxWidth / srcWidth, maxHeight / srcHeight, 1);\n const width = Math.round(srcWidth * scale);\n const height = Math.round(srcHeight * scale);\n\n // Draw to canvas\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n track.stop();\n return null;\n }\n\n ctx.drawImage(video, 0, 0, width, height);\n\n // Stop the stream\n track.stop();\n\n return canvas.toDataURL('image/webp', quality);\n } catch (error) {\n console.warn('Screenshot capture failed:', error);\n return null;\n }\n}\n"],"names":["captureScreenshot","options","quality","maxWidth","maxHeight","stream","track","settings","video","resolve","checkFrame","srcWidth","srcHeight","scale","width","height","canvas","ctx","error"],"mappings":"AAkBA,eAAsBA,EACpBC,IAA6B,IACL;AACxB,QAAM,EAAE,SAAAC,IAAU,GAAK,UAAAC,IAAW,MAAM,WAAAC,IAAY,QAAQH;AAE5D,MAAI;AAEF,QAAI,CAAC,UAAU,cAAc;AAC3B,qBAAQ,KAAK,kCAAkC,GACxC;AAIT,UAAMI,IAAS,MAAM,UAAU,aAAa,gBAAgB;AAAA,MAC1D,kBAAkB;AAAA,MAClB,OAAO;AAAA,QACL,gBAAgB;AAAA,MAAA;AAAA,IAClB,CAC4B,GAGxBC,IAAQD,EAAO,eAAA,EAAiB,CAAC,GACjCE,IAAWD,EAAM,YAAA,GAGjBE,IAAQ,SAAS,cAAc,OAAO;AAC5C,IAAAA,EAAM,YAAYH,GAClBG,EAAM,QAAQ,IAEd,MAAM,IAAI,QAAc,CAACC,MAAY;AACnC,MAAAD,EAAM,mBAAmB,MAAM;AAC7B,QAAAA,EAAM,KAAA,GACNC,EAAA;AAAA,MACF;AAAA,IACF,CAAC,GAGD,MAAM,IAAI,QAAc,CAACA,MAAY;AACnC,YAAMC,IAAa,MAAM;AACvB,QAAIF,EAAM,cAAc,IACtBC,EAAA,IAEA,sBAAsBC,CAAU;AAAA,MAEpC;AACA,MAAAA,EAAA;AAAA,IACF,CAAC,GAGD,MAAM,IAAI,QAAQ,CAACD,MAAY,WAAWA,GAAS,GAAG,CAAC;AAGvD,UAAME,IAAWJ,EAAS,SAASC,EAAM,YACnCI,IAAYL,EAAS,UAAUC,EAAM,aACrCK,IAAQ,KAAK,IAAIV,IAAWQ,GAAUP,IAAYQ,GAAW,CAAC,GAC9DE,IAAQ,KAAK,MAAMH,IAAWE,CAAK,GACnCE,IAAS,KAAK,MAAMH,IAAYC,CAAK,GAGrCG,IAAS,SAAS,cAAc,QAAQ;AAC9C,IAAAA,EAAO,QAAQF,GACfE,EAAO,SAASD;AAEhB,UAAME,IAAMD,EAAO,WAAW,IAAI;AAClC,WAAKC,KAKLA,EAAI,UAAUT,GAAO,GAAG,GAAGM,GAAOC,CAAM,GAGxCT,EAAM,KAAA,GAECU,EAAO,UAAU,cAAcd,CAAO,MAT3CI,EAAM,KAAA,GACC;AAAA,EASX,SAASY,GAAO;AACd,mBAAQ,KAAK,8BAA8BA,CAAK,GACzC;AAAA,EACT;AACF;"}
@@ -0,0 +1,63 @@
1
+ const w = "https://qaid.dev/lib/html2canvas.min.js";
2
+ let a = null;
3
+ function g() {
4
+ if (typeof document > "u" || typeof window > "u")
5
+ return !1;
6
+ const n = document.createElement("canvas");
7
+ return typeof n.getContext == "function" && !!n.getContext("2d");
8
+ }
9
+ function f() {
10
+ return window.html2canvas ? Promise.resolve(!0) : a || (a = new Promise((n) => {
11
+ if (!document.querySelector(
12
+ `script[src="${w}"]`
13
+ )) {
14
+ const e = document.createElement("script");
15
+ e.src = w, e.async = !0, document.head.appendChild(e);
16
+ }
17
+ const r = Date.now(), i = () => {
18
+ if (window.html2canvas) {
19
+ n(!0);
20
+ return;
21
+ }
22
+ if (Date.now() - r > 1e4) {
23
+ a = null, n(!1);
24
+ return;
25
+ }
26
+ setTimeout(i, 50);
27
+ };
28
+ i();
29
+ }), a);
30
+ }
31
+ async function L(n = {}) {
32
+ const { quality: c = 0.8, maxWidth: r = 1280, maxHeight: i = 800 } = n;
33
+ try {
34
+ if (!await f() || !window.html2canvas)
35
+ return console.warn("html2canvas failed to load"), null;
36
+ const s = await window.html2canvas(document.body, {
37
+ useCORS: !0,
38
+ allowTaint: !1,
39
+ logging: !1,
40
+ windowWidth: document.documentElement.clientWidth,
41
+ windowHeight: document.documentElement.clientHeight,
42
+ width: document.documentElement.clientWidth,
43
+ height: document.documentElement.clientHeight,
44
+ x: window.scrollX,
45
+ y: window.scrollY,
46
+ ignoreElements: (t) => t instanceof HTMLElement ? t.classList.contains("qaid-buttons") || t.classList.contains("qaid-targeting-overlay") || t.classList.contains("qaid-modal-container") || t.classList.contains("qaid-bottom-sheet") || t.classList.contains("qaid-backdrop") || t.classList.contains("qaid-selected-marker") || t.classList.contains("qaid-tooltip-text") || t.classList.contains("qaid-recording-indicator") || t.classList.contains("qaid-video-preview") || t.className?.toString().startsWith?.("qaid-") : !1
47
+ }), l = s.width, u = s.height, d = Math.min(r / l, i / u, 1);
48
+ if (d < 1) {
49
+ const t = Math.round(l * d), m = Math.round(u * d), o = document.createElement("canvas");
50
+ o.width = t, o.height = m;
51
+ const h = o.getContext("2d");
52
+ return h ? (h.drawImage(s, 0, 0, t, m), o.toDataURL("image/webp", c)) : null;
53
+ }
54
+ return s.toDataURL("image/webp", c);
55
+ } catch (e) {
56
+ return console.warn("DOM screenshot capture failed:", e), null;
57
+ }
58
+ }
59
+ export {
60
+ L as captureDomScreenshot,
61
+ g as isDomScreenshotSupported
62
+ };
63
+ //# sourceMappingURL=screenshot-dom-CME7tYRE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot-dom-CME7tYRE.js","sources":["../../src/screenshot-dom.ts"],"sourcesContent":["/**\n * DOM-based screenshot capture using html2canvas\n * No browser permission dialog required - reconstructs the page from the DOM\n */\n\nexport interface DomScreenshotOptions {\n /** Quality of WebP compression (0-1). Default: 0.8 */\n quality?: number;\n /** Max width of the screenshot. Default: 1280 */\n maxWidth?: number;\n /** Max height of the screenshot. Default: 800 */\n maxHeight?: number;\n}\n\ndeclare global {\n interface Window {\n html2canvas?: (\n element: HTMLElement,\n options?: Record<string, unknown>\n ) => Promise<HTMLCanvasElement>;\n }\n}\n\nconst SCRIPT_URL = \"https://qaid.dev/lib/html2canvas.min.js\";\nconst LOAD_TIMEOUT = 10_000;\nconst POLL_INTERVAL = 50;\n\nlet loadPromise: Promise<boolean> | null = null;\n\n/**\n * Check if DOM screenshot capture is supported\n * Requires canvas and basic DOM APIs\n */\nexport function isDomScreenshotSupported(): boolean {\n if (typeof document === \"undefined\" || typeof window === \"undefined\") {\n return false;\n }\n // Check canvas support\n const canvas = document.createElement(\"canvas\");\n return typeof canvas.getContext === \"function\" && !!canvas.getContext(\"2d\");\n}\n\n/**\n * Load html2canvas script if not already loaded\n */\nfunction loadHtml2Canvas(): Promise<boolean> {\n if (window.html2canvas) return Promise.resolve(true);\n\n if (loadPromise) return loadPromise;\n\n loadPromise = new Promise<boolean>((resolve) => {\n // Check if script tag already exists\n const existing = document.querySelector(\n `script[src=\"${SCRIPT_URL}\"]`\n ) as HTMLScriptElement | null;\n\n if (!existing) {\n const script = document.createElement(\"script\");\n script.src = SCRIPT_URL;\n script.async = true;\n document.head.appendChild(script);\n }\n\n // Poll for window.html2canvas availability\n const start = Date.now();\n const poll = () => {\n if (window.html2canvas) {\n resolve(true);\n return;\n }\n if (Date.now() - start > LOAD_TIMEOUT) {\n loadPromise = null;\n resolve(false);\n return;\n }\n setTimeout(poll, POLL_INTERVAL);\n };\n poll();\n });\n\n return loadPromise;\n}\n\n/**\n * Capture a screenshot of the current page using html2canvas\n * Returns a base64-encoded WebP data URL, or null on failure\n */\nexport async function captureDomScreenshot(\n options: DomScreenshotOptions = {}\n): Promise<string | null> {\n const { quality = 0.8, maxWidth = 1280, maxHeight = 800 } = options;\n\n try {\n const loaded = await loadHtml2Canvas();\n if (!loaded || !window.html2canvas) {\n console.warn(\"html2canvas failed to load\");\n return null;\n }\n\n const canvas = await window.html2canvas(document.body, {\n useCORS: true,\n allowTaint: false,\n logging: false,\n windowWidth: document.documentElement.clientWidth,\n windowHeight: document.documentElement.clientHeight,\n width: document.documentElement.clientWidth,\n height: document.documentElement.clientHeight,\n x: window.scrollX,\n y: window.scrollY,\n ignoreElements: (el: Element) => {\n // Hide all qaid elements from the screenshot\n if (el instanceof HTMLElement) {\n return (\n el.classList.contains(\"qaid-buttons\") ||\n el.classList.contains(\"qaid-targeting-overlay\") ||\n el.classList.contains(\"qaid-modal-container\") ||\n el.classList.contains(\"qaid-bottom-sheet\") ||\n el.classList.contains(\"qaid-backdrop\") ||\n el.classList.contains(\"qaid-selected-marker\") ||\n el.classList.contains(\"qaid-tooltip-text\") ||\n el.classList.contains(\"qaid-recording-indicator\") ||\n el.classList.contains(\"qaid-video-preview\") ||\n el.className?.toString().startsWith?.(\"qaid-\")\n );\n }\n return false;\n },\n });\n\n // Scale down if needed\n const srcWidth = canvas.width;\n const srcHeight = canvas.height;\n const scale = Math.min(maxWidth / srcWidth, maxHeight / srcHeight, 1);\n\n if (scale < 1) {\n const scaledWidth = Math.round(srcWidth * scale);\n const scaledHeight = Math.round(srcHeight * scale);\n\n const scaledCanvas = document.createElement(\"canvas\");\n scaledCanvas.width = scaledWidth;\n scaledCanvas.height = scaledHeight;\n\n const ctx = scaledCanvas.getContext(\"2d\");\n if (!ctx) return null;\n\n ctx.drawImage(canvas, 0, 0, scaledWidth, scaledHeight);\n return scaledCanvas.toDataURL(\"image/webp\", quality);\n }\n\n return canvas.toDataURL(\"image/webp\", quality);\n } catch (error) {\n console.warn(\"DOM screenshot capture failed:\", error);\n return null;\n }\n}\n"],"names":["SCRIPT_URL","loadPromise","isDomScreenshotSupported","canvas","loadHtml2Canvas","resolve","script","start","poll","captureDomScreenshot","options","quality","maxWidth","maxHeight","el","srcWidth","srcHeight","scale","scaledWidth","scaledHeight","scaledCanvas","ctx","error"],"mappings":"AAuBA,MAAMA,IAAa;AAInB,IAAIC,IAAuC;AAMpC,SAASC,IAAoC;AAClD,MAAI,OAAO,WAAa,OAAe,OAAO,SAAW;AACvD,WAAO;AAGT,QAAMC,IAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,OAAOA,EAAO,cAAe,cAAc,CAAC,CAACA,EAAO,WAAW,IAAI;AAC5E;AAKA,SAASC,IAAoC;AAC3C,SAAI,OAAO,cAAoB,QAAQ,QAAQ,EAAI,IAE/CH,MAEJA,IAAc,IAAI,QAAiB,CAACI,MAAY;AAM9C,QAAI,CAJa,SAAS;AAAA,MACxB,eAAeL,CAAU;AAAA,IAAA,GAGZ;AACb,YAAMM,IAAS,SAAS,cAAc,QAAQ;AAC9C,MAAAA,EAAO,MAAMN,GACbM,EAAO,QAAQ,IACf,SAAS,KAAK,YAAYA,CAAM;AAAA,IAClC;AAGA,UAAMC,IAAQ,KAAK,IAAA,GACbC,IAAO,MAAM;AACjB,UAAI,OAAO,aAAa;AACtB,QAAAH,EAAQ,EAAI;AACZ;AAAA,MACF;AACA,UAAI,KAAK,QAAQE,IAAQ,KAAc;AACrC,QAAAN,IAAc,MACdI,EAAQ,EAAK;AACb;AAAA,MACF;AACA,iBAAWG,GAAM,EAAa;AAAA,IAChC;AACA,IAAAA,EAAA;AAAA,EACF,CAAC,GAEMP;AACT;AAMA,eAAsBQ,EACpBC,IAAgC,IACR;AACxB,QAAM,EAAE,SAAAC,IAAU,KAAK,UAAAC,IAAW,MAAM,WAAAC,IAAY,QAAQH;AAE5D,MAAI;AAEF,QAAI,CADW,MAAMN,EAAA,KACN,CAAC,OAAO;AACrB,qBAAQ,KAAK,4BAA4B,GAClC;AAGT,UAAMD,IAAS,MAAM,OAAO,YAAY,SAAS,MAAM;AAAA,MACrD,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,aAAa,SAAS,gBAAgB;AAAA,MACtC,cAAc,SAAS,gBAAgB;AAAA,MACvC,OAAO,SAAS,gBAAgB;AAAA,MAChC,QAAQ,SAAS,gBAAgB;AAAA,MACjC,GAAG,OAAO;AAAA,MACV,GAAG,OAAO;AAAA,MACV,gBAAgB,CAACW,MAEXA,aAAc,cAEdA,EAAG,UAAU,SAAS,cAAc,KACpCA,EAAG,UAAU,SAAS,wBAAwB,KAC9CA,EAAG,UAAU,SAAS,sBAAsB,KAC5CA,EAAG,UAAU,SAAS,mBAAmB,KACzCA,EAAG,UAAU,SAAS,eAAe,KACrCA,EAAG,UAAU,SAAS,sBAAsB,KAC5CA,EAAG,UAAU,SAAS,mBAAmB,KACzCA,EAAG,UAAU,SAAS,0BAA0B,KAChDA,EAAG,UAAU,SAAS,oBAAoB,KAC1CA,EAAG,WAAW,SAAA,EAAW,aAAa,OAAO,IAG1C;AAAA,IACT,CACD,GAGKC,IAAWZ,EAAO,OAClBa,IAAYb,EAAO,QACnBc,IAAQ,KAAK,IAAIL,IAAWG,GAAUF,IAAYG,GAAW,CAAC;AAEpE,QAAIC,IAAQ,GAAG;AACb,YAAMC,IAAc,KAAK,MAAMH,IAAWE,CAAK,GACzCE,IAAe,KAAK,MAAMH,IAAYC,CAAK,GAE3CG,IAAe,SAAS,cAAc,QAAQ;AACpD,MAAAA,EAAa,QAAQF,GACrBE,EAAa,SAASD;AAEtB,YAAME,IAAMD,EAAa,WAAW,IAAI;AACxC,aAAKC,KAELA,EAAI,UAAUlB,GAAQ,GAAG,GAAGe,GAAaC,CAAY,GAC9CC,EAAa,UAAU,cAAcT,CAAO,KAHlC;AAAA,IAInB;AAEA,WAAOR,EAAO,UAAU,cAAcQ,CAAO;AAAA,EAC/C,SAASW,GAAO;AACd,mBAAQ,KAAK,kCAAkCA,CAAK,GAC7C;AAAA,EACT;AACF;"}