@qaiddev/thumbs-embed 1.2.2 → 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 (41) hide show
  1. package/dist/annotate.d.ts +150 -0
  2. package/dist/bootstrap.d.ts +15 -0
  3. package/dist/chunks/annotate-BNibaDg3.js +367 -0
  4. package/dist/chunks/annotate-BNibaDg3.js.map +1 -0
  5. package/dist/chunks/bootstrap-U4yiPm5-.js +1099 -0
  6. package/dist/chunks/bootstrap-U4yiPm5-.js.map +1 -0
  7. package/dist/chunks/modal-BX5wqy4s.js +119 -0
  8. package/dist/chunks/modal-BX5wqy4s.js.map +1 -0
  9. package/dist/chunks/network-capture-DMbMwwwR.js +63 -0
  10. package/dist/chunks/network-capture-DMbMwwwR.js.map +1 -0
  11. package/dist/chunks/recording--IwOtRL2.js +256 -0
  12. package/dist/chunks/recording--IwOtRL2.js.map +1 -0
  13. package/dist/chunks/screenshot-BkOCLuS2.js +33 -0
  14. package/dist/chunks/screenshot-BkOCLuS2.js.map +1 -0
  15. package/dist/chunks/screenshot-dom-CME7tYRE.js +63 -0
  16. package/dist/chunks/screenshot-dom-CME7tYRE.js.map +1 -0
  17. package/dist/chunks/targeting-DwdDr9Cm.js +295 -0
  18. package/dist/chunks/targeting-DwdDr9Cm.js.map +1 -0
  19. package/dist/chunks/video-BOYqf2Im.js +209 -0
  20. package/dist/chunks/video-BOYqf2Im.js.map +1 -0
  21. package/dist/dom.d.ts +36 -0
  22. package/dist/embed.d.ts +61 -68
  23. package/dist/icons.d.ts +7 -0
  24. package/dist/index.d.ts +12 -12
  25. package/dist/index.js +22 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/loader.d.ts +18 -0
  28. package/dist/loader.js +7 -0
  29. package/dist/loader.js.map +1 -0
  30. package/dist/modal.d.ts +49 -0
  31. package/dist/qaid.umd.cjs +30 -16
  32. package/dist/qaid.umd.cjs.map +1 -1
  33. package/dist/recording.d.ts +74 -0
  34. package/dist/targeting.d.ts +72 -0
  35. package/dist/types.d.ts +16 -1
  36. package/dist/video-capture.d.ts +7 -0
  37. package/dist/video-orientation.d.ts +2 -0
  38. package/dist/video-redaction.d.ts +58 -0
  39. package/package.json +9 -5
  40. package/dist/qaid.js +0 -1665
  41. package/dist/qaid.js.map +0 -1
