@qaiddev/thumbs-embed 1.5.1 → 1.6.1

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.
@@ -1,63 +0,0 @@
1
- function a(o) {
2
- const s = typeof o == "string" ? o : JSON.stringify(o);
3
- return s.length > 4096 ? s.slice(0, 4096) + "…[truncated]" : s;
4
- }
5
- function d(o, s) {
6
- o.length >= 20 && o.shift(), o.push(s);
7
- }
8
- async function h(o) {
9
- try {
10
- const s = await o.clone().text();
11
- return a(s);
12
- } catch {
13
- return;
14
- }
15
- }
16
- function y() {
17
- const o = [], s = window.fetch;
18
- window.fetch = async function(e, t) {
19
- const n = typeof e == "string" ? e : e instanceof URL ? e.toString() : e.url, u = t?.method ?? (typeof e == "object" && "method" in e ? e.method : "GET");
20
- let p;
21
- t?.body && (p = a(t.body));
22
- const r = await s.apply(window, [e, t]);
23
- if (r.status >= 400) {
24
- const f = await h(r);
25
- d(o, {
26
- url: n,
27
- method: u.toUpperCase(),
28
- status: r.status,
29
- statusText: r.statusText,
30
- requestBody: p,
31
- responseBody: f,
32
- timestamp: Date.now()
33
- });
34
- }
35
- return r;
36
- };
37
- const c = XMLHttpRequest.prototype.open, i = XMLHttpRequest.prototype.send;
38
- return XMLHttpRequest.prototype.open = function(e, t, ...n) {
39
- return this._qaid_method = e, this._qaid_url = typeof t == "string" ? t : t.toString(), c.apply(this, [e, t, ...n]);
40
- }, XMLHttpRequest.prototype.send = function(e) {
41
- const t = this, n = e ? a(e) : void 0;
42
- return t.addEventListener("load", function() {
43
- t.status >= 400 && d(o, {
44
- url: t._qaid_url,
45
- method: t._qaid_method.toUpperCase(),
46
- status: t.status,
47
- statusText: t.statusText,
48
- requestBody: n,
49
- responseBody: a(t.responseText),
50
- timestamp: Date.now()
51
- });
52
- }), i.apply(this, [e]);
53
- }, {
54
- errors: o,
55
- restore: () => {
56
- window.fetch = s, XMLHttpRequest.prototype.open = c, XMLHttpRequest.prototype.send = i;
57
- }
58
- };
59
- }
60
- export {
61
- y as c
62
- };
63
- //# sourceMappingURL=network-capture-DMbMwwwR.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"network-capture-DMbMwwwR.js","sources":["../../src/network-capture.ts"],"sourcesContent":["/**\n * Network capture utilities\n * Intercepts fetch() and XMLHttpRequest to capture 4xx/5xx errors\n */\n\nimport type { NetworkError } from \"./types\";\n\nconst MAX_ENTRIES = 20;\nconst MAX_BODY_SIZE = 4096; // 4KB\n\nexport interface NetworkCapture {\n errors: NetworkError[];\n restore: () => void;\n}\n\n/**\n * Truncate a body to max size. Callers must guard against null/undefined.\n */\nexport function truncateBody(body: unknown): string {\n const str = typeof body === \"string\" ? body : JSON.stringify(body);\n if (str.length > MAX_BODY_SIZE) {\n return str.slice(0, MAX_BODY_SIZE) + \"…[truncated]\";\n }\n return str;\n}\n\n/**\n * Add an error to the buffer, evicting oldest if at capacity\n */\nexport function addEntry(errors: NetworkError[], entry: NetworkError): void {\n if (errors.length >= MAX_ENTRIES) {\n errors.shift();\n }\n errors.push(entry);\n}\n\n/**\n * Read response body as text, with size limit\n */\nasync function safeReadBody(response: Response): Promise<string | undefined> {\n try {\n const text = await response.clone().text();\n return truncateBody(text);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Intercept fetch() and XMLHttpRequest to capture network errors (4xx/5xx)\n */\nexport function captureNetworkErrors(): NetworkCapture {\n const errors: NetworkError[] = [];\n const originalFetch = window.fetch;\n\n // Monkey-patch fetch\n window.fetch = async function (input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.toString() : input.url;\n const method = init?.method ?? (typeof input === \"object\" && \"method\" in input ? input.method : \"GET\");\n let requestBody: string | undefined;\n if (init?.body) {\n requestBody = truncateBody(init.body);\n }\n\n const response = await originalFetch.apply(window, [input, init]);\n\n if (response.status >= 400) {\n const responseBody = await safeReadBody(response);\n addEntry(errors, {\n url,\n method: method.toUpperCase(),\n status: response.status,\n statusText: response.statusText,\n requestBody,\n responseBody,\n timestamp: Date.now(),\n });\n }\n\n return response;\n };\n\n // Monkey-patch XMLHttpRequest\n const originalXHROpen = XMLHttpRequest.prototype.open;\n const originalXHRSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (method: string, url: string | URL, ...rest: unknown[]) {\n (this as XMLHttpRequest & { _qaid_method: string; _qaid_url: string })._qaid_method = method;\n (this as XMLHttpRequest & { _qaid_url: string })._qaid_url = typeof url === \"string\" ? url : url.toString();\n return originalXHROpen.apply(this, [method, url, ...rest] as Parameters<typeof originalXHROpen>);\n };\n\n XMLHttpRequest.prototype.send = function (body?: Document | XMLHttpRequestBodyInit | null) {\n const xhr = this as XMLHttpRequest & { _qaid_method: string; _qaid_url: string };\n const requestBody = body ? truncateBody(body) : undefined;\n\n xhr.addEventListener(\"load\", function () {\n if (xhr.status >= 400) {\n addEntry(errors, {\n url: xhr._qaid_url,\n method: xhr._qaid_method.toUpperCase(),\n status: xhr.status,\n statusText: xhr.statusText,\n requestBody,\n responseBody: truncateBody(xhr.responseText),\n timestamp: Date.now(),\n });\n }\n });\n\n return originalXHRSend.apply(this, [body]);\n };\n\n return {\n errors,\n restore: () => {\n window.fetch = originalFetch;\n XMLHttpRequest.prototype.open = originalXHROpen;\n XMLHttpRequest.prototype.send = originalXHRSend;\n },\n };\n}\n"],"names":["truncateBody","body","str","addEntry","errors","entry","safeReadBody","response","text","captureNetworkErrors","originalFetch","input","init","url","method","requestBody","responseBody","originalXHROpen","originalXHRSend","rest","xhr"],"mappings":"AAkBO,SAASA,EAAaC,GAAuB;AAClD,QAAMC,IAAM,OAAOD,KAAS,WAAWA,IAAO,KAAK,UAAUA,CAAI;AACjE,SAAIC,EAAI,SAAS,OACRA,EAAI,MAAM,GAAG,IAAa,IAAI,iBAEhCA;AACT;AAKO,SAASC,EAASC,GAAwBC,GAA2B;AAC1E,EAAID,EAAO,UAAU,MACnBA,EAAO,MAAA,GAETA,EAAO,KAAKC,CAAK;AACnB;AAKA,eAAeC,EAAaC,GAAiD;AAC3E,MAAI;AACF,UAAMC,IAAO,MAAMD,EAAS,MAAA,EAAQ,KAAA;AACpC,WAAOP,EAAaQ,CAAI;AAAA,EAC1B,QAAQ;AACN;AAAA,EACF;AACF;AAKO,SAASC,IAAuC;AACrD,QAAML,IAAyB,CAAA,GACzBM,IAAgB,OAAO;AAG7B,SAAO,QAAQ,eAAgBC,GAA0BC,GAAuC;AAC9F,UAAMC,IAAM,OAAOF,KAAU,WAAWA,IAAQA,aAAiB,MAAMA,EAAM,SAAA,IAAaA,EAAM,KAC1FG,IAASF,GAAM,WAAW,OAAOD,KAAU,YAAY,YAAYA,IAAQA,EAAM,SAAS;AAChG,QAAII;AACJ,IAAIH,GAAM,SACRG,IAAcf,EAAaY,EAAK,IAAI;AAGtC,UAAML,IAAW,MAAMG,EAAc,MAAM,QAAQ,CAACC,GAAOC,CAAI,CAAC;AAEhE,QAAIL,EAAS,UAAU,KAAK;AAC1B,YAAMS,IAAe,MAAMV,EAAaC,CAAQ;AAChD,MAAAJ,EAASC,GAAQ;AAAA,QACf,KAAAS;AAAA,QACA,QAAQC,EAAO,YAAA;AAAA,QACf,QAAQP,EAAS;AAAA,QACjB,YAAYA,EAAS;AAAA,QACrB,aAAAQ;AAAA,QACA,cAAAC;AAAA,QACA,WAAW,KAAK,IAAA;AAAA,MAAI,CACrB;AAAA,IACH;AAEA,WAAOT;AAAA,EACT;AAGA,QAAMU,IAAkB,eAAe,UAAU,MAC3CC,IAAkB,eAAe,UAAU;AAEjD,wBAAe,UAAU,OAAO,SAAUJ,GAAgBD,MAAsBM,GAAiB;AAC9F,gBAAsE,eAAeL,GACrF,KAAgD,YAAY,OAAOD,KAAQ,WAAWA,IAAMA,EAAI,SAAA,GAC1FI,EAAgB,MAAM,MAAM,CAACH,GAAQD,GAAK,GAAGM,CAAI,CAAuC;AAAA,EACjG,GAEA,eAAe,UAAU,OAAO,SAAUlB,GAAiD;AACzF,UAAMmB,IAAM,MACNL,IAAcd,IAAOD,EAAaC,CAAI,IAAI;AAEhD,WAAAmB,EAAI,iBAAiB,QAAQ,WAAY;AACvC,MAAIA,EAAI,UAAU,OAChBjB,EAASC,GAAQ;AAAA,QACf,KAAKgB,EAAI;AAAA,QACT,QAAQA,EAAI,aAAa,YAAA;AAAA,QACzB,QAAQA,EAAI;AAAA,QACZ,YAAYA,EAAI;AAAA,QAChB,aAAAL;AAAA,QACA,cAAcf,EAAaoB,EAAI,YAAY;AAAA,QAC3C,WAAW,KAAK,IAAA;AAAA,MAAI,CACrB;AAAA,IAEL,CAAC,GAEMF,EAAgB,MAAM,MAAM,CAACjB,CAAI,CAAC;AAAA,EAC3C,GAEO;AAAA,IACL,QAAAG;AAAA,IACA,SAAS,MAAM;AACb,aAAO,QAAQM,GACf,eAAe,UAAU,OAAOO,GAChC,eAAe,UAAU,OAAOC;AAAA,IAClC;AAAA,EAAA;AAEJ;"}
@@ -1 +0,0 @@
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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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"targeting-DwdDr9Cm.js","sources":["../../src/element-selector.ts","../../src/targeting.ts"],"sourcesContent":["/**\n * Element selector generation utilities\n */\n\n/** Data attributes to look for (in priority order) */\nexport const DATA_ATTRS = [\"data-comp\", \"data-qa\", \"data-testid\", \"data-id\"];\n\nexport interface ElementInfo {\n selector: string;\n text: string;\n dataAttr: string | null;\n}\n\n/**\n * Find the first matching data attribute on an element or its ancestors\n */\nexport function findDataAttribute(\n element: Element,\n stopAt: Element = document.body\n): string | null {\n let current: Element | null = element;\n\n while (current && current !== stopAt) {\n for (const attr of DATA_ATTRS) {\n const value = current.getAttribute(attr);\n if (value) {\n return `${attr}=\"${value}\"`;\n }\n }\n current = current.parentElement;\n }\n\n return null;\n}\n\n/**\n * Get truncated text content from an element\n */\nexport function getTruncatedText(element: Element, maxLength = 100): string {\n const textContent = element.textContent?.trim().slice(0, maxLength) || \"\";\n return textContent.length === maxLength ? textContent + \"...\" : textContent;\n}\n\n/**\n * Get the nth-child index of an element among its siblings\n */\nfunction getNthChildIndex(element: Element): number {\n let index = 1;\n let sibling = element.previousElementSibling;\n while (sibling) {\n index++;\n sibling = sibling.previousElementSibling;\n }\n return index;\n}\n\n/**\n * Generate an nth-child path from body to the element\n * This is always a valid CSS selector and uniquely identifies the element\n * Exported for testing\n */\nexport function generateNthChildPath(element: Element): string {\n const path: string[] = [];\n let current: Element | null = element;\n\n while (current && current !== document.body && current !== document.documentElement) {\n const tag = current.tagName.toLowerCase();\n const index = getNthChildIndex(current);\n path.unshift(`${tag}:nth-child(${index})`);\n current = current.parentElement;\n }\n\n return path.length > 0 ? `body > ${path.join(\" > \")}` : \"body\";\n}\n\n/**\n * Escape special CSS characters in a string (for IDs with special chars)\n */\nfunction escapeCSSIdentifier(str: string): string {\n return str.replace(/([!\"#$%&'()*+,./:;<=>?@[\\\\\\]^`{|}~])/g, \"\\\\$1\");\n}\n\n/**\n * Generate a selector string for an element\n * Priority: data attribute > ID > nth-child path\n */\nexport function generateSelector(element: Element): string {\n // Try data attribute first (on element or ancestors) - most stable\n const dataAttr = findDataAttribute(element);\n if (dataAttr) {\n return `[${dataAttr}]`;\n }\n\n // Try ID (escaped for special characters)\n if (element.id) {\n return `#${escapeCSSIdentifier(element.id)}`;\n }\n\n // Fall back to nth-child path - always valid and unique\n return generateNthChildPath(element);\n}\n\n/**\n * Generate complete element info for feedback\n */\nexport function generateElementInfo(element: Element): ElementInfo {\n const text = getTruncatedText(element);\n const dataAttr = findDataAttribute(element);\n const selector = generateSelector(element);\n\n return { selector, text, dataAttr };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Keyboard element targeting */\n/* */\n/* The mouse targeting path in embed.ts resolves the target with */\n/* elementFromPoint(clientX, clientY). A keyboard-activated thumb produces a */\n/* synthetic click at (0, 0), so a keyboard-only user can never point at a */\n/* real element. This controller provides a coordinate-free alternative: */\n/* Tab / arrow keys move a highlight between candidate elements (seeded from */\n/* document.activeElement), Enter / Space selects, Escape cancels. It never */\n/* hides the cursor — it drives the *native* focus ring instead, so both */\n/* sighted keyboard users and screen readers can follow the highlight. */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Default set of \"targetable\" elements for keyboard navigation: focusable\n * controls, ARIA-role'd nodes, common semantic content, and anything already\n * carrying one of the stable data attributes we key selectors off of.\n */\nexport const TARGETABLE_SELECTOR: string = [\n \"a[href]\",\n \"button\",\n \"input:not([type=hidden])\",\n \"select\",\n \"textarea\",\n \"summary\",\n \"[tabindex]\",\n \"[role]\",\n \"img\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"p\",\n \"li\",\n \"label\",\n ...DATA_ATTRS.map((attr) => `[${attr}]`),\n].join(\",\");\n\n/** Tag names that already take keyboard focus without an added tabindex. */\nconst NATIVELY_FOCUSABLE = /^(?:A|BUTTON|INPUT|SELECT|TEXTAREA|SUMMARY)$/;\n\n/**\n * Best-effort visibility test used to keep invisible nodes out of the keyboard\n * candidate list. Deliberately layout-free (no getBoundingClientRect) so it is\n * stable in headless/test environments: it only rejects nodes that are\n * explicitly hidden via the `hidden` attribute, `aria-hidden`, or a computed\n * `display:none` / `visibility:hidden`.\n */\nexport function isElementVisible(element: Element): boolean {\n if (element.hasAttribute(\"hidden\")) return false;\n if (element.getAttribute(\"aria-hidden\") === \"true\") return false;\n const view = element.ownerDocument?.defaultView;\n if (view && typeof view.getComputedStyle === \"function\") {\n const style = view.getComputedStyle(element);\n if (style.display === \"none\" || style.visibility === \"hidden\") return false;\n }\n return true;\n}\n\nexport interface CollectTargetableOptions {\n /** Where to search for candidates (default `document.body`). */\n root?: ParentNode;\n /** CSS selector for candidate elements (default {@link TARGETABLE_SELECTOR}). */\n selector?: string;\n /** Return true to drop an element (e.g. the embed's own shadow hosts). */\n isExcluded?: (element: Element) => boolean;\n /** Visibility predicate (default {@link isElementVisible}). */\n isVisible?: (element: Element) => boolean;\n}\n\n/**\n * Collect the ordered list of elements a keyboard user can target. Document\n * order matches the natural Tab order closely enough for feedback targeting.\n */\nexport function collectTargetableElements(\n options: CollectTargetableOptions = {}\n): Element[] {\n const root = options.root ?? document.body;\n const selector = options.selector ?? TARGETABLE_SELECTOR;\n const isExcluded = options.isExcluded ?? (() => false);\n const isVisible = options.isVisible ?? isElementVisible;\n\n return Array.from(root.querySelectorAll(selector)).filter(\n (element) => !isExcluded(element) && isVisible(element)\n );\n}\n\nexport interface KeyboardTargetingOptions {\n /**\n * Explicit candidate list. When omitted, candidates are collected via\n * {@link collectTargetableElements} using `root` / `selector` / `isExcluded`\n * / `isVisible`.\n */\n candidates?: Element[];\n /** Passed through to {@link collectTargetableElements} when `candidates` is omitted. */\n root?: ParentNode;\n /** Passed through to {@link collectTargetableElements} when `candidates` is omitted. */\n selector?: string;\n /** Passed through to {@link collectTargetableElements} when `candidates` is omitted. */\n isExcluded?: (element: Element) => boolean;\n /** Passed through to {@link collectTargetableElements} when `candidates` is omitted. */\n isVisible?: (element: Element) => boolean;\n /**\n * Element to start the highlight on. Defaults to `document.activeElement`\n * when it is one of the candidates, otherwise the first candidate.\n */\n initial?: Element | null;\n /**\n * Move native focus to the highlighted element (default true). This is what\n * keeps a screen reader / focus ring on the highlight; non-focusable nodes\n * get a temporary `tabindex=\"-1\"` that is removed on {@link stop}. The cursor\n * is never hidden.\n */\n moveFocus?: boolean;\n /** Where the keydown listener is attached (default `document`). */\n eventTarget?: Document | HTMLElement;\n /** Fired whenever the highlighted candidate changes (and once on start). */\n onHighlight?: (element: Element, index: number) => void;\n /** Fired when the user commits a selection (Enter / Space). Auto-stops first. */\n onSelect: (element: Element) => void;\n /** Fired when the user cancels (Escape). Auto-stops first. */\n onCancel?: () => void;\n}\n\nexport interface KeyboardTargetingController {\n /** The (frozen) candidate list being navigated. */\n readonly candidates: readonly Element[];\n /** Index of the highlighted candidate, or -1 when there are none. */\n getIndex(): number;\n /** The highlighted candidate, or null when there are none. */\n getCurrent(): Element | null;\n /** Highlight the next candidate (wraps to the first). */\n next(): void;\n /** Highlight the previous candidate (wraps to the last). */\n prev(): void;\n /** Highlight a specific index (wraps out-of-range values). */\n moveTo(index: number): void;\n /** Commit the current candidate (fires onSelect) and stop. */\n select(): void;\n /** Cancel (fires onCancel) and stop. */\n cancel(): void;\n /**\n * Handle a keydown. Wired automatically to `eventTarget`; exposed so a host\n * can forward events from another surface (e.g. the embed's shadow root).\n */\n handleKey(event: KeyboardEvent): void;\n /** Remove the listener and restore any temporary tabindex. Idempotent. */\n stop(): void;\n}\n\n/**\n * Start a keyboard element-targeting session. The returned controller wires its\n * own capture-phase keydown listener on `eventTarget` and emits the initial\n * highlight synchronously before returning.\n */\nexport function startKeyboardTargeting(\n options: KeyboardTargetingOptions\n): KeyboardTargetingController {\n const candidates =\n options.candidates ??\n collectTargetableElements({\n root: options.root,\n selector: options.selector,\n isExcluded: options.isExcluded,\n isVisible: options.isVisible,\n });\n\n const moveFocus = options.moveFocus ?? true;\n const target: EventTarget = options.eventTarget ?? document;\n const addedTabindex = new Set<Element>();\n\n let index = -1;\n let stopped = false;\n\n // Seed the highlight from the currently focused element when it is a\n // candidate; this makes \"Tab to a control, then hit the thumb\" land on that\n // control rather than the top of the page.\n const seed =\n options.initial !== undefined\n ? options.initial\n : typeof document !== \"undefined\"\n ? document.activeElement\n : null;\n if (seed) {\n const seedIndex = candidates.indexOf(seed);\n if (seedIndex >= 0) index = seedIndex;\n }\n if (index < 0 && candidates.length > 0) index = 0;\n\n function focusCurrent(element: Element): void {\n if (!moveFocus) return;\n const focusable = element as HTMLElement;\n if (typeof focusable.focus !== \"function\") return;\n // Make non-focusable content (p, img, li, …) programmatically focusable so\n // the native focus ring and AT follow the highlight. Never hide the cursor.\n if (\n element.getAttribute(\"tabindex\") === null &&\n !NATIVELY_FOCUSABLE.test(element.tagName)\n ) {\n element.setAttribute(\"tabindex\", \"-1\");\n addedTabindex.add(element);\n }\n try {\n focusable.focus();\n } catch {\n /* focus can throw in detached/headless cases — highlight still works */\n }\n }\n\n function emitHighlight(): void {\n const element = candidates[index];\n if (!element) return;\n focusCurrent(element);\n options.onHighlight?.(element, index);\n }\n\n function moveTo(nextIndex: number): void {\n if (stopped || candidates.length === 0) return;\n const len = candidates.length;\n index = ((nextIndex % len) + len) % len; // wrap in both directions\n emitHighlight();\n }\n\n function next(): void {\n moveTo(index + 1);\n }\n\n function prev(): void {\n moveTo(index - 1);\n }\n\n function getCurrent(): Element | null {\n return candidates[index] ?? null;\n }\n\n function stop(): void {\n if (stopped) return;\n stopped = true;\n target.removeEventListener(\"keydown\", handleKey as EventListener, true);\n addedTabindex.forEach((element) => element.removeAttribute(\"tabindex\"));\n addedTabindex.clear();\n }\n\n function select(): void {\n if (stopped) return;\n const element = getCurrent();\n stop();\n if (element) options.onSelect(element);\n }\n\n function cancel(): void {\n if (stopped) return;\n stop();\n options.onCancel?.();\n }\n\n function handleKey(event: KeyboardEvent): void {\n if (stopped) return;\n switch (event.key) {\n case \"Tab\":\n event.preventDefault();\n event.stopPropagation();\n if (event.shiftKey) prev();\n else next();\n break;\n case \"ArrowDown\":\n case \"ArrowRight\":\n event.preventDefault();\n event.stopPropagation();\n next();\n break;\n case \"ArrowUp\":\n case \"ArrowLeft\":\n event.preventDefault();\n event.stopPropagation();\n prev();\n break;\n case \"Enter\":\n case \" \":\n case \"Spacebar\": // legacy key value\n event.preventDefault();\n event.stopPropagation();\n select();\n break;\n case \"Escape\":\n case \"Esc\": // legacy key value\n event.preventDefault();\n event.stopPropagation();\n cancel();\n break;\n default:\n break;\n }\n }\n\n target.addEventListener(\"keydown\", handleKey as EventListener, true);\n\n // Emit the initial highlight so the caller can position its highlight box.\n if (index >= 0) emitHighlight();\n\n return {\n candidates,\n getIndex: () => index,\n getCurrent,\n next,\n prev,\n moveTo,\n select,\n cancel,\n handleKey,\n stop,\n };\n}\n","/**\n * Element-targeting subsystem — extracted from embed.ts into its own lazily\n * loaded chunk (it also carries `element-selector`, ~14 KB). It's fetched the\n * first time the user starts targeting (and pre-warmed on thumb-button hover,\n * so the reticle appears without a perceptible wait).\n *\n * The controller owns the reticle/overlay, the mouse/touch/keyboard targeting\n * flows, and the selected-element marker; it talks back to the embed through\n * the narrow `TargetingHost` interface.\n */\n\nimport {\n generateElementInfo,\n startKeyboardTargeting,\n type KeyboardTargetingController,\n} from \"./element-selector\";\nimport {\n getElementAtPointUnderOverlay,\n isEmbedElement,\n getElementBounds,\n} from \"./dom-utils\";\nimport type {\n ResolvedFeedbackConfig,\n EmbedState,\n FeedbackData,\n SelectedBounds,\n} from \"./types\";\n\n/** A touch that moves more than this (px) is a scroll, not a tap-to-select. */\nconst TOUCH_TAP_SLOP = 12;\n\n/** The slice of the embed the targeting subsystem needs. */\nexport interface TargetingHost {\n readonly config: ResolvedFeedbackConfig;\n readonly cssVars: Record<string, string>;\n readonly state: EmbedState;\n readonly shadowHost: HTMLDivElement | null;\n readonly overlayShadowHost: HTMLDivElement | null;\n readonly boundKeyDown: (e: KeyboardEvent) => void;\n /** Live references, mutated in place. */\n readonly feedbackData: FeedbackData;\n readonly selectedBounds: SelectedBounds;\n setState(state: EmbedState): void;\n ensureOverlayHost(): ShadowRoot;\n applyVars(el: HTMLElement): void;\n announceMsg(message: string, assertive?: boolean): void;\n clearActiveThumb(): void;\n submitFeedback(): Promise<void>;\n}\n\nexport class TargetingController {\n private mousePos = { x: 0, y: 0 };\n private touchStartPos: { x: number; y: number } | null = null;\n private overlayContainer: HTMLDivElement | null = null;\n private captureLayer: HTMLDivElement | null = null;\n private crosshairH: HTMLDivElement | null = null;\n private crosshairV: HTMLDivElement | null = null;\n private scope: HTMLDivElement | null = null;\n private highlightBox: HTMLDivElement | null = null;\n private marker: HTMLDivElement | null = null;\n private keyboardController: KeyboardTargetingController | null = null;\n\n private boundMouseMove = (e: MouseEvent): void => this.handleMouseMove(e);\n private boundClick = (e: MouseEvent): void => this.handleClick(e);\n private boundTouchStart = (e: TouchEvent): void => this.handleTouchStart(e);\n private boundTouchEnd = (e: TouchEvent): void => this.handleTouchEnd(e);\n\n constructor(private host: TargetingHost) {}\n\n startPointer(type: \"up\" | \"down\", e: MouseEvent): void {\n this.host.setState(\"TARGETING\");\n this.host.feedbackData.feedbackType = type;\n this.host.feedbackData.elementSelector = null;\n this.host.feedbackData.elementText = null;\n this.host.selectedBounds.visible = false;\n\n // Track initial mouse position from the click event\n this.mousePos.x = e.clientX;\n this.mousePos.y = e.clientY;\n\n // Add class to body (light DOM — for cursor override)\n document.body.classList.add(\"qaid-targeting\");\n if (type === \"up\") {\n document.body.classList.add(\"qaid-type-up\");\n } else {\n document.body.classList.remove(\"qaid-type-up\");\n }\n\n // Set targeting colors on body so .qaid-highlight rules can resolve them\n document.body.style.setProperty(\"--qaid-positive\", this.host.cssVars[\"--qaid-positive\"]);\n document.body.style.setProperty(\"--qaid-negative\", this.host.cssVars[\"--qaid-negative\"]);\n\n this.createTargetingOverlay();\n\n // Add event listeners on document — overlay is pointer-events:none so scroll works naturally\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n document.addEventListener(\"mousemove\", this.boundMouseMove);\n document.addEventListener(\"click\", this.boundClick, true);\n // Touch: tap to select (scrolling stays enabled). touchstart is passive\n // (we only read the point); touchend is non-passive so a tap can\n // preventDefault the synthesised click that would otherwise activate the\n // targeted element.\n document.addEventListener(\"touchstart\", this.boundTouchStart, { passive: true });\n document.addEventListener(\"touchend\", this.boundTouchEnd, { passive: false });\n }\n\n /**\n * Keyboard-driven targeting. Mirrors startPointer minus the mouse plumbing:\n * no `qaid-targeting` body class (keeps the cursor visible for keyboard\n * users), no mouse reticle, and no document mouse/click listeners. The\n * KeyboardTargetingController owns Tab/Arrow/Enter/Space/Escape.\n */\n startKeyboard(type: \"up\" | \"down\"): void {\n this.host.setState(\"TARGETING\");\n this.host.feedbackData.feedbackType = type;\n this.host.feedbackData.elementSelector = null;\n this.host.feedbackData.elementText = null;\n this.host.selectedBounds.visible = false;\n\n document.body.style.setProperty(\"--qaid-positive\", this.host.cssVars[\"--qaid-positive\"]);\n document.body.style.setProperty(\"--qaid-negative\", this.host.cssVars[\"--qaid-negative\"]);\n\n // Build the highlight box, then hide the mouse reticle (meaningless here)\n this.createTargetingOverlay();\n if (this.crosshairH) this.crosshairH.style.display = \"none\";\n if (this.crosshairV) this.crosshairV.style.display = \"none\";\n if (this.scope) this.scope.style.display = \"none\";\n\n this.keyboardController = startKeyboardTargeting({\n isExcluded: (el) => isEmbedElement(el),\n onHighlight: (el) => {\n const rect = el.getBoundingClientRect();\n const b = this.highlightBox;\n if (b) {\n b.style.transform = `translate(${rect.left}px, ${rect.top}px)`;\n b.style.width = `${rect.width}px`;\n b.style.height = `${rect.height}px`;\n b.style.display = \"block\";\n }\n const { text } = generateElementInfo(el);\n this.host.announceMsg(`Targeting ${text || el.tagName.toLowerCase()}`);\n },\n onSelect: (el) => this.selectKeyboardTarget(el),\n onCancel: () => this.cancel(),\n });\n }\n\n private selectKeyboardTarget(el: Element): void {\n const bounds = getElementBounds(el, 8);\n Object.assign(this.host.selectedBounds, bounds, {\n clickX: bounds.x + bounds.width / 2,\n clickY: bounds.y + bounds.height / 2,\n visible: true,\n });\n\n const { selector, text } = generateElementInfo(el);\n this.host.feedbackData.elementSelector = selector;\n this.host.feedbackData.elementText = text;\n\n // Teardown (controller has already auto-stopped before onSelect fired)\n this.removeTargetingOverlay();\n document.body.classList.remove(\"qaid-targeting\", \"qaid-type-up\");\n document.body.style.removeProperty(\"--qaid-positive\");\n document.body.style.removeProperty(\"--qaid-negative\");\n this.keyboardController = null;\n this.host.clearActiveThumb();\n\n this.host.setState(\"SELECTED\");\n this.showSelectedMarker();\n void this.host.submitFeedback();\n }\n\n private createTargetingOverlay(): void {\n const root = this.host.ensureOverlayHost();\n\n this.overlayContainer = document.createElement(\"div\");\n this.overlayContainer.className = `qaid-targeting-overlay qaid-type-${this.host.feedbackData.feedbackType}`;\n\n // Capture layer (pointer-events:none — scroll passes through naturally)\n this.captureLayer = document.createElement(\"div\");\n this.captureLayer.className = \"qaid-capture-layer\";\n\n // Vignette\n const vignette = document.createElement(\"div\");\n vignette.className = \"qaid-vignette\";\n\n // Crosshairs\n this.crosshairH = document.createElement(\"div\");\n this.crosshairH.className = \"qaid-crosshair-h\";\n\n this.crosshairV = document.createElement(\"div\");\n this.crosshairV.className = \"qaid-crosshair-v\";\n\n // Scope\n this.scope = document.createElement(\"div\");\n this.scope.className = \"qaid-scope\";\n this.scope.innerHTML = `\n <div class=\"qaid-scope-ring\"></div>\n <div class=\"qaid-scope-ring-inner\"></div>\n <div class=\"qaid-scope-dot\"></div>\n `;\n\n // Highlight box overlay (positioned over hovered elements)\n this.highlightBox = document.createElement(\"div\");\n this.highlightBox.className = \"qaid-highlight-box\";\n\n this.overlayContainer.appendChild(this.captureLayer);\n this.overlayContainer.appendChild(vignette);\n this.overlayContainer.appendChild(this.highlightBox);\n this.overlayContainer.appendChild(this.crosshairH);\n this.overlayContainer.appendChild(this.crosshairV);\n this.overlayContainer.appendChild(this.scope);\n\n // Position crosshairs immediately at current mouse position\n this.crosshairH.style.top = `${this.mousePos.y}px`;\n this.crosshairV.style.left = `${this.mousePos.x}px`;\n this.scope.style.left = `${this.mousePos.x}px`;\n this.scope.style.top = `${this.mousePos.y}px`;\n\n this.host.applyVars(this.overlayContainer);\n root.appendChild(this.overlayContainer);\n }\n\n private handleMouseMove(e: MouseEvent): void {\n this.updateReticleAt(e.clientX, e.clientY);\n }\n\n /** Move the crosshair/scope reticle and highlight the element under (x, y). */\n private updateReticleAt(x: number, y: number): void {\n this.mousePos.x = x;\n this.mousePos.y = y;\n\n if (this.crosshairH) this.crosshairH.style.top = `${y}px`;\n if (this.crosshairV) this.crosshairV.style.left = `${x}px`;\n if (this.scope) {\n this.scope.style.left = `${x}px`;\n this.scope.style.top = `${y}px`;\n }\n\n // Find element underneath — hide both shadow hosts\n if (this.captureLayer && this.host.shadowHost) {\n const hosts = [this.host.shadowHost, this.host.overlayShadowHost].filter(Boolean) as HTMLElement[];\n const elementUnder = getElementAtPointUnderOverlay(x, y, hosts);\n\n if (elementUnder && !isEmbedElement(elementUnder)) {\n if (this.highlightBox) {\n const rect = elementUnder.getBoundingClientRect();\n this.highlightBox.style.transform = `translate(${rect.left}px, ${rect.top}px)`;\n this.highlightBox.style.width = `${rect.width}px`;\n this.highlightBox.style.height = `${rect.height}px`;\n this.highlightBox.style.display = \"block\";\n }\n } else if (this.highlightBox) {\n this.highlightBox.style.display = \"none\";\n }\n }\n }\n\n private handleClick(e: MouseEvent): void {\n e.preventDefault();\n e.stopPropagation();\n this.selectAt(e.clientX, e.clientY);\n }\n\n // ---- Touch targeting (iOS/iPadOS) ----\n private handleTouchStart(e: TouchEvent): void {\n const t = e.touches[0];\n if (!t) return;\n this.touchStartPos = { x: t.clientX, y: t.clientY };\n // Preview what's under the finger; don't preventDefault so scrolling works.\n this.updateReticleAt(t.clientX, t.clientY);\n }\n\n private handleTouchEnd(e: TouchEvent): void {\n // touches is empty on touchend; the lifted point is in changedTouches.\n const t = e.changedTouches[0];\n const start = this.touchStartPos;\n this.touchStartPos = null;\n if (!t || !start) return;\n // A drag beyond the slop threshold is a scroll, not a selection — ignore it.\n const moved = Math.hypot(t.clientX - start.x, t.clientY - start.y);\n if (moved > TOUCH_TAP_SLOP) return;\n e.preventDefault(); // suppress the tap's synthesised click\n this.selectAt(t.clientX, t.clientY);\n }\n\n /** Select the element under (x, y) and tear down targeting. */\n private selectAt(x: number, y: number): void {\n const hosts = [this.host.shadowHost, this.host.overlayShadowHost].filter(Boolean) as HTMLElement[];\n const target = getElementAtPointUnderOverlay(x, y, hosts);\n\n if (!target || isEmbedElement(target)) {\n return;\n }\n\n const bounds = getElementBounds(target, 8);\n Object.assign(this.host.selectedBounds, bounds, { clickX: x, clickY: y, visible: true });\n\n const { selector, text } = generateElementInfo(target);\n this.host.feedbackData.elementSelector = selector;\n this.host.feedbackData.elementText = text;\n\n // Remove targeting overlay and document listeners\n this.stopTargetingListeners();\n document.body.classList.remove(\"qaid-targeting\", \"qaid-type-up\");\n document.body.style.removeProperty(\"--qaid-positive\");\n document.body.style.removeProperty(\"--qaid-negative\");\n\n this.host.setState(\"SELECTED\");\n this.host.clearActiveThumb();\n\n this.showSelectedMarker();\n void this.host.submitFeedback();\n }\n\n private stopTargetingListeners(): void {\n this.removeTargetingOverlay();\n this.touchStartPos = null;\n document.removeEventListener(\"mousemove\", this.boundMouseMove);\n document.removeEventListener(\"click\", this.boundClick, true);\n document.removeEventListener(\"touchstart\", this.boundTouchStart);\n document.removeEventListener(\"touchend\", this.boundTouchEnd);\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n cancel(): void {\n this.keyboardController?.stop();\n this.keyboardController = null;\n this.stopTargetingListeners();\n document.body.classList.remove(\"qaid-targeting\", \"qaid-type-up\");\n document.body.style.removeProperty(\"--qaid-positive\");\n document.body.style.removeProperty(\"--qaid-negative\");\n this.host.clearActiveThumb();\n\n this.host.setState(\"IDLE\");\n this.host.feedbackData.feedbackType = null;\n this.host.feedbackData.elementSelector = null;\n this.host.feedbackData.elementText = null;\n this.host.selectedBounds.visible = false;\n }\n\n private removeTargetingOverlay(): void {\n if (this.overlayContainer) {\n this.overlayContainer.remove();\n this.overlayContainer = null;\n }\n this.captureLayer = null;\n this.crosshairH = null;\n this.crosshairV = null;\n this.scope = null;\n }\n\n private showSelectedMarker(): void {\n const root = this.host.ensureOverlayHost();\n\n this.marker = document.createElement(\"div\");\n this.marker.className = \"qaid-selected-marker\";\n this.marker.style.left = `${this.host.selectedBounds.x}px`;\n this.marker.style.top = `${this.host.selectedBounds.y}px`;\n this.marker.style.width = `${this.host.selectedBounds.width}px`;\n this.marker.style.height = `${this.host.selectedBounds.height}px`;\n this.marker.style.zIndex = String(this.host.config.zIndex + 1);\n\n this.host.applyVars(this.marker);\n root.appendChild(this.marker);\n }\n\n hideMarker(): void {\n if (this.marker) {\n this.marker.remove();\n this.marker = null;\n }\n }\n\n /** Full teardown for embed.destroy(). */\n destroy(): void {\n this.keyboardController?.stop();\n this.keyboardController = null;\n this.stopTargetingListeners();\n this.hideMarker();\n }\n}\n"],"names":["DATA_ATTRS","findDataAttribute","element","stopAt","current","attr","value","getTruncatedText","maxLength","textContent","getNthChildIndex","index","sibling","generateNthChildPath","path","tag","escapeCSSIdentifier","str","generateSelector","dataAttr","generateElementInfo","text","TARGETABLE_SELECTOR","NATIVELY_FOCUSABLE","isElementVisible","view","style","collectTargetableElements","options","root","selector","isExcluded","isVisible","startKeyboardTargeting","candidates","moveFocus","target","addedTabindex","stopped","seed","seedIndex","focusCurrent","focusable","emitHighlight","moveTo","nextIndex","len","next","prev","getCurrent","stop","handleKey","select","cancel","event","TOUCH_TAP_SLOP","TargetingController","host","e","type","el","isEmbedElement","rect","b","bounds","getElementBounds","vignette","x","y","hosts","elementUnder","getElementAtPointUnderOverlay","t","start"],"mappings":"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"}