package/dist/qaid.js DELETED
@@ -1,1665 +0,0 @@
1
- const I = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
2
- <path stroke-linecap="round" stroke-linejoin="round" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"/>
3
- </svg>`, A = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
4
- <path stroke-linecap="round" stroke-linejoin="round" d="M10 14H5.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 018.737 3h4.018a2 2 0 01.485.06l3.76.94m-7 10v5a2 2 0 002 2h.095c.5 0 .905-.405.905-.905 0-.714.211-1.412.608-2.006L17 13V4m-7 10h2m5-10h2a2 2 0 012 2v6a2 2 0 01-2 2h-2.5"/>
5
- </svg>`, bt = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
6
- <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 10.5l4.72-4.72a.75.75 0 011.28.53v11.38a.75.75 0 01-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25h-9A2.25 2.25 0 002.25 7.5v9a2.25 2.25 0 002.25 2.25z"/>
7
- </svg>`, gt = "button{cursor:pointer}.qaid-buttons{display:flex;gap:.5rem}.qaid-buttons.qaid-vertical{flex-direction:column}.qaid-buttons.qaid-auto-container{position:fixed;z-index:50}.qaid-buttons.qaid-auto-container.qaid-bottom-right{bottom:1rem;right:1rem}.qaid-buttons.qaid-auto-container.qaid-bottom-left{bottom:1rem;left:1rem}.qaid-buttons.qaid-auto-container.qaid-top-right{top:1rem;right:1rem}.qaid-buttons.qaid-auto-container.qaid-top-left{top:1rem;left:1rem}.qaid-buttons.qaid-incognito{opacity:0;transition:opacity .2s ease-in-out}.qaid-buttons.qaid-incognito:hover{opacity:1}.qaid-buttons.qaid-dismissed{display:none!important}.qaid-dismiss-btn{width:20px;height:20px;padding:0;border:none;border-radius:50%;background:#0006;color:#fff;display:flex;align-items:center;justify-content:center;align-self:center;opacity:0;transition:opacity .15s,background .15s;cursor:pointer;pointer-events:auto;-webkit-appearance:none;appearance:none}.qaid-dismiss-btn:hover{background:#0009}.qaid-buttons:hover .qaid-dismiss-btn{opacity:1}button.qaid-btn-structural{display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-appearance:none;appearance:none}.qaid-icon{width:24px;height:24px}.qaid-btn-structural:not(:has(svg)),.qaid-btn:not(:has(svg)){font-size:var(--qaid-icon-size, 24px);line-height:1}.qaid-emoji-icon{font-size:var(--qaid-icon-size, 24px);line-height:1}.qaid-buttons,.qaid-buttons *,.qaid-modal-container,.qaid-modal-container *{cursor:pointer!important}.qaid-tooltip-wrapper{position:relative}.qaid-tooltip-text{position:fixed;padding:.5rem .75rem;background:#1f2937;color:#fff;font-size:1rem;font-weight:600;border-radius:.5rem;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .15s;z-index:99999}.qaid-tooltip-text.qaid-tooltip-visible{opacity:1}.qaid-targeting-overlay{position:fixed;inset:0;z-index:40;pointer-events:none}.qaid-capture-layer{position:fixed;inset:0;pointer-events:none;z-index:9999}@keyframes qaid-slideDown{0%{transform:translateY(-100%)}to{transform:translateY(0)}}.qaid-vignette{position:fixed;inset:0;pointer-events:none;background:radial-gradient(ellipse at center,transparent 30%,rgba(0,0,0,.3) 70%,rgba(0,0,0,.6) 100%);z-index:41}.qaid-crosshair-h,.qaid-crosshair-v{position:fixed;pointer-events:none;z-index:42}.qaid-crosshair-h{left:0;right:0;height:1px;background:color-mix(in srgb,var(--qaid-negative) 60%,transparent)}.qaid-crosshair-v{top:0;bottom:0;width:1px;background:color-mix(in srgb,var(--qaid-negative) 60%,transparent)}.qaid-type-up .qaid-crosshair-h,.qaid-type-up .qaid-crosshair-v{background:color-mix(in srgb,var(--qaid-positive) 60%,transparent)}.qaid-highlight-box{position:fixed;top:0;left:0;pointer-events:none;z-index:41;display:none;border:3px solid color-mix(in srgb,var(--qaid-negative) 80%,transparent);border-radius:2px;background:color-mix(in srgb,var(--qaid-negative) 8%,transparent);will-change:transform,width,height}.qaid-type-up .qaid-highlight-box{border-color:color-mix(in srgb,var(--qaid-positive) 80%,transparent);background:color-mix(in srgb,var(--qaid-positive) 8%,transparent)}.qaid-scope{position:fixed;width:80px;height:80px;pointer-events:none;z-index:43;transform:translate(-50%,-50%)}.qaid-scope-ring{position:absolute;inset:10px;border:2px solid color-mix(in srgb,var(--qaid-negative) 80%,transparent);border-radius:50%}.qaid-type-up .qaid-scope-ring{border-color:color-mix(in srgb,var(--qaid-positive) 80%,transparent)}.qaid-scope-ring-inner{position:absolute;inset:20px;border:1px solid color-mix(in srgb,var(--qaid-negative) 50%,transparent);border-radius:50%}.qaid-type-up .qaid-scope-ring-inner{border-color:color-mix(in srgb,var(--qaid-positive) 50%,transparent)}.qaid-scope-dot{position:absolute;top:50%;left:50%;width:4px;height:4px;background:var(--qaid-negative);border-radius:50%;transform:translate(-50%,-50%)}.qaid-type-up .qaid-scope-dot{background:var(--qaid-positive)}.qaid-selected-marker{position:fixed;border:3px solid var(--qaid-marker, #6366f1);border-radius:50%;pointer-events:none;z-index:44;animation:qaid-markerPulse 1.5s ease-in-out infinite}@keyframes qaid-markerPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.7;transform:scale(1.05)}}.qaid-backdrop{position:fixed;inset:0;z-index:45;background:#0000004d}.qaid-modal-container{position:fixed;z-index:50;display:flex;flex-direction:column;align-items:flex-start;max-height:calc(100vh - 32px);font-family:var(--qaid-font-family, system-ui, -apple-system, sans-serif);font-size:var(--qaid-font-size, 16px)}.qaid-modal-container.qaid-above{flex-direction:column-reverse}.qaid-modal-arrow{width:0;height:0;border-left:12px solid transparent;border-right:12px solid transparent;position:relative;align-self:flex-start}.qaid-modal-container.qaid-below .qaid-modal-arrow{border-bottom:12px solid light-dark(#ffffff,#1f2937)}.qaid-modal-container.qaid-above .qaid-modal-arrow{border-top:12px solid light-dark(#ffffff,#1f2937)}.qaid-modal-box{color-scheme:inherit;background:light-dark(#ffffff,#1f2937);border-radius:1rem;padding:1.5rem;box-shadow:0 25px 50px -12px light-dark(rgba(0,0,0,.25),rgba(0,0,0,.5));width:var(--qaid-modal-width, 400px);max-width:calc(100vw - 32px);max-height:calc(100vh - 60px);overflow-y:auto}.qaid-modal-header{display:flex;align-items:flex-start;gap:.75rem;margin-bottom:1rem}button.qaid-type-toggle{border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s;flex-shrink:0;-webkit-appearance:none;appearance:none}button.qaid-type-toggle:hover{transform:scale(1.1)}button.qaid-type-toggle:not(.qaid-type-toggle-custom){width:2.5rem;height:2.5rem;border-radius:50%}button.qaid-type-toggle:not(.qaid-type-toggle-custom) svg{width:1.25rem;height:1.25rem}button.qaid-type-toggle.qaid-type-up{background:var(--qaid-positive);color:#fff}button.qaid-type-toggle.qaid-type-down{background:var(--qaid-negative);color:#fff}.qaid-modal-header-text{flex:1;min-width:0}.qaid-modal-title{font-size:1.125rem;font-weight:700;margin:0 0 .25rem;color:light-dark(#1f2937,#f9fafb)}.qaid-modal-subtitle{color:light-dark(#6b7280,#9ca3af);margin:0;font-size:.875rem}.qaid-textarea{width:100%;height:6rem;padding:.75rem;border:1px solid light-dark(#d1d5db,#374151);border-radius:.5rem;font-family:inherit;font-size:1rem;resize:vertical;margin-bottom:1rem;box-sizing:border-box;background:light-dark(#ffffff,#111827);color:light-dark(#1f2937,#f9fafb)}.qaid-textarea:focus{outline:none;border-color:var(--qaid-marker);box-shadow:0 0 0 3px color-mix(in srgb,var(--qaid-marker) 20%,transparent)}.qaid-btn-row{display:flex;gap:.5rem;justify-content:flex-end}button.qaid-btn-submit{padding:.5rem 1rem;background:var(--qaid-marker);color:var(--qaid-marker-text, white);border:none;border-radius:.5rem;font-size:.875rem;font-weight:500;cursor:pointer;transition:background-color .2s,filter .2s;-webkit-appearance:none;appearance:none}button.qaid-btn-submit:hover{filter:brightness(.85)}button.qaid-btn-submit:focus{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--qaid-marker) 30%,transparent)}.qaid-bottom-sheet{position:fixed;bottom:0;left:0;right:0;z-index:50;animation:qaid-slideUpSheet .3s ease-out;font-family:var(--qaid-font-family, system-ui, -apple-system, sans-serif);font-size:var(--qaid-font-size, 16px)}.qaid-bottom-sheet-content{background:light-dark(#ffffff,#1f2937);border-radius:1rem 1rem 0 0;padding:1.5rem;padding-bottom:max(1.5rem,env(safe-area-inset-bottom))}.qaid-bottom-sheet-handle{width:36px;height:4px;background:light-dark(rgba(0,0,0,.2),rgba(255,255,255,.2));border-radius:2px;margin:0 auto 1rem}@keyframes qaid-slideUpSheet{0%{transform:translateY(100%)}to{transform:translateY(0)}}button.qaid-btn-record:hover{background:#dc2626;color:#fff}.qaid-recording-indicator{position:fixed;top:12px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;background:light-dark(#1f2937,#374151);color:#fff;border-radius:9999px;font-family:var(--qaid-font-family, system-ui, -apple-system, sans-serif);font-size:.875rem;font-weight:500;box-shadow:0 4px 12px #0000004d;z-index:99999;animation:qaid-slideDown .2s ease-out}.qaid-recording-dot{width:10px;height:10px;background:#dc2626;border-radius:50%;animation:qaid-dotPulse 1.5s ease-in-out infinite}@keyframes qaid-dotPulse{0%,to{opacity:1}50%{opacity:.3}}.qaid-recording-time{font-variant-numeric:tabular-nums;min-width:2.5rem;text-align:center}button.qaid-recording-stop{padding:.25rem .75rem;background:#dc2626;color:#fff;border:none;border-radius:9999px;font-size:.75rem;font-weight:600;cursor:pointer;transition:background-color .2s;-webkit-appearance:none;appearance:none}button.qaid-recording-stop:hover{background:#b91c1c}.qaid-video-preview{position:fixed;inset:0 0 auto;height:100vh;height:100dvh;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:max(16px,env(safe-area-inset-top));padding-bottom:max(16px,env(safe-area-inset-bottom));box-sizing:border-box;background:#0009;z-index:99998;animation:qaid-fadeIn .2s ease-out}@keyframes qaid-fadeIn{0%{opacity:0}to{opacity:1}}.qaid-video-preview-box{color-scheme:inherit;background:light-dark(#ffffff,#1f2937);border-radius:1rem;padding:1.5rem;box-shadow:0 25px 50px -12px #00000080;width:560px;max-width:100%;max-height:100%;overflow-y:auto;font-family:var(--qaid-font-family, system-ui, -apple-system, sans-serif);font-size:var(--qaid-font-size, 16px)}.qaid-video-preview-box h3{font-size:1.125rem;font-weight:700;margin:0 0 1rem;color:light-dark(#1f2937,#f9fafb)}.qaid-video-preview-box video{width:100%;max-height:50vh;max-height:50dvh;object-fit:contain;border-radius:.5rem;background:#000;margin-bottom:1rem}.qaid-video-preview-box textarea{width:100%;height:4rem;padding:.75rem;border:1px solid light-dark(#d1d5db,#374151);border-radius:.5rem;font-family:inherit;font-size:.875rem;resize:vertical;margin-bottom:1rem;box-sizing:border-box;background:light-dark(#ffffff,#111827);color:light-dark(#1f2937,#f9fafb)}.qaid-video-preview-box textarea:focus{outline:none;border-color:var(--qaid-marker);box-shadow:0 0 0 3px color-mix(in srgb,var(--qaid-marker) 20%,transparent)}.qaid-video-preview-actions{display:flex;gap:.5rem;justify-content:flex-end}button.qaid-video-btn{padding:.5rem 1rem;border:none;border-radius:.5rem;font-size:.875rem;font-weight:500;cursor:pointer;transition:background-color .2s,filter .2s;-webkit-appearance:none;appearance:none}button.qaid-video-btn-cancel{background:light-dark(#f3f4f6,#374151);color:light-dark(#374151,#d1d5db)}button.qaid-video-btn-cancel:hover{background:light-dark(#e5e7eb,#4b5563)}button.qaid-video-btn-rerecord{background:light-dark(#fef3c7,#78350f);color:light-dark(#92400e,#fde68a)}button.qaid-video-btn-rerecord:hover{filter:brightness(.9)}button.qaid-video-btn-send{background:var(--qaid-marker);color:var(--qaid-marker-text, white)}button.qaid-video-btn-send:hover{filter:brightness(.85)}button.qaid-video-btn-send:disabled{opacity:.5;cursor:not-allowed}.qaid-video-sending{display:flex;align-items:center;gap:.5rem;font-size:.875rem;color:light-dark(#6b7280,#9ca3af)}button:focus-visible,textarea:focus-visible,a:focus-visible,[tabindex]:focus-visible,[role=button]:focus-visible{outline:2px solid var(--qaid-marker, #6366f1);outline-offset:2px}.qaid-textarea:focus-visible,button.qaid-btn-submit:focus-visible,.qaid-video-preview-box textarea:focus-visible{outline:2px solid var(--qaid-marker, #6366f1);outline-offset:2px}.qaid-buttons.qaid-incognito:focus-within{opacity:1}.qaid-buttons:focus-within .qaid-dismiss-btn,.qaid-dismiss-btn:focus-visible{opacity:1}.qaid-dismiss-btn{min-width:24px;min-height:24px}button.qaid-recording-stop{min-height:24px}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}@media(forced-colors:active){button:focus-visible,textarea:focus-visible,a:focus-visible,[tabindex]:focus-visible,[role=button]:focus-visible,.qaid-textarea:focus,button.qaid-btn-submit:focus,.qaid-video-preview-box textarea:focus{outline:2px solid CanvasText;outline-offset:2px}.qaid-selected-marker,.qaid-highlight-box{border-color:Highlight}}", vt = "body.qaid-targeting,body.qaid-targeting *{cursor:none!important}";
8
- let k = 0;
9
- const yt = "button.qaid-btn{width:var(--qaid-btn-size);height:var(--qaid-btn-size);border-radius:50%;border:none;background:#f3f4f6;color:#374151;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);transition:background-color .2s,color .2s,transform .2s;-webkit-appearance:none;appearance:none;--qaid-hover-up-bg:var(--qaid-positive);--qaid-hover-up-color:#fff;--qaid-hover-down-bg:var(--qaid-negative);--qaid-hover-down-color:#fff}button.qaid-btn:hover{transform:scale(1.05)}button.qaid-btn-up:hover{background:var(--qaid-hover-up-bg);color:var(--qaid-hover-up-color)}button.qaid-btn-down:hover{background:var(--qaid-hover-down-bg);color:var(--qaid-hover-down-color)}button.qaid-btn svg{width:var(--qaid-icon-size);height:var(--qaid-icon-size)}", wt = {
10
- small: 36,
11
- medium: 48,
12
- large: 64
13
- }, xt = {
14
- small: 18,
15
- medium: 24,
16
- large: 32
17
- };
18
- function qt(e, t, i) {
19
- const [o, n, s] = [e, t, i].map((a) => (a = a / 255, a <= 0.03928 ? a / 12.92 : Math.pow((a + 0.055) / 1.055, 2.4)));
20
- return 0.2126 * o + 0.7152 * n + 0.0722 * s;
21
- }
22
- function kt(e) {
23
- if (e.startsWith("#")) {
24
- const i = e.slice(1), o = i.length === 3 ? i.split("").map((s) => s + s).join("") : i, n = parseInt(o, 16);
25
- return {
26
- r: n >> 16 & 255,
27
- g: n >> 8 & 255,
28
- b: n & 255
29
- };
30
- }
31
- const t = e.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
32
- return t ? {
33
- r: parseInt(t[1]),
34
- g: parseInt(t[2]),
35
- b: parseInt(t[3])
36
- } : null;
37
- }
38
- function Ct(e) {
39
- const t = kt(e);
40
- return t && qt(t.r, t.g, t.b) > 0.4 ? "black" : "white";
41
- }
42
- function St(e = {}) {
43
- const {
44
- positiveColor: t = "rgb(0, 200, 83)",
45
- negativeColor: i = "rgb(255, 0, 0)",
46
- markerColor: o = "#6366f1",
47
- buttonSize: n = "medium",
48
- modalWidth: s = 400,
49
- backdropOpacity: a = 0.3,
50
- fontFamily: d = "system-ui, -apple-system, sans-serif",
51
- fontSize: l = 16
52
- } = e, r = wt[n], c = xt[n], p = Ct(o);
53
- return {
54
- "--qaid-positive": t,
55
- "--qaid-negative": i,
56
- "--qaid-marker": o,
57
- "--qaid-marker-text": p,
58
- "--qaid-btn-size": `${r}px`,
59
- "--qaid-icon-size": `${c}px`,
60
- "--qaid-modal-width": `${s}px`,
61
- "--qaid-backdrop-opacity": String(a),
62
- "--qaid-font-family": d,
63
- "--qaid-font-size": `${l}px`
64
- };
65
- }
66
- function Et(e, t) {
67
- for (const [i, o] of Object.entries(t))
68
- e.style.setProperty(i, o);
69
- }
70
- function N() {
71
- return gt + yt;
72
- }
73
- function Tt() {
74
- if (k++, k > 1) return;
75
- const e = document.createElement("style");
76
- e.id = "qaid-styles", e.textContent = vt, document.head.appendChild(e);
77
- }
78
- function Lt() {
79
- k <= 0 || (k--, k === 0 && document.getElementById("qaid-styles").remove());
80
- }
81
- const G = ["data-comp", "data-qa", "data-testid", "data-id"];
82
- function J(e, t = document.body) {
83
- let i = e;
84
- for (; i && i !== t; ) {
85
- for (const o of G) {
86
- const n = i.getAttribute(o);
87
- if (n)
88
- return `${o}="${n}"`;
89
- }
90
- i = i.parentElement;
91
- }
92
- return null;
93
- }
94
- function It(e, t = 100) {
95
- const i = e.textContent?.trim().slice(0, t) || "";
96
- return i.length === t ? i + "..." : i;
97
- }
98
- function At(e) {
99
- let t = 1, i = e.previousElementSibling;
100
- for (; i; )
101
- t++, i = i.previousElementSibling;
102
- return t;
103
- }
104
- function Dt(e) {
105
- const t = [];
106
- let i = e;
107
- for (; i && i !== document.body && i !== document.documentElement; ) {
108
- const o = i.tagName.toLowerCase(), n = At(i);
109
- t.unshift(`${o}:nth-child(${n})`), i = i.parentElement;
110
- }
111
- return t.length > 0 ? `body > ${t.join(" > ")}` : "body";
112
- }
113
- function Mt(e) {
114
- return e.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1");
115
- }
116
- function Rt(e) {
117
- const t = J(e);
118
- return t ? `[${t}]` : e.id ? `#${Mt(e.id)}` : Dt(e);
119
- }
120
- function D(e) {
121
- const t = It(e), i = J(e);
122
- return { selector: Rt(e), text: t, dataAttr: i };
123
- }
124
- const Ht = [
125
- "a[href]",
126
- "button",
127
- "input:not([type=hidden])",
128
- "select",
129
- "textarea",
130
- "summary",
131
- "[tabindex]",
132
- "[role]",
133
- "img",
134
- "h1",
135
- "h2",
136
- "h3",
137
- "h4",
138
- "h5",
139
- "h6",
140
- "p",
141
- "li",
142
- "label",
143
- ...G.map((e) => `[${e}]`)
144
- ].join(","), Bt = /^(?:A|BUTTON|INPUT|SELECT|TEXTAREA|SUMMARY)$/;
145
- function Pt(e) {
146
- if (e.hasAttribute("hidden") || e.getAttribute("aria-hidden") === "true") return !1;
147
- const t = e.ownerDocument?.defaultView;
148
- if (t && typeof t.getComputedStyle == "function") {
149
- const i = t.getComputedStyle(e);
150
- if (i.display === "none" || i.visibility === "hidden") return !1;
151
- }
152
- return !0;
153
- }
154
- function zt(e = {}) {
155
- const t = e.root ?? document.body, i = e.selector ?? Ht, o = e.isExcluded ?? (() => !1), n = e.isVisible ?? Pt;
156
- return Array.from(t.querySelectorAll(i)).filter(
157
- (s) => !o(s) && n(s)
158
- );
159
- }
160
- function Ot(e) {
161
- const t = e.candidates ?? zt({
162
- root: e.root,
163
- selector: e.selector,
164
- isExcluded: e.isExcluded,
165
- isVisible: e.isVisible
166
- }), i = e.moveFocus ?? !0, o = e.eventTarget ?? document, n = /* @__PURE__ */ new Set();
167
- let s = -1, a = !1;
168
- const d = e.initial !== void 0 ? e.initial : typeof document < "u" ? document.activeElement : null;
169
- if (d) {
170
- const h = t.indexOf(d);
171
- h >= 0 && (s = h);
172
- }
173
- s < 0 && t.length > 0 && (s = 0);
174
- function l(h) {
175
- if (!i) return;
176
- const w = h;
177
- if (typeof w.focus == "function") {
178
- h.getAttribute("tabindex") === null && !Bt.test(h.tagName) && (h.setAttribute("tabindex", "-1"), n.add(h));
179
- try {
180
- w.focus();
181
- } catch {
182
- }
183
- }
184
- }
185
- function r() {
186
- const h = t[s];
187
- h && (l(h), e.onHighlight?.(h, s));
188
- }
189
- function c(h) {
190
- if (a || t.length === 0) return;
191
- const w = t.length;
192
- s = (h % w + w) % w, r();
193
- }
194
- function p() {
195
- c(s + 1);
196
- }
197
- function u() {
198
- c(s - 1);
199
- }
200
- function m() {
201
- return t[s] ?? null;
202
- }
203
- function b() {
204
- a || (a = !0, o.removeEventListener("keydown", f, !0), n.forEach((h) => h.removeAttribute("tabindex")), n.clear());
205
- }
206
- function v() {
207
- if (a) return;
208
- const h = m();
209
- b(), h && e.onSelect(h);
210
- }
211
- function y() {
212
- a || (b(), e.onCancel?.());
213
- }
214
- function f(h) {
215
- if (!a)
216
- switch (h.key) {
217
- case "Tab":
218
- h.preventDefault(), h.stopPropagation(), h.shiftKey ? u() : p();
219
- break;
220
- case "ArrowDown":
221
- case "ArrowRight":
222
- h.preventDefault(), h.stopPropagation(), p();
223
- break;
224
- case "ArrowUp":
225
- case "ArrowLeft":
226
- h.preventDefault(), h.stopPropagation(), u();
227
- break;
228
- case "Enter":
229
- case " ":
230
- case "Spacebar":
231
- h.preventDefault(), h.stopPropagation(), v();
232
- break;
233
- case "Escape":
234
- case "Esc":
235
- h.preventDefault(), h.stopPropagation(), y();
236
- break;
237
- }
238
- }
239
- return o.addEventListener("keydown", f, !0), s >= 0 && r(), {
240
- candidates: t,
241
- getIndex: () => s,
242
- getCurrent: m,
243
- next: p,
244
- prev: u,
245
- moveTo: c,
246
- select: v,
247
- cancel: y,
248
- handleKey: f,
249
- stop: b
250
- };
251
- }
252
- const V = "data-qaid-a11y-live", $t = "position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;", Nt = [
253
- "a[href]",
254
- "button",
255
- "input",
256
- "textarea",
257
- "select",
258
- "[tabindex]"
259
- ].join(",");
260
- function Vt(e) {
261
- return e.ownerDocument || document;
262
- }
263
- function Ft(e, t) {
264
- const i = t ? "assertive" : "polite", o = e.querySelector(
265
- `[${V}="${i}"]`
266
- );
267
- if (o) return o;
268
- const s = Vt(e).createElement("div");
269
- return s.setAttribute(V, i), s.setAttribute("role", t ? "alert" : "status"), s.setAttribute("aria-live", t ? "assertive" : "polite"), s.setAttribute("aria-atomic", "true"), s.style.cssText = $t, e.appendChild(s), s;
270
- }
271
- function Ut(e, t, i = {}) {
272
- const o = Ft(e, !!i.assertive);
273
- o.textContent = "", o.textContent = t;
274
- }
275
- function _t(e) {
276
- return e.hasAttribute("disabled") ? !0 : e.disabled === !0;
277
- }
278
- function Kt(e) {
279
- let t = e;
280
- for (; t; ) {
281
- if (t.hasAttribute("hidden")) return !0;
282
- const i = t.style;
283
- if (i && (i.display === "none" || i.visibility === "hidden" || i.visibility === "collapse"))
284
- return !0;
285
- t = t.parentElement;
286
- }
287
- return !1;
288
- }
289
- function F(e) {
290
- return Array.from(
291
- e.querySelectorAll(Nt)
292
- ).filter((i) => !(i.getAttribute("tabindex") === "-1" || i instanceof HTMLInputElement && i.type === "hidden" || i instanceof HTMLAnchorElement && !i.getAttribute("href") || _t(i) || Kt(i)));
293
- }
294
- function Z(e) {
295
- let t = e.activeElement;
296
- for (; t && t.shadowRoot && t.shadowRoot.activeElement; )
297
- t = t.shadowRoot.activeElement;
298
- return t instanceof HTMLElement ? t : null;
299
- }
300
- function jt(e) {
301
- const t = e.ownerDocument || document;
302
- let i = !1;
303
- const o = (s) => {
304
- if (s.key !== "Tab") return;
305
- const a = F(e);
306
- if (a.length === 0) {
307
- s.preventDefault(), e.focus();
308
- return;
309
- }
310
- const d = a[0], l = a[a.length - 1], r = Z(t), c = r ? a.indexOf(r) !== -1 : !1;
311
- s.shiftKey ? (!c || r === d) && (s.preventDefault(), l.focus()) : (!c || r === l) && (s.preventDefault(), d.focus());
312
- };
313
- e.addEventListener("keydown", o);
314
- const n = F(e);
315
- return n.length > 0 ? n[0].focus() : (e.hasAttribute("tabindex") || (e.setAttribute("tabindex", "-1"), i = !0), e.focus()), {
316
- release() {
317
- e.removeEventListener("keydown", o), i && (e.removeAttribute("tabindex"), i = !1);
318
- }
319
- };
320
- }
321
- function Xt() {
322
- return Z(document);
323
- }
324
- function Wt(e) {
325
- if (!(!e || typeof e.focus != "function"))
326
- try {
327
- e.focus();
328
- } catch {
329
- }
330
- }
331
- function Yt(e, t = {}) {
332
- e.setAttribute("role", "dialog"), e.setAttribute("aria-modal", "true"), t.labelledbyId && e.setAttribute("aria-labelledby", t.labelledbyId), t.describedbyId && e.setAttribute("aria-describedby", t.describedbyId), t.label && e.setAttribute("aria-label", t.label);
333
- }
334
- function Qt(e, t) {
335
- let i = e;
336
- for (; i && i !== t; ) {
337
- const o = i.parentNode;
338
- if (o === t) return i;
339
- if (o && o.host) {
340
- i = o.host;
341
- continue;
342
- }
343
- if (!o) return null;
344
- i = o;
345
- }
346
- return null;
347
- }
348
- function Gt(e) {
349
- const i = (e.ownerDocument || document).body;
350
- if (!i) return () => {
351
- };
352
- const o = Qt(e, i), n = [];
353
- return Array.from(i.children).forEach((s) => {
354
- if (!(s instanceof HTMLElement) || o && s === o) return;
355
- const a = s.inert === !0, d = s.getAttribute("aria-hidden");
356
- a && d === "true" || (s.inert = !0, s.setAttribute("aria-hidden", "true"), n.push({ el: s, prevInert: a, prevAriaHidden: d }));
357
- }), function() {
358
- for (; n.length; ) {
359
- const a = n.pop();
360
- a.el.inert = a.prevInert, a.prevAriaHidden === null ? a.el.removeAttribute("aria-hidden") : a.el.setAttribute("aria-hidden", a.prevAriaHidden);
361
- }
362
- };
363
- }
364
- const tt = {
365
- width: 400,
366
- height: 280,
367
- arrowHeight: 12,
368
- gap: 8,
369
- viewportPadding: 16
370
- };
371
- function Jt(e, t, i, o, n) {
372
- const s = e.y, a = t - (e.y + e.height);
373
- if (a >= i + o)
374
- return {
375
- top: e.y + e.height + o,
376
- position: "below"
377
- };
378
- if (s >= i + o)
379
- return {
380
- top: e.y - i - o,
381
- position: "above"
382
- };
383
- const d = a > s ? "below" : "above";
384
- let l;
385
- return d === "below" ? l = Math.min(
386
- e.y + e.height + o,
387
- t - i - n
388
- ) : l = Math.max(n, e.y - i - o), { top: l, position: d };
389
- }
390
- function Zt(e, t, i, o) {
391
- let s = e.x + e.width / 2 - i / 2;
392
- return s = Math.max(o, Math.min(s, t - i - o)), s;
393
- }
394
- function te(e, t, i, o = 24, n = 24) {
395
- const a = e.clickX - t - n / 2;
396
- return Math.max(o, Math.min(a, i - o - n / 2));
397
- }
398
- function ee(e, t, i, o = tt) {
399
- const n = o.height + o.arrowHeight, s = Jt(
400
- e,
401
- i,
402
- n,
403
- o.gap,
404
- o.viewportPadding
405
- ), a = Zt(
406
- e,
407
- t,
408
- o.width,
409
- o.viewportPadding
410
- );
411
- return {
412
- top: s.top,
413
- left: a,
414
- position: s.position
415
- };
416
- }
417
- function ie(e, t, i, o = {}) {
418
- const n = o.gap ?? 8;
419
- let s = e.bottom + n;
420
- s + t.height > i.height - n && (s = e.top - t.height - n);
421
- let a = e.left;
422
- return a < n ? a = n : a + t.width > i.width - n && (a = i.width - t.width - n), s < n ? s = n : s + t.height > i.height - n && (s = i.height - t.height - n), { top: s, left: a };
423
- }
424
- function oe(e, t, i, o = tt) {
425
- const n = ee(e, t, i, o), s = te(
426
- e,
427
- n.left,
428
- o.width
429
- );
430
- return {
431
- modal: n,
432
- arrow: { left: s }
433
- };
434
- }
435
- const ne = 20;
436
- function se(e) {
437
- const t = [], i = console.error, o = console.warn, n = console.log, s = (a, d) => {
438
- const l = {
439
- message: d.map((r) => String(r)).join(" "),
440
- timestamp: Date.now(),
441
- level: a
442
- };
443
- t.length >= ne && t.shift(), t.push(l), e && e(l);
444
- };
445
- return console.error = function(...a) {
446
- s("error", a), i.apply(console, a);
447
- }, console.warn = function(...a) {
448
- s("warn", a), o.apply(console, a);
449
- }, console.log = function(...a) {
450
- s("log", a), n.apply(console, a);
451
- }, {
452
- errors: t,
453
- restore: () => {
454
- console.error = i, console.warn = o, console.log = n;
455
- }
456
- };
457
- }
458
- const ae = 20, U = 4096;
459
- function S(e) {
460
- const t = typeof e == "string" ? e : JSON.stringify(e);
461
- return t.length > U ? t.slice(0, U) + "…[truncated]" : t;
462
- }
463
- function _(e, t) {
464
- e.length >= ae && e.shift(), e.push(t);
465
- }
466
- async function re(e) {
467
- try {
468
- const t = await e.clone().text();
469
- return S(t);
470
- } catch {
471
- return;
472
- }
473
- }
474
- function de() {
475
- const e = [], t = window.fetch;
476
- window.fetch = async function(n, s) {
477
- const a = typeof n == "string" ? n : n instanceof URL ? n.toString() : n.url, d = s?.method ?? (typeof n == "object" && "method" in n ? n.method : "GET");
478
- let l;
479
- s?.body && (l = S(s.body));
480
- const r = await t.apply(window, [n, s]);
481
- if (r.status >= 400) {
482
- const c = await re(r);
483
- _(e, {
484
- url: a,
485
- method: d.toUpperCase(),
486
- status: r.status,
487
- statusText: r.statusText,
488
- requestBody: l,
489
- responseBody: c,
490
- timestamp: Date.now()
491
- });
492
- }
493
- return r;
494
- };
495
- const i = XMLHttpRequest.prototype.open, o = XMLHttpRequest.prototype.send;
496
- return XMLHttpRequest.prototype.open = function(n, s, ...a) {
497
- return this._qaid_method = n, this._qaid_url = typeof s == "string" ? s : s.toString(), i.apply(this, [n, s, ...a]);
498
- }, XMLHttpRequest.prototype.send = function(n) {
499
- const s = this, a = n ? S(n) : void 0;
500
- return s.addEventListener("load", function() {
501
- s.status >= 400 && _(e, {
502
- url: s._qaid_url,
503
- method: s._qaid_method.toUpperCase(),
504
- status: s.status,
505
- statusText: s.statusText,
506
- requestBody: a,
507
- responseBody: S(s.responseText),
508
- timestamp: Date.now()
509
- });
510
- }), o.apply(this, [n]);
511
- }, {
512
- errors: e,
513
- restore: () => {
514
- window.fetch = t, XMLHttpRequest.prototype.open = i, XMLHttpRequest.prototype.send = o;
515
- }
516
- };
517
- }
518
- function ce() {
519
- const e = navigator.userAgent || "";
520
- return /iP(hone|ad|od)/.test(e) || // iPadOS 13+ masquerades as "MacIntel" but is a multi-touch device.
521
- navigator.platform === "MacIntel" && (navigator.maxTouchPoints || 0) > 1;
522
- }
523
- function le(e, t, i, o, n) {
524
- const s = e >= t, a = i >= o;
525
- return s === a ? { width: e, height: t, rotate: 0 } : { width: t, height: e, rotate: n === 270 ? -90 : 90 };
526
- }
527
- function he(e, t, i) {
528
- if (i.rotate === 0) {
529
- e.drawImage(t, 0, 0, i.width, i.height);
530
- return;
531
- }
532
- e.save(), i.rotate === 90 ? (e.translate(i.width, 0), e.rotate(Math.PI / 2)) : (e.translate(0, i.height), e.rotate(-Math.PI / 2)), e.drawImage(t, 0, 0, i.height, i.width), e.restore();
533
- }
534
- function ue() {
535
- const e = typeof screen < "u" ? screen.orientation : void 0;
536
- return e && typeof e.angle == "number" ? e.angle : 0;
537
- }
538
- function pe(e) {
539
- return new Promise((t) => {
540
- if (e.videoWidth > 0) {
541
- t();
542
- return;
543
- }
544
- e.addEventListener("loadedmetadata", () => t(), { once: !0 });
545
- });
546
- }
547
- async function me(e) {
548
- try {
549
- await e.play();
550
- } catch {
551
- }
552
- }
553
- async function fe(e, t = 15) {
554
- const i = document.createElement("canvas");
555
- if (typeof i.captureStream != "function") return null;
556
- const o = i.getContext("2d");
557
- if (!o) return null;
558
- const n = document.createElement("video");
559
- n.muted = !0, n.playsInline = !0, n.srcObject = e, await pe(n);
560
- const s = le(
561
- n.videoWidth,
562
- n.videoHeight,
563
- window.innerWidth,
564
- window.innerHeight,
565
- ue()
566
- );
567
- i.width = s.width, i.height = s.height, await me(n);
568
- let a = 0;
569
- const d = () => {
570
- he(o, n, s), a = requestAnimationFrame(d);
571
- };
572
- d();
573
- const l = i.captureStream(t);
574
- return {
575
- stream: l,
576
- stop: () => {
577
- a && cancelAnimationFrame(a), a = 0, l.getTracks().forEach((r) => r.stop()), n.pause(), n.srcObject = null;
578
- }
579
- };
580
- }
581
- function be() {
582
- return typeof navigator < "u" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia == "function" && typeof MediaRecorder < "u";
583
- }
584
- function ge() {
585
- if (typeof MediaRecorder > "u") return "";
586
- const e = [
587
- "video/webm;codecs=vp9",
588
- "video/webm;codecs=vp8",
589
- "video/webm",
590
- "video/mp4"
591
- ];
592
- for (const t of e)
593
- if (MediaRecorder.isTypeSupported(t))
594
- return t;
595
- return "";
596
- }
597
- function ve(e = {}) {
598
- const t = e.maxDuration ?? 15, i = e.videoBitsPerSecond ?? 8e5;
599
- let o = null, n = null, s = null, a = [], d = null, l = null, r = null, c = 0, p = null, u = null, m = !1, b = null;
600
- function v() {
601
- r !== null && (clearInterval(r), r = null), b !== null && (clearTimeout(b), b = null), n && (n.stop(), n = null), o && (o.getTracks().forEach((f) => f.stop()), o = null), s = null, a = [], d = null, l = null, p = null, u = null;
602
- }
603
- function y() {
604
- m || (m = !0, s && s.state !== "inactive" && s.stop());
605
- }
606
- return {
607
- async start() {
608
- m = !1, a = [];
609
- const f = ge();
610
- if (!f)
611
- throw new Error("No supported video MIME type found");
612
- o = await navigator.mediaDevices.getDisplayMedia({
613
- video: {
614
- frameRate: 15
615
- },
616
- audio: !1,
617
- // @ts-expect-error preferCurrentTab is not in the TS types yet
618
- preferCurrentTab: !0
619
- });
620
- const h = o.getVideoTracks()[0];
621
- h && h.addEventListener("ended", () => {
622
- y();
623
- });
624
- let w = o;
625
- if (ce()) {
626
- const g = await fe(o);
627
- g && (n = g, w = g.stream);
628
- }
629
- s = new MediaRecorder(w, {
630
- mimeType: f,
631
- videoBitsPerSecond: i
632
- }), s.ondataavailable = (g) => {
633
- g.data.size > 0 && a.push(g.data);
634
- }, s.onstop = () => {
635
- const g = new Blob(a, { type: f });
636
- p && p(g), l && l(g), n && (n.stop(), n = null), o && o.getTracks().forEach((C) => C.stop());
637
- }, s.onerror = () => {
638
- u && u(new Error("MediaRecorder error"));
639
- }, s.start(1e3), c = Date.now(), r = setInterval(() => {
640
- const g = Math.floor((Date.now() - c) / 1e3);
641
- d && d(g);
642
- }, 1e3), b = setTimeout(() => {
643
- y();
644
- }, t * 1e3);
645
- },
646
- stop() {
647
- return new Promise((f, h) => {
648
- p = f, u = h, y();
649
- });
650
- },
651
- onTick(f) {
652
- d = f;
653
- },
654
- onStop(f) {
655
- l = f;
656
- },
657
- destroy() {
658
- y(), v();
659
- }
660
- };
661
- }
662
- function ye(e = 640) {
663
- return typeof window < "u" && window.innerWidth < e;
664
- }
665
- function K(e, t, i) {
666
- const o = i.map((s) => s.style.visibility);
667
- i.forEach((s) => s.style.visibility = "hidden");
668
- const n = document.elementFromPoint(e, t);
669
- return i.forEach((s, a) => s.style.visibility = o[a]), n;
670
- }
671
- function M(e) {
672
- return e ? e.hasAttribute("data-qaid-embed") || e.hasAttribute("data-qaid-embed-overlay") ? !0 : !!e.closest("[data-qaid-embed], [data-qaid-embed-overlay]") : !1;
673
- }
674
- function j(e, t = 0) {
675
- const i = e.getBoundingClientRect();
676
- return {
677
- x: i.left - t,
678
- y: i.top - t,
679
- width: i.width + t * 2,
680
- height: i.height + t * 2
681
- };
682
- }
683
- async function we(e = {}) {
684
- const { quality: t = 1, maxWidth: i = 1280, maxHeight: o = 800 } = e;
685
- try {
686
- if (!navigator.mediaDevices?.getDisplayMedia)
687
- return console.warn("Screen Capture API not available"), null;
688
- const n = await navigator.mediaDevices.getDisplayMedia({
689
- preferCurrentTab: !0,
690
- video: {
691
- displaySurface: "browser"
692
- }
693
- }), s = n.getVideoTracks()[0], a = s.getSettings(), d = document.createElement("video");
694
- d.srcObject = n, d.muted = !0, await new Promise((v) => {
695
- d.onloadedmetadata = () => {
696
- d.play(), v();
697
- };
698
- }), await new Promise((v) => {
699
- const y = () => {
700
- d.readyState >= 2 ? v() : requestAnimationFrame(y);
701
- };
702
- y();
703
- }), await new Promise((v) => setTimeout(v, 100));
704
- const l = a.width || d.videoWidth, r = a.height || d.videoHeight, c = Math.min(i / l, o / r, 1), p = Math.round(l * c), u = Math.round(r * c), m = document.createElement("canvas");
705
- m.width = p, m.height = u;
706
- const b = m.getContext("2d");
707
- return b ? (b.drawImage(d, 0, 0, p, u), s.stop(), m.toDataURL("image/webp", t)) : (s.stop(), null);
708
- } catch (n) {
709
- return console.warn("Screenshot capture failed:", n), null;
710
- }
711
- }
712
- const X = "https://qaid.dev/lib/html2canvas.min.js", xe = 1e4, qe = 50;
713
- let x = null;
714
- function ze() {
715
- if (typeof document > "u" || typeof window > "u")
716
- return !1;
717
- const e = document.createElement("canvas");
718
- return typeof e.getContext == "function" && !!e.getContext("2d");
719
- }
720
- function ke() {
721
- return window.html2canvas ? Promise.resolve(!0) : x || (x = new Promise((e) => {
722
- if (!document.querySelector(
723
- `script[src="${X}"]`
724
- )) {
725
- const n = document.createElement("script");
726
- n.src = X, n.async = !0, document.head.appendChild(n);
727
- }
728
- const i = Date.now(), o = () => {
729
- if (window.html2canvas) {
730
- e(!0);
731
- return;
732
- }
733
- if (Date.now() - i > xe) {
734
- x = null, e(!1);
735
- return;
736
- }
737
- setTimeout(o, qe);
738
- };
739
- o();
740
- }), x);
741
- }
742
- async function Ce(e = {}) {
743
- const { quality: t = 0.8, maxWidth: i = 1280, maxHeight: o = 800 } = e;
744
- try {
745
- if (!await ke() || !window.html2canvas)
746
- return console.warn("html2canvas failed to load"), null;
747
- const s = await window.html2canvas(document.body, {
748
- useCORS: !0,
749
- allowTaint: !1,
750
- logging: !1,
751
- windowWidth: document.documentElement.clientWidth,
752
- windowHeight: document.documentElement.clientHeight,
753
- width: document.documentElement.clientWidth,
754
- height: document.documentElement.clientHeight,
755
- x: window.scrollX,
756
- y: window.scrollY,
757
- ignoreElements: (r) => r instanceof HTMLElement ? r.classList.contains("qaid-buttons") || r.classList.contains("qaid-targeting-overlay") || r.classList.contains("qaid-modal-container") || r.classList.contains("qaid-bottom-sheet") || r.classList.contains("qaid-backdrop") || r.classList.contains("qaid-selected-marker") || r.classList.contains("qaid-tooltip-text") || r.classList.contains("qaid-recording-indicator") || r.classList.contains("qaid-video-preview") || r.className?.toString().startsWith?.("qaid-") : !1
758
- }), a = s.width, d = s.height, l = Math.min(i / a, o / d, 1);
759
- if (l < 1) {
760
- const r = Math.round(a * l), c = Math.round(d * l), p = document.createElement("canvas");
761
- p.width = r, p.height = c;
762
- const u = p.getContext("2d");
763
- return u ? (u.drawImage(s, 0, 0, r, c), p.toDataURL("image/webp", t)) : null;
764
- }
765
- return s.toDataURL("image/webp", t);
766
- } catch (n) {
767
- return console.warn("DOM screenshot capture failed:", n), null;
768
- }
769
- }
770
- const Se = "https://unpkg.com/@qaiddev/quests-embed@1/dist/qaid-quests.js", Ee = (e) => (
771
- // The URL is a runtime value, not a static specifier — keep Vite from
772
- // trying to analyze/bundle it.
773
- import(
774
- /* @vite-ignore */
775
- e
776
- )
777
- );
778
- let Te = Ee, q = null, R = null;
779
- function Le(e) {
780
- return q && R === e || (R = e, q = Promise.resolve(Te(e)).then((t) => {
781
- const i = t;
782
- if (!i || typeof i.QaidQuests != "function")
783
- throw new Error("quests module has no QaidQuests export");
784
- return i;
785
- }).catch((t) => {
786
- throw q = null, R = null, t;
787
- })), q;
788
- }
789
- async function Ie(e) {
790
- const t = await Le(e.moduleUrl), i = e.base.replace(/\/+$/, ""), o = e.feedbackId != null ? { feedbackId: e.feedbackId } : void 0;
791
- return new t.QaidQuests({
792
- endpoint: `${i}/responses`,
793
- configUrl: `${i}/${encodeURIComponent(e.questId)}/definition`,
794
- apiKey: e.apiKey || void 0,
795
- metadata: o,
796
- onClose: e.onClose
797
- });
798
- }
799
- const W = "qaid_visitor_id", Y = "qaid_hide_feedback", Ae = 12;
800
- function De() {
801
- return typeof window.matchMedia == "function" && window.matchMedia("(pointer: coarse)").matches;
802
- }
803
- function H(e) {
804
- return e ? `${Y}_${e}` : Y;
805
- }
806
- function Me(e) {
807
- try {
808
- return localStorage.getItem(H(e)) === "1";
809
- } catch {
810
- return !1;
811
- }
812
- }
813
- function Q(e, t = !0) {
814
- try {
815
- t ? localStorage.setItem(H(e), "1") : localStorage.removeItem(H(e));
816
- } catch {
817
- }
818
- }
819
- function Re() {
820
- try {
821
- let e = localStorage.getItem(W);
822
- return e || (e = crypto.randomUUID(), localStorage.setItem(W, e)), e;
823
- } catch {
824
- return crypto.randomUUID();
825
- }
826
- }
827
- class He {
828
- config;
829
- state = "IDLE";
830
- feedbackData = {
831
- feedbackType: null,
832
- elementSelector: null,
833
- elementText: null,
834
- consoleErrors: []
835
- };
836
- selectedBounds = {
837
- x: 0,
838
- y: 0,
839
- width: 0,
840
- height: 0,
841
- clickX: 0,
842
- clickY: 0,
843
- visible: !1
844
- };
845
- feedbackId = null;
846
- // A quest launched from a button (in place of the message box), if any.
847
- activeQuest = null;
848
- mousePos = { x: 0, y: 0 };
849
- isMobile = !1;
850
- visitorId;
851
- // Console capture
852
- consoleCapture = null;
853
- // Video recording
854
- videoRecorder = null;
855
- networkCapture = null;
856
- recordedBlob = null;
857
- recordingIndicator = null;
858
- videoPreview = null;
859
- isRecording = !1;
860
- isSendingVideo = !1;
861
- // Shadow DOM
862
- shadowHost = null;
863
- shadowRoot = null;
864
- // Overlay shadow DOM (always on document.body for full-page coverage)
865
- overlayShadowHost = null;
866
- overlayShadowRoot = null;
867
- // DOM elements (inside shadow root)
868
- buttonsContainer = null;
869
- isUserProvidedContainer = !1;
870
- overlayContainer = null;
871
- captureLayer = null;
872
- crosshairH = null;
873
- crosshairV = null;
874
- scope = null;
875
- highlightBox = null;
876
- marker = null;
877
- modalContainer = null;
878
- backdrop = null;
879
- dismissBtn = null;
880
- // Per-instance CSS variables
881
- cssVars = {};
882
- // Keyboard element-targeting
883
- keyboardController = null;
884
- activeThumbBtn = null;
885
- // Tracks whether the pending activation came from the keyboard (Enter/Space)
886
- // rather than a pointer, so targeting can avoid elementFromPoint(0,0).
887
- keyboardActivation = !1;
888
- // Accessibility: dialog focus management
889
- dialogTrigger = null;
890
- dialogTrap = null;
891
- dialogRestoreInert = null;
892
- // Unique id suffix for aria-labelledby/describedby references
893
- uid = Math.random().toString(36).slice(2, 9);
894
- // Bound event handlers
895
- boundKeyDown;
896
- boundMouseMove;
897
- boundClick;
898
- // Touch equivalents for targeting. iOS/iPadOS does not synthesise `click`
899
- // (or `mousemove`) for taps on non-interactive page elements, so targeting
900
- // must be driven by touch events there.
901
- boundTouchStart;
902
- boundTouchEnd;
903
- // Where the current targeting touch began, to tell a tap from a scroll.
904
- touchStartPos = null;
905
- boundResize;
906
- // Start with buttons slid off-screen (localStorage dismiss, no animation)
907
- _startDismissed = !1;
908
- // DOM persistence (survives framework client-side navigation)
909
- destroyed = !1;
910
- domObserver = null;
911
- boundBeforeSwap = null;
912
- constructor(t) {
913
- this.config = {
914
- endpoint: t.endpoint,
915
- apiKey: t.apiKey ?? "",
916
- container: t.container ?? "",
917
- buttonClass: t.buttonClass ?? "",
918
- direction: t.direction ?? "horizontal",
919
- position: t.position ?? "bottom-right",
920
- offset: {
921
- x: t.offset?.x ?? 16,
922
- y: t.offset?.y ?? 16
923
- },
924
- zIndex: t.zIndex ?? 50,
925
- skipTargeting: t.skipTargeting ?? !1,
926
- colors: {
927
- positive: t.colors?.positive ?? "rgb(0, 200, 83)",
928
- negative: t.colors?.negative ?? "rgb(255, 0, 0)",
929
- marker: t.colors?.marker ?? "#6366f1"
930
- },
931
- buttonSize: t.buttonSize ?? "medium",
932
- text: {
933
- tooltip: t.text?.tooltip ?? "",
934
- modalTitle: t.text?.modalTitle ?? "Thank you for your feedback!",
935
- modalSubtitle: t.text?.modalSubtitle ?? "Would you like to add a message to help us understand your feedback better?",
936
- placeholder: t.text?.placeholder ?? "Optional: Tell us more about your experience...",
937
- submitButton: t.text?.submitButton ?? "Submit",
938
- skipButton: t.text?.skipButton ?? "Skip",
939
- positiveLabel: t.text?.positiveLabel ?? "Send positive feedback",
940
- negativeLabel: t.text?.negativeLabel ?? "Send negative feedback",
941
- recordLabel: t.text?.recordLabel ?? "Record a screen recording",
942
- dismissLabel: t.text?.dismissLabel ?? "Hide Feedback"
943
- },
944
- modalWidth: t.modalWidth ?? 400,
945
- backdropOpacity: t.backdropOpacity ?? 0.3,
946
- fontFamily: t.fontFamily ?? "system-ui, -apple-system, sans-serif",
947
- fontSize: t.fontSize ?? 16,
948
- captureScreenshot: t.captureScreenshot ?? !1,
949
- screenshotMethod: t.screenshotMethod ?? "permission",
950
- screenshotOptions: {
951
- quality: t.screenshotOptions?.quality ?? 0.8,
952
- maxWidth: t.screenshotOptions?.maxWidth ?? 1280,
953
- maxHeight: t.screenshotOptions?.maxHeight ?? 800
954
- },
955
- incognito: t.incognito ?? !1,
956
- hideDismiss: t.hideDismiss ?? !1,
957
- positiveIcon: t.positiveIcon ?? "",
958
- negativeIcon: t.negativeIcon ?? "",
959
- hideThumbs: t.hideThumbs ?? !1,
960
- css: t.css ?? "",
961
- captureVideo: t.captureVideo ?? !1,
962
- videoOptions: {
963
- maxDuration: t.videoOptions?.maxDuration ?? 15
964
- },
965
- recordIcon: t.recordIcon ?? "",
966
- quests: {
967
- base: t.quests?.base ?? "",
968
- up: t.quests?.up ?? "",
969
- down: t.quests?.down ?? "",
970
- video: t.quests?.video ?? "",
971
- // Quest service reuses the feedback API key unless overridden.
972
- apiKey: t.quests?.apiKey ?? t.apiKey ?? "",
973
- moduleUrl: t.quests?.moduleUrl ?? Se
974
- }
975
- }, this.boundKeyDown = this.handleKeyDown.bind(this), this.boundMouseMove = this.handleMouseMove.bind(this), this.boundClick = this.handleClick.bind(this), this.boundTouchStart = this.handleTouchStart.bind(this), this.boundTouchEnd = this.handleTouchEnd.bind(this), this.boundResize = this.handleResize.bind(this), this.visitorId = Re(), this.init();
976
- }
977
- applyVars(t) {
978
- Et(t, this.cssVars);
979
- }
980
- /**
981
- * Announce a message via the shared visually-hidden live regions.
982
- * Prefer the overlay shadow root (which hosts every transient surface and
983
- * is never inerted by its own dialogs) so announcements are not suppressed
984
- * while a dialog aria-hides the main button host.
985
- */
986
- announceMsg(t, i = !1) {
987
- const o = this.overlayShadowRoot ?? this.shadowRoot;
988
- o && Ut(o, t, { assertive: i });
989
- }
990
- /**
991
- * Turn a transient surface into an accessible modal dialog: save the
992
- * invoking control, apply dialog semantics, trap focus, and inert the
993
- * background. Paired with closeDialogA11y() on every close path.
994
- */
995
- openDialogA11y(t, i) {
996
- this.dialogTrigger = Xt(), Yt(t, i), this.dialogTrap = jt(t), this.dialogRestoreInert = Gt(t);
997
- }
998
- closeDialogA11y() {
999
- this.dialogTrap?.release(), this.dialogTrap = null, this.dialogRestoreInert && (this.dialogRestoreInert(), this.dialogRestoreInert = null), Wt(this.dialogTrigger), this.dialogTrigger = null;
1000
- }
1001
- clearActiveThumb() {
1002
- this.activeThumbBtn && (this.activeThumbBtn.setAttribute("aria-pressed", "false"), this.activeThumbBtn = null);
1003
- }
1004
- init() {
1005
- Tt(), this.cssVars = St({
1006
- positiveColor: this.config.colors.positive,
1007
- negativeColor: this.config.colors.negative,
1008
- markerColor: this.config.colors.marker,
1009
- buttonSize: this.config.buttonSize,
1010
- modalWidth: this.config.modalWidth,
1011
- backdropOpacity: this.config.backdropOpacity,
1012
- fontFamily: this.config.fontFamily,
1013
- fontSize: this.config.fontSize
1014
- }), !this.config.hideDismiss && Me(this.config.apiKey) && (this._startDismissed = !0), this.checkMobile(), window.addEventListener("resize", this.boundResize), this.createEmbed(), this.consoleCapture = se((t) => {
1015
- this.feedbackData.consoleErrors = this.consoleCapture?.errors ?? [];
1016
- }), this.feedbackData.consoleErrors = this.consoleCapture.errors, this.observeDom();
1017
- }
1018
- /**
1019
- * Watch for the shadow hosts being removed from the DOM by framework
1020
- * client-side navigation (e.g. Astro View Transitions swapping <body>
1021
- * contents, or any SPA router that replaces DOM subtrees). If the host
1022
- * is disconnected and destroy() wasn't called, re-append it.
1023
- *
1024
- * Also hooks into Astro's `astro:before-swap` when available, which
1025
- * lets us carry elements into the new document before the swap happens
1026
- * (avoids a flash of the widget disappearing and reappearing).
1027
- */
1028
- observeDom() {
1029
- this.boundBeforeSwap = (t) => {
1030
- const i = t.newDocument;
1031
- !i || this.destroyed || (this.shadowHost && i.body.appendChild(this.shadowHost), this.overlayShadowHost && i.body.appendChild(this.overlayShadowHost));
1032
- }, document.addEventListener("astro:before-swap", this.boundBeforeSwap), this.domObserver = new MutationObserver(() => {
1033
- this.destroyed || (this.shadowHost && !this.shadowHost.isConnected && document.body.appendChild(this.shadowHost), this.overlayShadowHost && !this.overlayShadowHost.isConnected && document.body.appendChild(this.overlayShadowHost));
1034
- }), this.domObserver.observe(document.body, { childList: !0 });
1035
- }
1036
- checkMobile() {
1037
- this.isMobile = ye();
1038
- }
1039
- handleResize() {
1040
- this.checkMobile();
1041
- }
1042
- createEmbed() {
1043
- this.shadowHost = document.createElement("div"), this.shadowHost.setAttribute("data-qaid-embed", ""), this.shadowHost.style.position = "static", this.shadowHost.style.display = "contents";
1044
- let t = null;
1045
- this.config.container && (t = document.querySelector(this.config.container)), t ? (t.appendChild(this.shadowHost), this.isUserProvidedContainer = !0, this.config.hideDismiss = !0) : (this.shadowHost.style.position = "fixed", this.shadowHost.style.display = "block", this.shadowHost.style.inset = "0", this.shadowHost.style.pointerEvents = "none", this.shadowHost.style.zIndex = String(this.config.zIndex), document.body.appendChild(this.shadowHost)), this.shadowRoot = this.shadowHost.attachShadow({ mode: "open" });
1046
- const i = document.createElement("style");
1047
- if (i.textContent = N(), this.shadowRoot.appendChild(i), this.config.css) {
1048
- const r = document.createElement("style");
1049
- r.textContent = this.config.css, this.shadowRoot.appendChild(r);
1050
- }
1051
- const o = this.config.direction === "vertical" ? "qaid-vertical" : "";
1052
- if (this.isUserProvidedContainer)
1053
- this.buttonsContainer = document.createElement("div"), this.buttonsContainer.className = `qaid-buttons qaid-${this.config.position} ${o}`.trim(), this.config.incognito && this.buttonsContainer.classList.add("qaid-incognito");
1054
- else {
1055
- this.buttonsContainer = document.createElement("div"), this.buttonsContainer.className = `qaid-buttons qaid-auto-container qaid-${this.config.position} ${o}${this.config.incognito ? " qaid-incognito" : ""}`.trim(), this.buttonsContainer.style.pointerEvents = "auto";
1056
- const { x: r, y: c } = this.config.offset;
1057
- this.config.position.includes("right") ? this.buttonsContainer.style.right = `${r}px` : this.buttonsContainer.style.left = `${r}px`, this.config.position.includes("bottom") ? this.buttonsContainer.style.bottom = `${c}px` : this.buttonsContainer.style.top = `${c}px`;
1058
- }
1059
- this.applyVars(this.buttonsContainer), this.shadowRoot.appendChild(this.buttonsContainer), this.buttonsContainer.addEventListener("keydown", (r) => {
1060
- (r.key === "Enter" || r.key === " " || r.key === "Spacebar") && (this.keyboardActivation = !0);
1061
- }), this.buttonsContainer.addEventListener("mousedown", () => {
1062
- this.keyboardActivation = !1;
1063
- }), this.buttonsContainer.addEventListener("pointerdown", () => {
1064
- this.keyboardActivation = !1;
1065
- });
1066
- const n = !!this.config.buttonClass, s = n ? `qaid-btn-structural ${this.config.buttonClass}` : "qaid-btn", d = this.config.text.tooltip || "Feedback for us?", l = document.createElement("div");
1067
- if (l.className = "qaid-tooltip-text", l.textContent = d, this.applyVars(l), this.shadowRoot.appendChild(l), this.tooltipElement = l, !this.config.hideThumbs) {
1068
- const r = document.createElement("div");
1069
- r.className = "qaid-tooltip-wrapper";
1070
- const c = document.createElement("button");
1071
- c.type = "button", c.className = n ? `${s} qaid-btn-up` : "qaid-btn qaid-btn-up", c.setAttribute("aria-label", this.config.text.positiveLabel), c.innerHTML = this.config.positiveIcon || I, c.addEventListener("click", (m) => this.handleThumbClick("up", m.currentTarget, m)), c.addEventListener("mouseenter", () => this.showTooltip(c)), c.addEventListener("mouseleave", () => this.hideTooltip()), r.appendChild(c);
1072
- const p = document.createElement("div");
1073
- p.className = "qaid-tooltip-wrapper";
1074
- const u = document.createElement("button");
1075
- u.type = "button", u.className = n ? `${s} qaid-btn-down` : "qaid-btn qaid-btn-down", u.setAttribute("aria-label", this.config.text.negativeLabel), u.innerHTML = this.config.negativeIcon || A, u.addEventListener("click", (m) => this.handleThumbClick("down", m.currentTarget, m)), u.addEventListener("mouseenter", () => this.showTooltip(u)), u.addEventListener("mouseleave", () => this.hideTooltip()), p.appendChild(u), this.buttonsContainer.appendChild(r), this.buttonsContainer.appendChild(p);
1076
- }
1077
- if (this.config.captureVideo && be()) {
1078
- const r = document.createElement("div");
1079
- r.className = "qaid-tooltip-wrapper";
1080
- const c = document.createElement("button");
1081
- c.type = "button", c.className = n ? `${s} qaid-btn-record` : "qaid-btn qaid-btn-record", c.setAttribute("aria-label", this.config.text.recordLabel), c.innerHTML = this.config.recordIcon || bt, c.addEventListener("click", () => this.startRecording()), c.addEventListener("mouseenter", () => this.showTooltip(c)), c.addEventListener("mouseleave", () => this.hideTooltip()), r.appendChild(c), this.buttonsContainer.appendChild(r);
1082
- }
1083
- this.config.hideDismiss || (this.dismissBtn = document.createElement("button"), this.dismissBtn.type = "button", this.dismissBtn.className = "qaid-dismiss-btn", this.dismissBtn.setAttribute("aria-label", this.config.text.dismissLabel), this.dismissBtn.title = this.config.text.dismissLabel, this.dismissBtn.innerHTML = '<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2 2l8 8M10 2l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>', this.dismissBtn.addEventListener("click", (r) => {
1084
- r.stopPropagation(), this.handleDismiss();
1085
- }), this.buttonsContainer.insertBefore(this.dismissBtn, this.buttonsContainer.firstChild), this._startDismissed && this.buttonsContainer.classList.add("qaid-dismissed"));
1086
- }
1087
- /**
1088
- * Lazily create a separate overlay shadow host on document.body.
1089
- * This host contains all full-page elements (targeting overlay, marker,
1090
- * backdrop, modal, recording indicator, video preview) so they escape
1091
- * clip-path / transform containing blocks in user containers.
1092
- */
1093
- ensureOverlayHost() {
1094
- if (this.overlayShadowRoot) return this.overlayShadowRoot;
1095
- this.overlayShadowHost = document.createElement("div"), this.overlayShadowHost.setAttribute("data-qaid-embed-overlay", ""), this.overlayShadowHost.style.position = "fixed", this.overlayShadowHost.style.inset = "0", this.overlayShadowHost.style.pointerEvents = "none", this.overlayShadowHost.style.zIndex = String(this.config.zIndex), document.body.appendChild(this.overlayShadowHost), this.overlayShadowRoot = this.overlayShadowHost.attachShadow({ mode: "open" });
1096
- const t = document.createElement("style");
1097
- if (t.textContent = N(), this.overlayShadowRoot.appendChild(t), this.config.css) {
1098
- const i = document.createElement("style");
1099
- i.textContent = this.config.css, this.overlayShadowRoot.appendChild(i);
1100
- }
1101
- return this.overlayShadowRoot;
1102
- }
1103
- tooltipElement = null;
1104
- showTooltip(t) {
1105
- const i = this.tooltipElement;
1106
- i.style.visibility = "hidden", i.classList.add("qaid-tooltip-visible");
1107
- const o = t.getBoundingClientRect(), n = i.getBoundingClientRect(), { top: s, left: a } = ie(
1108
- o,
1109
- n,
1110
- { width: window.innerWidth, height: window.innerHeight }
1111
- );
1112
- i.style.top = `${s}px`, i.style.left = `${a}px`, i.style.visibility = "visible";
1113
- }
1114
- hideTooltip() {
1115
- this.tooltipElement.classList.remove("qaid-tooltip-visible");
1116
- }
1117
- handleDismiss() {
1118
- this.buttonsContainer.classList.add("qaid-dismissed"), Q(this.config.apiKey, !0);
1119
- }
1120
- handleThumbClick(t, i, o) {
1121
- if (this.buttonsContainer?.classList.contains("qaid-incognito") && (this.buttonsContainer.classList.remove("qaid-incognito"), Q(this.config.apiKey, !1)), this.config.skipTargeting)
1122
- this.submitDirectFeedback(t, i);
1123
- else {
1124
- this.activeThumbBtn = i, i.setAttribute("aria-pressed", "true");
1125
- const n = this.keyboardActivation;
1126
- this.keyboardActivation = !1, n ? this.startKeyboardTargetingFlow(t) : this.startTargeting(t, o);
1127
- }
1128
- }
1129
- submitDirectFeedback(t, i) {
1130
- this.feedbackData.feedbackType = t, this.feedbackData.elementSelector = null, this.feedbackData.elementText = null;
1131
- const o = i.getBoundingClientRect();
1132
- this.selectedBounds = {
1133
- x: o.left,
1134
- y: o.top,
1135
- width: o.width,
1136
- height: o.height,
1137
- clickX: o.left + o.width / 2,
1138
- clickY: o.top + o.height / 2,
1139
- visible: !1
1140
- }, this.submitFeedback();
1141
- }
1142
- startTargeting(t, i) {
1143
- this.state = "TARGETING", this.feedbackData.feedbackType = t, this.feedbackData.elementSelector = null, this.feedbackData.elementText = null, this.selectedBounds.visible = !1, this.mousePos.x = i.clientX, this.mousePos.y = i.clientY, document.body.classList.add("qaid-targeting"), t === "up" ? document.body.classList.add("qaid-type-up") : document.body.classList.remove("qaid-type-up"), document.body.style.setProperty("--qaid-positive", this.cssVars["--qaid-positive"]), document.body.style.setProperty("--qaid-negative", this.cssVars["--qaid-negative"]), this.createTargetingOverlay(), document.addEventListener("keydown", this.boundKeyDown), document.addEventListener("mousemove", this.boundMouseMove), document.addEventListener("click", this.boundClick, !0), document.addEventListener("touchstart", this.boundTouchStart, { passive: !0 }), document.addEventListener("touchend", this.boundTouchEnd, { passive: !1 });
1144
- }
1145
- /**
1146
- * Keyboard-driven targeting. Mirrors startTargeting minus the mouse
1147
- * plumbing: no `qaid-targeting` body class (keeps the cursor visible for
1148
- * keyboard users), no mouse reticle, and no document mouse/click listeners.
1149
- * The KeyboardTargetingController owns Tab/Arrow/Enter/Space/Escape.
1150
- */
1151
- startKeyboardTargetingFlow(t) {
1152
- this.state = "TARGETING", this.feedbackData.feedbackType = t, this.feedbackData.elementSelector = null, this.feedbackData.elementText = null, this.selectedBounds.visible = !1, document.body.style.setProperty("--qaid-positive", this.cssVars["--qaid-positive"]), document.body.style.setProperty("--qaid-negative", this.cssVars["--qaid-negative"]), this.createTargetingOverlay(), this.crosshairH && (this.crosshairH.style.display = "none"), this.crosshairV && (this.crosshairV.style.display = "none"), this.scope && (this.scope.style.display = "none"), this.keyboardController = Ot({
1153
- isExcluded: (i) => M(i),
1154
- onHighlight: (i) => {
1155
- const o = i.getBoundingClientRect(), n = this.highlightBox;
1156
- n && (n.style.transform = `translate(${o.left}px, ${o.top}px)`, n.style.width = `${o.width}px`, n.style.height = `${o.height}px`, n.style.display = "block");
1157
- const { text: s } = D(i);
1158
- this.announceMsg(`Targeting ${s || i.tagName.toLowerCase()}`);
1159
- },
1160
- onSelect: (i) => this.selectKeyboardTarget(i),
1161
- onCancel: () => this.cancelTargeting()
1162
- });
1163
- }
1164
- selectKeyboardTarget(t) {
1165
- const i = j(t, 8);
1166
- this.selectedBounds = {
1167
- ...i,
1168
- clickX: i.x + i.width / 2,
1169
- clickY: i.y + i.height / 2,
1170
- visible: !0
1171
- };
1172
- const { selector: o, text: n } = D(t);
1173
- this.feedbackData.elementSelector = o, this.feedbackData.elementText = n, this.removeTargetingOverlay(), document.body.classList.remove("qaid-targeting", "qaid-type-up"), document.body.style.removeProperty("--qaid-positive"), document.body.style.removeProperty("--qaid-negative"), this.keyboardController = null, this.clearActiveThumb(), this.state = "SELECTED", this.showSelectedMarker(), this.submitFeedback();
1174
- }
1175
- createTargetingOverlay() {
1176
- const t = this.ensureOverlayHost();
1177
- this.overlayContainer = document.createElement("div"), this.overlayContainer.className = `qaid-targeting-overlay qaid-type-${this.feedbackData.feedbackType}`, this.captureLayer = document.createElement("div"), this.captureLayer.className = "qaid-capture-layer";
1178
- const i = document.createElement("div");
1179
- i.className = "qaid-vignette", this.crosshairH = document.createElement("div"), this.crosshairH.className = "qaid-crosshair-h", this.crosshairV = document.createElement("div"), this.crosshairV.className = "qaid-crosshair-v", this.scope = document.createElement("div"), this.scope.className = "qaid-scope", this.scope.innerHTML = `
1180
- <div class="qaid-scope-ring"></div>
1181
- <div class="qaid-scope-ring-inner"></div>
1182
- <div class="qaid-scope-dot"></div>
1183
- `, this.highlightBox = document.createElement("div"), this.highlightBox.className = "qaid-highlight-box", this.overlayContainer.appendChild(this.captureLayer), this.overlayContainer.appendChild(i), this.overlayContainer.appendChild(this.highlightBox), this.overlayContainer.appendChild(this.crosshairH), this.overlayContainer.appendChild(this.crosshairV), this.overlayContainer.appendChild(this.scope), this.crosshairH.style.top = `${this.mousePos.y}px`, this.crosshairV.style.left = `${this.mousePos.x}px`, this.scope.style.left = `${this.mousePos.x}px`, this.scope.style.top = `${this.mousePos.y}px`, this.applyVars(this.overlayContainer), t.appendChild(this.overlayContainer);
1184
- }
1185
- handleKeyDown(t) {
1186
- t.key === "Escape" && (this.isRecording ? this.stopRecording() : this.videoPreview ? this.cancelRecordingPreview() : this.state === "TARGETING" ? this.cancelTargeting() : this.state === "MODAL_OPEN" && this.closeModal());
1187
- }
1188
- handleMouseMove(t) {
1189
- this.updateReticleAt(t.clientX, t.clientY);
1190
- }
1191
- /** Move the crosshair/scope reticle and highlight the element under (x, y).
1192
- * Shared by the mouse (hover) and touch (drag) targeting paths. */
1193
- updateReticleAt(t, i) {
1194
- if (this.mousePos.x = t, this.mousePos.y = i, this.crosshairH && (this.crosshairH.style.top = `${i}px`), this.crosshairV && (this.crosshairV.style.left = `${t}px`), this.scope && (this.scope.style.left = `${t}px`, this.scope.style.top = `${i}px`), this.captureLayer && this.shadowHost) {
1195
- const o = [this.shadowHost, this.overlayShadowHost].filter(Boolean), n = K(t, i, o);
1196
- if (n && !M(n)) {
1197
- if (this.highlightBox) {
1198
- const s = n.getBoundingClientRect();
1199
- this.highlightBox.style.transform = `translate(${s.left}px, ${s.top}px)`, this.highlightBox.style.width = `${s.width}px`, this.highlightBox.style.height = `${s.height}px`, this.highlightBox.style.display = "block";
1200
- }
1201
- } else
1202
- this.highlightBox && (this.highlightBox.style.display = "none");
1203
- }
1204
- }
1205
- handleClick(t) {
1206
- t.preventDefault(), t.stopPropagation(), this.selectAt(t.clientX, t.clientY);
1207
- }
1208
- // ---- Touch targeting (iOS/iPadOS) ----
1209
- // iOS/iPadOS taps on non-interactive elements don't fire click, so touch
1210
- // drives targeting here. Scrolling stays enabled (the page may need to scroll
1211
- // to bring the target into view); a low-movement touch is treated as a tap
1212
- // that selects, distinguishing it from a scroll/drag.
1213
- handleTouchStart(t) {
1214
- const i = t.touches[0];
1215
- i && (this.touchStartPos = { x: i.clientX, y: i.clientY }, this.updateReticleAt(i.clientX, i.clientY));
1216
- }
1217
- handleTouchEnd(t) {
1218
- const i = t.changedTouches[0], o = this.touchStartPos;
1219
- this.touchStartPos = null, !(!i || !o || Math.hypot(i.clientX - o.x, i.clientY - o.y) > Ae) && (t.preventDefault(), this.selectAt(i.clientX, i.clientY));
1220
- }
1221
- /** Select the element under (x, y) and tear down targeting.
1222
- * Shared by the mouse (click) and touch (touchend) targeting paths. */
1223
- selectAt(t, i) {
1224
- const o = [this.shadowHost, this.overlayShadowHost].filter(Boolean), n = K(t, i, o);
1225
- if (!n || M(n))
1226
- return;
1227
- const s = j(n, 8);
1228
- this.selectedBounds = {
1229
- ...s,
1230
- clickX: t,
1231
- clickY: i,
1232
- visible: !0
1233
- };
1234
- const { selector: a, text: d } = D(n);
1235
- this.feedbackData.elementSelector = a, this.feedbackData.elementText = d, this.stopTargetingListeners(), document.body.classList.remove("qaid-targeting", "qaid-type-up"), document.body.style.removeProperty("--qaid-positive"), document.body.style.removeProperty("--qaid-negative"), this.state = "SELECTED", this.clearActiveThumb(), this.showSelectedMarker(), this.submitFeedback();
1236
- }
1237
- /** Remove the targeting overlay and every mouse/touch/keyboard listener the
1238
- * pointer-targeting flow attaches to the document. */
1239
- stopTargetingListeners() {
1240
- this.removeTargetingOverlay(), this.touchStartPos = null, document.removeEventListener("mousemove", this.boundMouseMove), document.removeEventListener("click", this.boundClick, !0), document.removeEventListener("touchstart", this.boundTouchStart), document.removeEventListener("touchend", this.boundTouchEnd), document.removeEventListener("keydown", this.boundKeyDown);
1241
- }
1242
- cancelTargeting() {
1243
- this.keyboardController?.stop(), this.keyboardController = null, this.stopTargetingListeners(), document.body.classList.remove("qaid-targeting", "qaid-type-up"), document.body.style.removeProperty("--qaid-positive"), document.body.style.removeProperty("--qaid-negative"), this.clearActiveThumb(), this.state = "IDLE", this.feedbackData.feedbackType = null, this.feedbackData.elementSelector = null, this.feedbackData.elementText = null, this.selectedBounds.visible = !1;
1244
- }
1245
- removeTargetingOverlay() {
1246
- this.overlayContainer && (this.overlayContainer.remove(), this.overlayContainer = null), this.captureLayer = null, this.crosshairH = null, this.crosshairV = null, this.scope = null;
1247
- }
1248
- showSelectedMarker() {
1249
- const t = this.ensureOverlayHost();
1250
- this.marker = document.createElement("div"), this.marker.className = "qaid-selected-marker", this.marker.style.left = `${this.selectedBounds.x}px`, this.marker.style.top = `${this.selectedBounds.y}px`, this.marker.style.width = `${this.selectedBounds.width}px`, this.marker.style.height = `${this.selectedBounds.height}px`, this.marker.style.zIndex = String(this.config.zIndex + 1), this.applyVars(this.marker), t.appendChild(this.marker);
1251
- }
1252
- hideSelectedMarker() {
1253
- this.marker && (this.marker.remove(), this.marker = null);
1254
- }
1255
- /** Whether to capture the screenshot with the DOM/canvas method (html2canvas)
1256
- * instead of the permission-based Screen Capture API. Explicit "dom" wins;
1257
- * otherwise DOM is used on touch devices to avoid the getDisplayMedia prompt. */
1258
- shouldCaptureViaDom() {
1259
- return this.config.screenshotMethod === "dom" || De();
1260
- }
1261
- async submitFeedback() {
1262
- let t = null;
1263
- this.config.captureScreenshot && (t = this.shouldCaptureViaDom() ? await Ce(this.config.screenshotOptions) : await we(this.config.screenshotOptions), t && this.announceMsg("Screenshot captured"));
1264
- const i = this.feedbackData.elementSelector ? {
1265
- x: this.selectedBounds.x,
1266
- y: this.selectedBounds.y,
1267
- width: this.selectedBounds.width,
1268
- height: this.selectedBounds.height
1269
- } : null, o = {
1270
- feedbackType: this.feedbackData.feedbackType,
1271
- pageUrl: window.location.href,
1272
- apiKey: this.config.apiKey || void 0,
1273
- elementSelector: this.feedbackData.elementSelector,
1274
- elementText: this.feedbackData.elementText,
1275
- consoleErrors: [...this.feedbackData.consoleErrors],
1276
- screenWidth: window.innerWidth,
1277
- screenHeight: window.innerHeight,
1278
- clickX: this.selectedBounds.clickX,
1279
- clickY: this.selectedBounds.clickY,
1280
- scrollX: window.scrollX,
1281
- scrollY: window.scrollY,
1282
- screenshot: t,
1283
- visitorId: this.visitorId,
1284
- // Include element bounds and user agent for server-side screenshot fallback
1285
- elementBounds: i,
1286
- userAgent: navigator.userAgent
1287
- };
1288
- try {
1289
- const s = await fetch(this.config.endpoint, {
1290
- method: "POST",
1291
- headers: { "Content-Type": "application/json" },
1292
- body: JSON.stringify(o)
1293
- });
1294
- if (s.ok) {
1295
- const a = await s.json();
1296
- this.feedbackId = a.id, this.announceMsg("Feedback sent");
1297
- }
1298
- } catch (s) {
1299
- console.error("Failed to submit feedback:", s), this.announceMsg("Failed to send feedback", !0);
1300
- }
1301
- const n = this.feedbackData.feedbackType;
1302
- if (n && await this.tryLaunchQuest(n, this.feedbackId)) {
1303
- this.resetFeedbackUi();
1304
- return;
1305
- }
1306
- this.state = "MODAL_OPEN", this.showModal(), this.overlayShadowHost && (this.overlayShadowHost.style.pointerEvents = "auto");
1307
- }
1308
- /**
1309
- * Quest id linked to `type`, or "" when quest launching is disabled
1310
- * (no `base`) or this button has no quest configured.
1311
- */
1312
- questIdFor(t) {
1313
- const i = this.config.quests;
1314
- return i.base && i[t] || "";
1315
- }
1316
- /**
1317
- * Launch the quest linked to `type`, if any. Resolves `true` when a quest
1318
- * was configured and the widget launched; `false` when no quest is
1319
- * configured or the widget failed to load (caller falls back to its
1320
- * normal UI). The created feedback record id is passed through so the
1321
- * quest response can be joined back to it server-side.
1322
- */
1323
- async tryLaunchQuest(t, i) {
1324
- const o = this.questIdFor(t);
1325
- if (!o) return !1;
1326
- try {
1327
- return this.activeQuest?.destroy(), this.activeQuest = await Ie({
1328
- questId: o,
1329
- base: this.config.quests.base,
1330
- apiKey: this.config.quests.apiKey || void 0,
1331
- moduleUrl: this.config.quests.moduleUrl,
1332
- feedbackId: i,
1333
- onClose: () => {
1334
- this.activeQuest = null;
1335
- }
1336
- }), !0;
1337
- } catch (n) {
1338
- return console.error("Failed to launch quest:", n), this.activeQuest = null, !1;
1339
- }
1340
- }
1341
- /**
1342
- * Reset the thumbs targeting/marker UI back to idle without opening or
1343
- * closing the message modal. Shared by closeModal() and the quest-launch
1344
- * path (which bypasses the modal entirely).
1345
- */
1346
- resetFeedbackUi() {
1347
- this.hideSelectedMarker(), this.overlayShadowHost && (this.overlayShadowHost.style.pointerEvents = "none"), this.state = "IDLE", this.feedbackId = null, this.feedbackData.feedbackType = null, this.feedbackData.elementSelector = null, this.feedbackData.elementText = null, this.selectedBounds.visible = !1;
1348
- }
1349
- showModal() {
1350
- const t = this.ensureOverlayHost();
1351
- this.backdrop = document.createElement("div"), this.backdrop.className = "qaid-backdrop", this.backdrop.style.zIndex = String(this.config.zIndex + 2), this.backdrop.style.background = `rgba(0, 0, 0, ${this.config.backdropOpacity})`, this.backdrop.addEventListener("click", () => this.closeModal()), this.applyVars(this.backdrop), this.isMobile ? this.showBottomSheet() : this.showPositionedModal(), t.appendChild(this.backdrop), document.addEventListener("keydown", this.boundKeyDown);
1352
- }
1353
- showBottomSheet() {
1354
- const t = this.ensureOverlayHost(), i = document.createElement("div");
1355
- i.className = "qaid-bottom-sheet", i.style.zIndex = String(this.config.zIndex + 3), i.innerHTML = `
1356
- <div class="qaid-bottom-sheet-content">
1357
- <div class="qaid-bottom-sheet-handle"></div>
1358
- ${this.getModalContent()}
1359
- </div>
1360
- `, this.applyVars(i), t.appendChild(i), this.modalContainer = i, this.setupModalInteractions();
1361
- }
1362
- showPositionedModal() {
1363
- const t = this.ensureOverlayHost(), { modal: i, arrow: o } = oe(
1364
- this.selectedBounds,
1365
- window.innerWidth,
1366
- window.innerHeight,
1367
- {
1368
- width: this.config.modalWidth,
1369
- height: 280,
1370
- arrowHeight: 12,
1371
- gap: 8,
1372
- viewportPadding: 16
1373
- }
1374
- );
1375
- this.modalContainer = document.createElement("div"), this.modalContainer.className = `qaid-modal-container qaid-${i.position}`, this.modalContainer.style.top = `${i.top}px`, this.modalContainer.style.left = `${i.left}px`, this.modalContainer.style.zIndex = String(this.config.zIndex + 3);
1376
- const n = document.createElement("div");
1377
- n.className = "qaid-modal-arrow", n.style.left = `${o.left}px`;
1378
- const s = document.createElement("div");
1379
- s.className = "qaid-modal-box", s.innerHTML = this.getModalContent(), this.modalContainer.appendChild(n), this.modalContainer.appendChild(s), this.applyVars(this.modalContainer), t.appendChild(this.modalContainer), this.setupModalInteractions();
1380
- }
1381
- getModalContent() {
1382
- const t = this.feedbackData.feedbackType === "up", i = this.config.positiveIcon || I, o = this.config.negativeIcon || A;
1383
- return `
1384
- <div class="qaid-modal-header">
1385
- <button type="button" class="${this.config.buttonClass ? `qaid-type-toggle qaid-type-toggle-custom ${this.config.buttonClass} ${t ? "qaid-btn-up" : "qaid-btn-down"}` : `qaid-type-toggle ${t ? "qaid-type-up" : "qaid-type-down"}`}" title="Click to switch" aria-pressed="${t}" aria-label="${t ? "Feedback type: positive" : "Feedback type: negative"}">
1386
- ${t ? i : o}
1387
- </button>
1388
- <div class="qaid-modal-header-text">
1389
- <h3 class="qaid-modal-title" id="qaid-modal-title-${this.uid}">${this.config.text.modalTitle}</h3>
1390
- <p class="qaid-modal-subtitle" id="qaid-modal-subtitle-${this.uid}">${this.config.text.modalSubtitle}</p>
1391
- </div>
1392
- </div>
1393
- <textarea class="qaid-textarea" aria-label="${this.config.text.modalSubtitle}" placeholder="${this.config.text.placeholder}"></textarea>
1394
- <div class="qaid-btn-row">
1395
- <button type="button" class="qaid-btn-submit">${this.config.text.skipButton}</button>
1396
- </div>
1397
- `;
1398
- }
1399
- setupModalInteractions() {
1400
- this.openDialogA11y(this.modalContainer, {
1401
- labelledbyId: `qaid-modal-title-${this.uid}`,
1402
- describedbyId: `qaid-modal-subtitle-${this.uid}`
1403
- });
1404
- const t = this.modalContainer.querySelector(".qaid-textarea"), i = this.modalContainer.querySelector(".qaid-btn-submit");
1405
- t && (setTimeout(() => t.focus(), 100), t.addEventListener("input", () => {
1406
- i && (i.textContent = t.value.trim() ? this.config.text.submitButton : this.config.text.skipButton);
1407
- })), i && i.addEventListener("click", () => {
1408
- const n = t?.value.trim() || null;
1409
- this.submitMessage(n);
1410
- });
1411
- const o = this.modalContainer.querySelector(".qaid-type-toggle");
1412
- o && o.addEventListener("click", () => {
1413
- const n = this.feedbackData.feedbackType === "up" ? "down" : "up";
1414
- this.feedbackData.feedbackType = n, this.config.buttonClass ? (o.classList.toggle("qaid-btn-up", n === "up"), o.classList.toggle("qaid-btn-down", n === "down")) : (o.classList.toggle("qaid-type-up", n === "up"), o.classList.toggle("qaid-type-down", n === "down"));
1415
- const s = this.config.positiveIcon || I, a = this.config.negativeIcon || A;
1416
- o.innerHTML = n === "up" ? s : a;
1417
- const d = n === "up" ? "Feedback type: positive" : "Feedback type: negative";
1418
- o.setAttribute("aria-pressed", String(n === "up")), o.setAttribute("aria-label", d), this.announceMsg(d), this.feedbackId && fetch(`${this.config.endpoint}/${this.feedbackId}`, {
1419
- method: "PATCH",
1420
- headers: { "Content-Type": "application/json" },
1421
- body: JSON.stringify({ feedbackType: n })
1422
- }).catch((l) => console.error("Failed to update feedback type:", l));
1423
- });
1424
- }
1425
- async submitMessage(t) {
1426
- if (this.feedbackId) {
1427
- try {
1428
- await fetch(`${this.config.endpoint}/${this.feedbackId}`, {
1429
- method: "PATCH",
1430
- headers: { "Content-Type": "application/json" },
1431
- body: JSON.stringify({ message: t })
1432
- });
1433
- } catch (i) {
1434
- console.error("Failed to submit feedback message:", i);
1435
- }
1436
- this.feedbackId = null;
1437
- }
1438
- this.closeModal();
1439
- }
1440
- closeModal() {
1441
- this.state === "MODAL_OPEN" && this.feedbackId && fetch(`${this.config.endpoint}/${this.feedbackId}`, {
1442
- method: "PATCH",
1443
- headers: { "Content-Type": "application/json" },
1444
- body: JSON.stringify({ message: null })
1445
- }).catch((t) => console.error("Failed to finalize feedback:", t)), this.closeDialogA11y(), this.modalContainer && (this.modalContainer.remove(), this.modalContainer = null), this.backdrop && (this.backdrop.remove(), this.backdrop = null), document.removeEventListener("keydown", this.boundKeyDown), this.resetFeedbackUi();
1446
- }
1447
- // ==================== Video Recording ====================
1448
- async startRecording() {
1449
- if (!(this.isRecording || this.state !== "IDLE"))
1450
- try {
1451
- this.networkCapture = de(), this.videoRecorder = ve({
1452
- maxDuration: this.config.videoOptions.maxDuration
1453
- }), this.videoRecorder.onTick((t) => {
1454
- this.updateRecordingTimer(t);
1455
- }), this.videoRecorder.onStop((t) => {
1456
- this.isRecording && (this.recordedBlob = t, this.isRecording = !1, this.announceMsg("Recording stopped"), this.removeRecordingIndicator(), document.removeEventListener("keydown", this.boundKeyDown), this.setButtonsDisabled(!1), t && t.size > 0 ? this.showRecordingPreview() : this.cleanupRecording());
1457
- }), await this.videoRecorder.start(), this.isRecording = !0, this.announceMsg("Recording started"), this.setButtonsDisabled(!0), this.showRecordingIndicator(), document.addEventListener("keydown", this.boundKeyDown);
1458
- } catch {
1459
- this.cleanupRecording();
1460
- }
1461
- }
1462
- async stopRecording() {
1463
- try {
1464
- this.recordedBlob = await this.videoRecorder.stop();
1465
- } catch {
1466
- this.recordedBlob = null;
1467
- }
1468
- this.isRecording = !1, this.announceMsg("Recording stopped"), this.removeRecordingIndicator(), document.removeEventListener("keydown", this.boundKeyDown), this.recordedBlob && this.recordedBlob.size > 0 ? this.showRecordingPreview() : this.cleanupRecording();
1469
- }
1470
- showRecordingIndicator() {
1471
- const t = this.ensureOverlayHost();
1472
- this.recordingIndicator = document.createElement("div"), this.recordingIndicator.className = "qaid-recording-indicator", this.recordingIndicator.style.zIndex = String(this.config.zIndex + 100);
1473
- const i = document.createElement("div");
1474
- i.className = "qaid-recording-dot";
1475
- const o = document.createElement("span");
1476
- o.className = "qaid-recording-time", o.textContent = this.formatTime(this.config.videoOptions.maxDuration);
1477
- const n = document.createElement("button");
1478
- n.type = "button", n.className = "qaid-recording-stop", n.textContent = "Stop", n.addEventListener("click", () => this.stopRecording()), this.recordingIndicator.appendChild(i), this.recordingIndicator.appendChild(o), this.recordingIndicator.appendChild(n), this.applyVars(this.recordingIndicator), this.overlayShadowHost && (this.overlayShadowHost.style.pointerEvents = "auto"), t.appendChild(this.recordingIndicator);
1479
- }
1480
- updateRecordingTimer(t) {
1481
- if (!this.recordingIndicator) return;
1482
- const i = this.recordingIndicator.querySelector(".qaid-recording-time");
1483
- if (i) {
1484
- const o = Math.max(0, this.config.videoOptions.maxDuration - t);
1485
- i.textContent = this.formatTime(o), o > 0 && o <= 5 && this.announceMsg(`${o} second${o === 1 ? "" : "s"} remaining`);
1486
- }
1487
- }
1488
- formatTime(t) {
1489
- const i = Math.floor(t / 60), o = t % 60;
1490
- return `${i}:${o.toString().padStart(2, "0")}`;
1491
- }
1492
- removeRecordingIndicator() {
1493
- this.recordingIndicator && (this.recordingIndicator.remove(), this.recordingIndicator = null);
1494
- }
1495
- showRecordingPreview() {
1496
- const t = this.ensureOverlayHost(), i = URL.createObjectURL(this.recordedBlob);
1497
- this.videoPreview = document.createElement("div"), this.videoPreview.className = "qaid-video-preview", this.videoPreview.style.zIndex = String(this.config.zIndex + 100);
1498
- const o = document.createElement("div");
1499
- o.className = "qaid-video-preview-box";
1500
- const n = document.createElement("h3");
1501
- n.textContent = "Review your recording", n.id = `qaid-video-title-${this.uid}`;
1502
- const s = document.createElement("video");
1503
- s.src = i, s.controls = !0, s.autoplay = !0, s.muted = !0;
1504
- const a = document.createElement("textarea");
1505
- a.placeholder = "Optional: Describe the issue you recorded...", a.setAttribute("aria-label", "Describe the issue you recorded");
1506
- const d = document.createElement("div");
1507
- d.className = "qaid-video-preview-actions";
1508
- const l = document.createElement("button");
1509
- l.type = "button", l.className = "qaid-video-btn qaid-video-btn-cancel", l.textContent = "Cancel", l.addEventListener("click", () => this.cancelRecordingPreview());
1510
- const r = document.createElement("button");
1511
- r.type = "button", r.className = "qaid-video-btn qaid-video-btn-rerecord", r.textContent = "Re-record", r.addEventListener("click", () => {
1512
- this.cancelRecordingPreview(), this.startRecording();
1513
- });
1514
- const c = document.createElement("button");
1515
- c.type = "button", c.className = "qaid-video-btn qaid-video-btn-send", c.textContent = "Send", c.addEventListener("click", () => {
1516
- const p = a.value.trim() || null;
1517
- this.submitVideoFeedback(p, c);
1518
- }), d.appendChild(l), d.appendChild(r), d.appendChild(c), o.appendChild(n), o.appendChild(s), o.appendChild(a), o.appendChild(d), this.videoPreview.appendChild(o), this.applyVars(this.videoPreview), this.overlayShadowHost && (this.overlayShadowHost.style.pointerEvents = "auto"), t.appendChild(this.videoPreview), this.openDialogA11y(o, { labelledbyId: n.id }), document.addEventListener("keydown", this.boundKeyDown);
1519
- }
1520
- cancelRecordingPreview() {
1521
- this.removeVideoPreview(), this.cleanupRecording();
1522
- }
1523
- removeVideoPreview() {
1524
- if (this.closeDialogA11y(), this.videoPreview) {
1525
- const t = this.videoPreview.querySelector("video");
1526
- t?.src && URL.revokeObjectURL(t.src), this.videoPreview.remove(), this.videoPreview = null;
1527
- }
1528
- document.removeEventListener("keydown", this.boundKeyDown);
1529
- }
1530
- async submitVideoFeedback(t, i) {
1531
- if (!this.recordedBlob || this.isSendingVideo) return;
1532
- this.isSendingVideo = !0, i.disabled = !0, i.textContent = "Sending...";
1533
- const o = new FormData();
1534
- o.append("video", this.recordedBlob, `recording.${this.recordedBlob.type.includes("mp4") ? "mp4" : "webm"}`), o.append("pageUrl", window.location.href), o.append("visitorId", this.visitorId), this.config.apiKey && o.append("apiKey", this.config.apiKey), t && o.append("message", t), this.consoleCapture && o.append("consoleErrors", JSON.stringify(this.consoleCapture.errors)), this.networkCapture && o.append("networkErrors", JSON.stringify(this.networkCapture.errors));
1535
- let n = null;
1536
- try {
1537
- const s = await fetch(`${this.config.endpoint}/video`, {
1538
- method: "POST",
1539
- body: o
1540
- });
1541
- if (s.ok) {
1542
- this.announceMsg("Recording sent");
1543
- try {
1544
- n = (await s.json())?.id ?? null;
1545
- } catch {
1546
- }
1547
- } else
1548
- console.error("Failed to submit video feedback:", await s.text()), this.announceMsg("Failed to send recording", !0);
1549
- } catch (s) {
1550
- console.error("Failed to submit video feedback:", s), this.announceMsg("Failed to send recording", !0);
1551
- }
1552
- this.isSendingVideo = !1, this.removeVideoPreview(), this.cleanupRecording(), await this.tryLaunchQuest("video", n);
1553
- }
1554
- cleanupRecording() {
1555
- 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.setButtonsDisabled(!1), this.overlayShadowHost && this.state === "IDLE" && (this.overlayShadowHost.style.pointerEvents = "none");
1556
- }
1557
- setButtonsDisabled(t) {
1558
- if (!this.buttonsContainer) return;
1559
- this.buttonsContainer.querySelectorAll("button.qaid-btn, button.qaid-btn-structural").forEach((o) => {
1560
- t ? o.classList.contains("qaid-btn-record") || (o.disabled = !0, o.style.opacity = "0.5") : (o.disabled = !1, o.style.opacity = "");
1561
- });
1562
- }
1563
- /**
1564
- * Destroy the embed and clean up all resources
1565
- */
1566
- destroy() {
1567
- this.destroyed = !0, this.domObserver && (this.domObserver.disconnect(), this.domObserver = null), this.boundBeforeSwap && (document.removeEventListener("astro:before-swap", this.boundBeforeSwap), this.boundBeforeSwap = null), this.keyboardController?.stop(), this.keyboardController = null, this.clearActiveThumb(), this.closeDialogA11y(), this.cleanupRecording(), this.removeVideoPreview(), this.activeQuest?.destroy(), this.activeQuest = null, this.consoleCapture && (this.consoleCapture.restore(), this.consoleCapture = null), window.removeEventListener("resize", this.boundResize), document.removeEventListener("keydown", this.boundKeyDown), document.removeEventListener("mousemove", this.boundMouseMove), document.removeEventListener("click", this.boundClick, !0), document.removeEventListener("touchstart", this.boundTouchStart), document.removeEventListener("touchend", this.boundTouchEnd), document.body.classList.remove("qaid-targeting", "qaid-type-up"), document.body.style.removeProperty("--qaid-positive"), document.body.style.removeProperty("--qaid-negative"), this.shadowHost && (this.shadowHost.remove(), this.shadowHost = null, this.shadowRoot = null), this.overlayShadowHost && (this.overlayShadowHost.remove(), this.overlayShadowHost = null, this.overlayShadowRoot = null), this.buttonsContainer = null, this.overlayContainer = null, this.captureLayer = null, this.crosshairH = null, this.crosshairV = null, this.scope = null, this.marker = null, this.modalContainer = null, this.backdrop = null, this.dismissBtn = null, this.tooltipElement = null, Lt();
1568
- }
1569
- }
1570
- function et(e) {
1571
- return document.querySelector(e)?.textContent?.trim() ?? "";
1572
- }
1573
- function Be() {
1574
- const e = document.querySelector(
1575
- 'script[type="application/json"][data-feedback-config]'
1576
- );
1577
- if (!e) return null;
1578
- const t = e.textContent?.trim();
1579
- if (!t) return null;
1580
- try {
1581
- const i = JSON.parse(t);
1582
- return i.cssSelector && !i.css && (i.css = et(i.cssSelector), delete i.cssSelector), i;
1583
- } catch {
1584
- return null;
1585
- }
1586
- }
1587
- function Pe(e) {
1588
- const t = e.getAttribute("data-endpoint");
1589
- if (!t) return null;
1590
- const i = e.getAttribute("data-position"), o = e.getAttribute("data-zindex"), n = e.getAttribute("data-positive-color"), s = e.getAttribute("data-negative-color"), a = e.getAttribute("data-marker-color"), d = e.getAttribute("data-container"), l = e.getAttribute("data-button-class"), r = e.getAttribute("data-skip-targeting"), c = e.getAttribute("data-incognito"), p = e.getAttribute("data-button-size"), u = e.getAttribute("data-offset-x"), m = e.getAttribute("data-offset-y"), b = e.getAttribute("data-modal-width"), v = e.getAttribute("data-backdrop-opacity"), y = e.getAttribute("data-font-family"), f = e.getAttribute("data-font-size"), h = e.getAttribute("data-tooltip"), w = e.getAttribute("data-modal-title"), g = e.getAttribute("data-modal-subtitle"), C = e.getAttribute("data-placeholder"), B = e.getAttribute("data-submit-button"), P = e.getAttribute("data-skip-button"), it = e.getAttribute("data-positive-icon"), ot = e.getAttribute("data-negative-icon"), nt = e.getAttribute("data-api-key"), st = e.getAttribute("data-capture-screenshot"), E = e.getAttribute("data-screenshot-quality"), T = e.getAttribute("data-screenshot-max-width"), L = e.getAttribute("data-screenshot-max-height"), at = e.getAttribute("data-capture-video"), rt = e.getAttribute("data-hide-thumbs"), dt = e.getAttribute("data-hide-dismiss"), z = e.getAttribute("data-video-max-duration"), ct = e.getAttribute("data-screenshot-method"), lt = e.getAttribute("data-direction"), O = e.getAttribute("data-css-selector"), $ = e.getAttribute("data-quest-base"), ht = e.getAttribute("data-quest-up"), ut = e.getAttribute("data-quest-down"), pt = e.getAttribute("data-quest-video"), mt = e.getAttribute("data-quest-api-key"), ft = e.getAttribute("data-quest-module-url");
1591
- return {
1592
- endpoint: t,
1593
- css: O ? et(O) : void 0,
1594
- apiKey: nt ?? void 0,
1595
- captureScreenshot: st === "true" ? !0 : void 0,
1596
- screenshotOptions: E || T || L ? {
1597
- quality: E ? parseFloat(E) : void 0,
1598
- maxWidth: T ? parseInt(T, 10) : void 0,
1599
- maxHeight: L ? parseInt(L, 10) : void 0
1600
- } : void 0,
1601
- container: d ?? void 0,
1602
- buttonClass: l ?? void 0,
1603
- direction: lt ?? void 0,
1604
- position: i ?? void 0,
1605
- zIndex: o ? parseInt(o, 10) : void 0,
1606
- skipTargeting: r === "true" ? !0 : void 0,
1607
- incognito: c === "true" ? !0 : void 0,
1608
- buttonSize: p ?? void 0,
1609
- offset: u || m ? {
1610
- x: u ? parseInt(u, 10) : void 0,
1611
- y: m ? parseInt(m, 10) : void 0
1612
- } : void 0,
1613
- modalWidth: b ? parseInt(b, 10) : void 0,
1614
- backdropOpacity: v ? parseFloat(v) : void 0,
1615
- fontFamily: y ?? void 0,
1616
- fontSize: f ? parseInt(f, 10) : void 0,
1617
- colors: {
1618
- positive: n ?? void 0,
1619
- negative: s ?? void 0,
1620
- marker: a ?? void 0
1621
- },
1622
- text: h || w || g || C || B || P ? {
1623
- tooltip: h ?? void 0,
1624
- modalTitle: w ?? void 0,
1625
- modalSubtitle: g ?? void 0,
1626
- placeholder: C ?? void 0,
1627
- submitButton: B ?? void 0,
1628
- skipButton: P ?? void 0
1629
- } : void 0,
1630
- positiveIcon: it ?? void 0,
1631
- negativeIcon: ot ?? void 0,
1632
- screenshotMethod: ct ?? void 0,
1633
- captureVideo: at === "true" ? !0 : void 0,
1634
- hideThumbs: rt === "true" ? !0 : void 0,
1635
- hideDismiss: dt === "true" ? !0 : void 0,
1636
- videoOptions: z ? {
1637
- maxDuration: parseInt(z, 10)
1638
- } : void 0,
1639
- quests: $ ? {
1640
- base: $,
1641
- up: ht ?? void 0,
1642
- down: ut ?? void 0,
1643
- video: pt ?? void 0,
1644
- apiKey: mt ?? void 0,
1645
- moduleUrl: ft ?? void 0
1646
- } : void 0
1647
- };
1648
- }
1649
- if (typeof document < "u") {
1650
- const e = () => {
1651
- const t = document.currentScript, i = Be(), o = t ? Pe(t) : null, n = i ?? o;
1652
- n?.endpoint && new He(n);
1653
- };
1654
- document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", e) : e();
1655
- }
1656
- export {
1657
- He as QaidFeedback,
1658
- Ce as captureDomScreenshot,
1659
- de as captureNetworkErrors,
1660
- ve as createVideoRecorder,
1661
- ge as getSupportedMimeType,
1662
- ze as isDomScreenshotSupported,
1663
- be as isVideoRecordingSupported
1664
- };
1665
- //# sourceMappingURL=qaid.js.map