@guidelytech/guide-runtime 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/bug-report.cjs +340 -0
  2. package/dist/bug-report.cjs.map +1 -0
  3. package/dist/bug-report.d.cts +38 -0
  4. package/dist/bug-report.d.ts +38 -0
  5. package/dist/bug-report.js +213 -0
  6. package/dist/bug-report.js.map +1 -0
  7. package/dist/capture.cjs +5 -0
  8. package/dist/capture.cjs.map +1 -1
  9. package/dist/capture.js +32 -420
  10. package/dist/capture.js.map +1 -1
  11. package/dist/{chunk-VJSQM23R.js → chunk-63M4VHQG.js} +6 -6
  12. package/dist/chunk-63M4VHQG.js.map +1 -0
  13. package/dist/{chunk-VQJ7MZ2K.js → chunk-EPR4LWA2.js} +2 -2
  14. package/dist/{chunk-H3CROHPR.js → chunk-QFKMXMM4.js} +2 -2
  15. package/dist/chunk-THGKUOHM.js +445 -0
  16. package/dist/chunk-THGKUOHM.js.map +1 -0
  17. package/dist/chunk-V6TY7KAL.js +8 -0
  18. package/dist/chunk-V6TY7KAL.js.map +1 -0
  19. package/dist/index.cjs +100 -24
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +11 -4
  22. package/dist/index.d.ts +11 -4
  23. package/dist/index.js +87 -15
  24. package/dist/index.js.map +1 -1
  25. package/dist/messenger.js +3 -2
  26. package/dist/messenger.js.map +1 -1
  27. package/package.json +9 -4
  28. package/dist/chunk-4FKYF6OB.js +0 -466
  29. package/dist/chunk-4FKYF6OB.js.map +0 -1
  30. package/dist/chunk-4UW6YF44.js +0 -464
  31. package/dist/chunk-4UW6YF44.js.map +0 -1
  32. package/dist/chunk-7DOGZKWD.js +0 -322
  33. package/dist/chunk-7DOGZKWD.js.map +0 -1
  34. package/dist/chunk-GUH2PQJ7.js +0 -462
  35. package/dist/chunk-GUH2PQJ7.js.map +0 -1
  36. package/dist/chunk-GUSKGNNR.js +0 -301
  37. package/dist/chunk-GUSKGNNR.js.map +0 -1
  38. package/dist/chunk-KJRXCITD.js +0 -515
  39. package/dist/chunk-KJRXCITD.js.map +0 -1
  40. package/dist/chunk-PG7CMBHD.js +0 -330
  41. package/dist/chunk-PG7CMBHD.js.map +0 -1
  42. package/dist/chunk-VJSQM23R.js.map +0 -1
  43. package/dist/chunk-VNJPNGZ4.js +0 -301
  44. package/dist/chunk-VNJPNGZ4.js.map +0 -1
  45. /package/dist/{chunk-VQJ7MZ2K.js.map → chunk-EPR4LWA2.js.map} +0 -0
  46. /package/dist/{chunk-H3CROHPR.js.map → chunk-QFKMXMM4.js.map} +0 -0
@@ -0,0 +1,213 @@
1
+ import {
2
+ canCaptureScreen,
3
+ requestTabCapture
4
+ } from "./chunk-THGKUOHM.js";
5
+ import "./chunk-EPR4LWA2.js";
6
+ import "./chunk-V6TY7KAL.js";
7
+
8
+ // src/bug-report.ts
9
+ import { MAX_BUG_REPORT_LENGTH, MAX_BUG_REPORT_SCREENSHOT_BYTES } from "@guidelytech/shared/runtime";
10
+ var CSS = `
11
+ .g-br-bg{position:fixed;inset:0;z-index:var(--g-z,2147483000);background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;padding:16px;font-family:var(--g-font,system-ui,sans-serif)}
12
+ .g-br{background:var(--g-bg,#fff);color:var(--g-text,#111827);border-radius:calc(var(--g-radius,8px) + 2px);box-shadow:0 20px 50px rgba(0,0,0,.25);width:min(440px,100%);max-height:calc(100vh - 32px);overflow:auto;padding:20px;box-sizing:border-box;font-size:14px;line-height:1.45}
13
+ .g-br h2{margin:0 0 4px;font-size:17px;font-weight:600}
14
+ .g-br p{margin:0;opacity:.75;font-size:13px}
15
+ .g-br label{display:block;margin:12px 0 4px;font-size:12px;font-weight:600}
16
+ .g-br input,.g-br textarea{width:100%;box-sizing:border-box;border:1px solid rgba(127,127,127,.4);border-radius:var(--g-radius,8px);padding:8px 10px;font:inherit;color:inherit;background:transparent}
17
+ .g-br textarea{min-height:110px;resize:vertical}
18
+ .g-br input:focus,.g-br textarea:focus,.g-br button:focus-visible{outline:2px solid var(--g-primary,#4f46e5);outline-offset:1px}
19
+ .g-br-row{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:10px}
20
+ .g-br-end{justify-content:flex-end;margin-top:16px}
21
+ .g-br button{font:inherit;font-size:13px;border-radius:var(--g-radius,8px);padding:7px 12px;cursor:pointer;border:1px solid rgba(127,127,127,.4);background:transparent;color:inherit}
22
+ .g-br button.g-br-primary{background:var(--g-primary,#4f46e5);border-color:var(--g-primary,#4f46e5);color:var(--g-primary-text,#fff)}
23
+ .g-br button:disabled{opacity:.6;cursor:default}
24
+ .g-br-shot{display:flex;gap:8px;align-items:center;margin-top:10px;font-size:12px}
25
+ .g-br-shot img{max-width:120px;max-height:72px;border-radius:4px;border:1px solid rgba(127,127,127,.3)}
26
+ .g-br-err{color:#dc2626;font-size:12px;margin-top:8px}
27
+ .g-br[data-theme=dark] .g-br-err,[data-theme=dark] .g-br-err{color:#f87171}
28
+ .g-br-ok{font-weight:600;margin:8px 0}
29
+ `;
30
+ var MAX_SIDE = 1920;
31
+ function el(doc, tag, attrs = {}, text) {
32
+ const e = doc.createElement(tag);
33
+ for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, v);
34
+ if (text !== void 0) e.textContent = text;
35
+ return e;
36
+ }
37
+ function canvasToScreenshot(canvas, doc) {
38
+ const scale = Math.min(1, MAX_SIDE / Math.max(canvas.width || 1, canvas.height || 1));
39
+ let src = canvas;
40
+ if (scale < 1) {
41
+ src = doc.createElement("canvas");
42
+ src.width = Math.max(1, Math.round(canvas.width * scale));
43
+ src.height = Math.max(1, Math.round(canvas.height * scale));
44
+ src.getContext("2d")?.drawImage(canvas, 0, 0, src.width, src.height);
45
+ }
46
+ for (const q of [0.85, 0.7, 0.5]) {
47
+ const url = src.toDataURL("image/jpeg", q);
48
+ const data = url.slice(url.indexOf(",") + 1);
49
+ if (data.length * 0.75 <= MAX_BUG_REPORT_SCREENSHOT_BYTES) return data;
50
+ }
51
+ return null;
52
+ }
53
+ async function readImageFile(doc, file) {
54
+ if (!/^image\/(png|jpeg|webp|gif)$/.test(file.type)) throw new Error("Attach a PNG, JPEG, WebP or GIF image.");
55
+ const asIs = await new Promise((resolve, reject) => {
56
+ const r = new FileReader();
57
+ r.onload = () => resolve(String(r.result));
58
+ r.onerror = () => reject(new Error("The image couldn't be read."));
59
+ r.readAsDataURL(file);
60
+ });
61
+ const data = asIs.slice(asIs.indexOf(",") + 1);
62
+ if (data.length * 0.75 <= MAX_BUG_REPORT_SCREENSHOT_BYTES) return { fileName: file.name || "screenshot", data };
63
+ const img = await new Promise((resolve, reject) => {
64
+ const i = new Image();
65
+ i.onload = () => resolve(i);
66
+ i.onerror = () => reject(new Error("The image couldn't be read."));
67
+ i.src = asIs;
68
+ });
69
+ const canvas = doc.createElement("canvas");
70
+ canvas.width = img.naturalWidth;
71
+ canvas.height = img.naturalHeight;
72
+ canvas.getContext("2d")?.drawImage(img, 0, 0);
73
+ const scaled = canvasToScreenshot(canvas, doc);
74
+ if (!scaled) throw new Error("That image is too large. Screenshots can be up to 4 MB.");
75
+ return { fileName: `${(file.name || "screenshot").replace(/\.[a-z0-9]+$/i, "")}.jpg`, data: scaled };
76
+ }
77
+ async function captureTab(doc, hide) {
78
+ hide(true);
79
+ try {
80
+ const res = await requestTabCapture(doc.body, doc);
81
+ if (!res.ok) throw new Error(res.reason === "denied" ? "Screen capture was cancelled." : res.reason === "wrong-surface" ? "Share this tab to capture it." : "This browser can't capture the page.");
82
+ try {
83
+ await res.screen.nextFrame(800);
84
+ await res.screen.nextFrame(800);
85
+ const canvas = res.screen.grab();
86
+ const data = canvas ? canvasToScreenshot(canvas, doc) : null;
87
+ if (!data) throw new Error("The page couldn't be captured.");
88
+ return { fileName: "screenshot.jpg", data };
89
+ } finally {
90
+ res.screen.stop();
91
+ }
92
+ } finally {
93
+ hide(false);
94
+ }
95
+ }
96
+ function renderBugReport(o) {
97
+ const doc = o.scope.ownerDocument;
98
+ const root = el(doc, "div");
99
+ root.appendChild(el(doc, "style", {}, CSS));
100
+ const bg = el(doc, "div", { class: "g-br-bg" });
101
+ const box = el(doc, "div", { class: "g-br", role: "dialog", "aria-modal": "true", "aria-labelledby": "g-br-title", "data-theme": o.scope.getAttribute("data-theme") ?? "light" });
102
+ box.appendChild(el(doc, "h2", { id: "g-br-title" }, o.title ?? "Report a bug"));
103
+ box.appendChild(el(doc, "p", {}, "Tell us what went wrong. The team sees the page you're on and what happened just before."));
104
+ const form = el(doc, "form", { novalidate: "" });
105
+ const field = (id, label, input) => {
106
+ form.appendChild(el(doc, "label", { for: id }, label));
107
+ input.id = id;
108
+ form.appendChild(input);
109
+ return input;
110
+ };
111
+ const message = field("g-br-message", "What happened?", el(doc, "textarea", { maxlength: String(MAX_BUG_REPORT_LENGTH), required: "" }));
112
+ message.value = o.message ?? "";
113
+ const name = field("g-br-name", "Name", el(doc, "input", { type: "text", maxlength: "200", autocomplete: "name" }));
114
+ name.value = o.name ?? "";
115
+ const email = field("g-br-email", "Email", el(doc, "input", { type: "email", maxlength: "254", autocomplete: "email" }));
116
+ email.value = o.email ?? "";
117
+ let screenshot = null;
118
+ const shotRow = el(doc, "div", { class: "g-br-row" });
119
+ const file = el(doc, "input", { type: "file", accept: "image/png,image/jpeg,image/webp,image/gif", hidden: "" });
120
+ const attach = el(doc, "button", { type: "button" }, "Attach an image");
121
+ shotRow.appendChild(attach);
122
+ const capture = canCaptureScreen() ? el(doc, "button", { type: "button" }, "Capture this page") : null;
123
+ if (capture) shotRow.appendChild(capture);
124
+ shotRow.appendChild(file);
125
+ form.appendChild(shotRow);
126
+ const preview = el(doc, "div", { class: "g-br-shot", hidden: "" });
127
+ form.appendChild(preview);
128
+ const error = el(doc, "div", { class: "g-br-err", role: "alert" });
129
+ form.appendChild(error);
130
+ const actions = el(doc, "div", { class: "g-br-row g-br-end" });
131
+ const cancel = el(doc, "button", { type: "button" }, "Cancel");
132
+ const send = el(doc, "button", { type: "submit", class: "g-br-primary" }, "Send report");
133
+ actions.append(cancel, send);
134
+ form.appendChild(actions);
135
+ box.appendChild(form);
136
+ bg.appendChild(box);
137
+ root.appendChild(bg);
138
+ o.scope.appendChild(root);
139
+ const previous = doc.activeElement;
140
+ setTimeout(() => message.focus(), 0);
141
+ const showShot = (s) => {
142
+ screenshot = s;
143
+ preview.replaceChildren();
144
+ preview.hidden = !s;
145
+ if (!s) return;
146
+ const img = el(doc, "img", { alt: "Screenshot preview" });
147
+ img.src = `data:image/${/\.png$/i.test(s.fileName) ? "png" : "jpeg"};base64,${s.data}`;
148
+ const remove = el(doc, "button", { type: "button" }, "Remove");
149
+ remove.addEventListener("click", () => showShot(null));
150
+ preview.append(img, el(doc, "span", {}, s.fileName), remove);
151
+ };
152
+ attach.addEventListener("click", () => file.click());
153
+ file.addEventListener("change", () => {
154
+ const f = file.files?.[0];
155
+ file.value = "";
156
+ if (!f) return;
157
+ error.textContent = "";
158
+ readImageFile(doc, f).then(showShot, (e) => error.textContent = e.message);
159
+ });
160
+ capture?.addEventListener("click", () => {
161
+ error.textContent = "";
162
+ captureTab(doc, (hidden) => bg.style.visibility = hidden ? "hidden" : "").then(showShot, (e) => error.textContent = e.message);
163
+ });
164
+ let closed = false;
165
+ const close = () => {
166
+ if (closed) return;
167
+ closed = true;
168
+ doc.removeEventListener("keydown", onKey, true);
169
+ root.remove();
170
+ previous?.focus?.();
171
+ o.onClose?.();
172
+ };
173
+ const onKey = (e) => {
174
+ if (e.key === "Escape") {
175
+ e.stopPropagation();
176
+ close();
177
+ }
178
+ };
179
+ doc.addEventListener("keydown", onKey, true);
180
+ cancel.addEventListener("click", close);
181
+ bg.addEventListener("click", (e) => e.target === bg && close());
182
+ form.addEventListener("submit", (e) => {
183
+ e.preventDefault();
184
+ error.textContent = "";
185
+ if (!message.value.trim()) {
186
+ error.textContent = "Describe what happened.";
187
+ message.focus();
188
+ return;
189
+ }
190
+ if (email.value.trim() && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email.value.trim())) {
191
+ error.textContent = "Enter a valid email address, or leave it empty.";
192
+ email.focus();
193
+ return;
194
+ }
195
+ send.disabled = true;
196
+ o.onSubmit({ message: message.value.trim(), name: name.value.trim(), email: email.value.trim(), screenshot }).then(
197
+ () => {
198
+ form.replaceWith(el(doc, "p", { class: "g-br-ok", role: "status" }, "Thanks! Your report was sent to the team."));
199
+ setTimeout(close, 2500);
200
+ },
201
+ (err) => {
202
+ send.disabled = false;
203
+ error.textContent = err?.message || "Your report couldn't be sent. Please try again.";
204
+ }
205
+ );
206
+ });
207
+ return { close };
208
+ }
209
+ export {
210
+ readImageFile,
211
+ renderBugReport
212
+ };
213
+ //# sourceMappingURL=bug-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bug-report.ts"],"sourcesContent":["import { MAX_BUG_REPORT_LENGTH, MAX_BUG_REPORT_SCREENSHOT_BYTES } from \"@guidelytech/shared/runtime\";\nimport { canCaptureScreen, requestTabCapture } from \"./capture-screen\";\n\n/**\n * \"Report a bug\": the dialog behind `guidely.showBugReport()` (its own on-demand chunk). The person\n * describes the problem and can add a screenshot, either an image file or a capture of this tab\n * (the browser's own share prompt; nothing is captured without it). Plain DOM in the SDK's shadow\n * scope; text is only ever set with textContent.\n */\n\nexport interface BugReportDialogInput {\n message: string;\n name: string;\n email: string;\n screenshot: { fileName: string; data: string } | null;\n}\n\nexport interface BugReportDialogOptions {\n /** The SDK's themed scope element (inside its shadow root). */\n scope: HTMLElement;\n /** Prefilled from identify() traits. */\n name?: string;\n email?: string;\n message?: string;\n title?: string;\n /** Sends the report; rejects with a message to show. */\n onSubmit: (input: BugReportDialogInput) => Promise<void>;\n onClose?: () => void;\n}\n\nexport interface BugReportDialog {\n close(): void;\n}\n\nconst CSS = `\n.g-br-bg{position:fixed;inset:0;z-index:var(--g-z,2147483000);background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;padding:16px;font-family:var(--g-font,system-ui,sans-serif)}\n.g-br{background:var(--g-bg,#fff);color:var(--g-text,#111827);border-radius:calc(var(--g-radius,8px) + 2px);box-shadow:0 20px 50px rgba(0,0,0,.25);width:min(440px,100%);max-height:calc(100vh - 32px);overflow:auto;padding:20px;box-sizing:border-box;font-size:14px;line-height:1.45}\n.g-br h2{margin:0 0 4px;font-size:17px;font-weight:600}\n.g-br p{margin:0;opacity:.75;font-size:13px}\n.g-br label{display:block;margin:12px 0 4px;font-size:12px;font-weight:600}\n.g-br input,.g-br textarea{width:100%;box-sizing:border-box;border:1px solid rgba(127,127,127,.4);border-radius:var(--g-radius,8px);padding:8px 10px;font:inherit;color:inherit;background:transparent}\n.g-br textarea{min-height:110px;resize:vertical}\n.g-br input:focus,.g-br textarea:focus,.g-br button:focus-visible{outline:2px solid var(--g-primary,#4f46e5);outline-offset:1px}\n.g-br-row{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:10px}\n.g-br-end{justify-content:flex-end;margin-top:16px}\n.g-br button{font:inherit;font-size:13px;border-radius:var(--g-radius,8px);padding:7px 12px;cursor:pointer;border:1px solid rgba(127,127,127,.4);background:transparent;color:inherit}\n.g-br button.g-br-primary{background:var(--g-primary,#4f46e5);border-color:var(--g-primary,#4f46e5);color:var(--g-primary-text,#fff)}\n.g-br button:disabled{opacity:.6;cursor:default}\n.g-br-shot{display:flex;gap:8px;align-items:center;margin-top:10px;font-size:12px}\n.g-br-shot img{max-width:120px;max-height:72px;border-radius:4px;border:1px solid rgba(127,127,127,.3)}\n.g-br-err{color:#dc2626;font-size:12px;margin-top:8px}\n.g-br[data-theme=dark] .g-br-err,[data-theme=dark] .g-br-err{color:#f87171}\n.g-br-ok{font-weight:600;margin:8px 0}\n`;\n\nconst MAX_SIDE = 1920;\n\nfunction el<K extends keyof HTMLElementTagNameMap>(doc: Document, tag: K, attrs: Record<string, string> = {}, text?: string): HTMLElementTagNameMap[K] {\n const e = doc.createElement(tag);\n for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, v);\n if (text !== undefined) e.textContent = text;\n return e;\n}\n\n/** Base64 (no prefix) of a canvas as JPEG small enough to send; null when it can't fit. */\nfunction canvasToScreenshot(canvas: HTMLCanvasElement, doc: Document): string | null {\n const scale = Math.min(1, MAX_SIDE / Math.max(canvas.width || 1, canvas.height || 1));\n let src = canvas;\n if (scale < 1) {\n src = doc.createElement(\"canvas\");\n src.width = Math.max(1, Math.round(canvas.width * scale));\n src.height = Math.max(1, Math.round(canvas.height * scale));\n src.getContext(\"2d\")?.drawImage(canvas, 0, 0, src.width, src.height);\n }\n for (const q of [0.85, 0.7, 0.5]) {\n const url = src.toDataURL(\"image/jpeg\", q);\n const data = url.slice(url.indexOf(\",\") + 1);\n if (data.length * 0.75 <= MAX_BUG_REPORT_SCREENSHOT_BYTES) return data;\n }\n return null;\n}\n\n/** Reads a picked image, scaled down and re-encoded when it's too large. */\nexport async function readImageFile(doc: Document, file: File): Promise<{ fileName: string; data: string }> {\n if (!/^image\\/(png|jpeg|webp|gif)$/.test(file.type)) throw new Error(\"Attach a PNG, JPEG, WebP or GIF image.\");\n const asIs = await new Promise<string>((resolve, reject) => {\n const r = new FileReader();\n r.onload = () => resolve(String(r.result));\n r.onerror = () => reject(new Error(\"The image couldn't be read.\"));\n r.readAsDataURL(file);\n });\n const data = asIs.slice(asIs.indexOf(\",\") + 1);\n if (data.length * 0.75 <= MAX_BUG_REPORT_SCREENSHOT_BYTES) return { fileName: file.name || \"screenshot\", data };\n const img = await new Promise<HTMLImageElement>((resolve, reject) => {\n const i = new Image();\n i.onload = () => resolve(i);\n i.onerror = () => reject(new Error(\"The image couldn't be read.\"));\n i.src = asIs;\n });\n const canvas = doc.createElement(\"canvas\");\n canvas.width = img.naturalWidth;\n canvas.height = img.naturalHeight;\n canvas.getContext(\"2d\")?.drawImage(img, 0, 0);\n const scaled = canvasToScreenshot(canvas, doc);\n if (!scaled) throw new Error(\"That image is too large. Screenshots can be up to 4 MB.\");\n return { fileName: `${(file.name || \"screenshot\").replace(/\\.[a-z0-9]+$/i, \"\")}.jpg`, data: scaled };\n}\n\n/** One frame of this tab, after the browser's share prompt (the dialog hides while it's taken). */\nasync function captureTab(doc: Document, hide: (hidden: boolean) => void): Promise<{ fileName: string; data: string }> {\n hide(true);\n try {\n const res = await requestTabCapture(doc.body, doc);\n if (!res.ok) throw new Error(res.reason === \"denied\" ? \"Screen capture was cancelled.\" : res.reason === \"wrong-surface\" ? \"Share this tab to capture it.\" : \"This browser can't capture the page.\");\n try {\n await res.screen.nextFrame(800);\n await res.screen.nextFrame(800);\n const canvas = res.screen.grab();\n const data = canvas ? canvasToScreenshot(canvas, doc) : null;\n if (!data) throw new Error(\"The page couldn't be captured.\");\n return { fileName: \"screenshot.jpg\", data };\n } finally {\n res.screen.stop();\n }\n } finally {\n hide(false);\n }\n}\n\nexport function renderBugReport(o: BugReportDialogOptions): BugReportDialog {\n const doc = o.scope.ownerDocument;\n const root = el(doc, \"div\");\n root.appendChild(el(doc, \"style\", {}, CSS));\n const bg = el(doc, \"div\", { class: \"g-br-bg\" });\n const box = el(doc, \"div\", { class: \"g-br\", role: \"dialog\", \"aria-modal\": \"true\", \"aria-labelledby\": \"g-br-title\", \"data-theme\": o.scope.getAttribute(\"data-theme\") ?? \"light\" });\n box.appendChild(el(doc, \"h2\", { id: \"g-br-title\" }, o.title ?? \"Report a bug\"));\n box.appendChild(el(doc, \"p\", {}, \"Tell us what went wrong. The team sees the page you're on and what happened just before.\"));\n const form = el(doc, \"form\", { novalidate: \"\" });\n const field = <T extends HTMLInputElement | HTMLTextAreaElement>(id: string, label: string, input: T) => {\n form.appendChild(el(doc, \"label\", { for: id }, label));\n input.id = id;\n form.appendChild(input);\n return input;\n };\n const message = field(\"g-br-message\", \"What happened?\", el(doc, \"textarea\", { maxlength: String(MAX_BUG_REPORT_LENGTH), required: \"\" }));\n message.value = o.message ?? \"\";\n const name = field(\"g-br-name\", \"Name\", el(doc, \"input\", { type: \"text\", maxlength: \"200\", autocomplete: \"name\" }));\n name.value = o.name ?? \"\";\n const email = field(\"g-br-email\", \"Email\", el(doc, \"input\", { type: \"email\", maxlength: \"254\", autocomplete: \"email\" }));\n email.value = o.email ?? \"\";\n\n let screenshot: { fileName: string; data: string } | null = null;\n const shotRow = el(doc, \"div\", { class: \"g-br-row\" });\n const file = el(doc, \"input\", { type: \"file\", accept: \"image/png,image/jpeg,image/webp,image/gif\", hidden: \"\" });\n const attach = el(doc, \"button\", { type: \"button\" }, \"Attach an image\");\n shotRow.appendChild(attach);\n const capture = canCaptureScreen() ? el(doc, \"button\", { type: \"button\" }, \"Capture this page\") : null;\n if (capture) shotRow.appendChild(capture);\n shotRow.appendChild(file);\n form.appendChild(shotRow);\n const preview = el(doc, \"div\", { class: \"g-br-shot\", hidden: \"\" });\n form.appendChild(preview);\n const error = el(doc, \"div\", { class: \"g-br-err\", role: \"alert\" });\n form.appendChild(error);\n const actions = el(doc, \"div\", { class: \"g-br-row g-br-end\" });\n const cancel = el(doc, \"button\", { type: \"button\" }, \"Cancel\");\n const send = el(doc, \"button\", { type: \"submit\", class: \"g-br-primary\" }, \"Send report\");\n actions.append(cancel, send);\n form.appendChild(actions);\n box.appendChild(form);\n bg.appendChild(box);\n root.appendChild(bg);\n o.scope.appendChild(root);\n const previous = doc.activeElement as HTMLElement | null;\n setTimeout(() => message.focus(), 0);\n\n const showShot = (s: { fileName: string; data: string } | null) => {\n screenshot = s;\n preview.replaceChildren();\n preview.hidden = !s;\n if (!s) return;\n const img = el(doc, \"img\", { alt: \"Screenshot preview\" });\n img.src = `data:image/${/\\.png$/i.test(s.fileName) ? \"png\" : \"jpeg\"};base64,${s.data}`;\n const remove = el(doc, \"button\", { type: \"button\" }, \"Remove\");\n remove.addEventListener(\"click\", () => showShot(null));\n preview.append(img, el(doc, \"span\", {}, s.fileName), remove);\n };\n attach.addEventListener(\"click\", () => file.click());\n file.addEventListener(\"change\", () => {\n const f = file.files?.[0];\n file.value = \"\";\n if (!f) return;\n error.textContent = \"\";\n readImageFile(doc, f).then(showShot, (e: Error) => (error.textContent = e.message));\n });\n capture?.addEventListener(\"click\", () => {\n error.textContent = \"\";\n captureTab(doc, (hidden) => (bg.style.visibility = hidden ? \"hidden\" : \"\")).then(showShot, (e: Error) => (error.textContent = e.message));\n });\n\n let closed = false;\n const close = () => {\n if (closed) return;\n closed = true;\n doc.removeEventListener(\"keydown\", onKey, true);\n root.remove();\n previous?.focus?.();\n o.onClose?.();\n };\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n close();\n }\n };\n doc.addEventListener(\"keydown\", onKey, true);\n cancel.addEventListener(\"click\", close);\n bg.addEventListener(\"click\", (e) => e.target === bg && close());\n form.addEventListener(\"submit\", (e) => {\n e.preventDefault();\n error.textContent = \"\";\n if (!message.value.trim()) {\n error.textContent = \"Describe what happened.\";\n message.focus();\n return;\n }\n if (email.value.trim() && !/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(email.value.trim())) {\n error.textContent = \"Enter a valid email address, or leave it empty.\";\n email.focus();\n return;\n }\n send.disabled = true;\n o.onSubmit({ message: message.value.trim(), name: name.value.trim(), email: email.value.trim(), screenshot }).then(\n () => {\n form.replaceWith(el(doc, \"p\", { class: \"g-br-ok\", role: \"status\" }, \"Thanks! Your report was sent to the team.\"));\n setTimeout(close, 2500);\n },\n (err: Error) => {\n send.disabled = false;\n error.textContent = err?.message || \"Your report couldn't be sent. Please try again.\";\n },\n );\n });\n return { close };\n}\n"],"mappings":"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","names":[]}
package/dist/capture.cjs CHANGED
@@ -789,6 +789,11 @@ var STYLES = `
789
789
  .g-close:hover,.g-close:focus-visible{background:var(--g-subtle);color:var(--g-text);outline:none}
790
790
  .g-close:focus-visible,.g-btn:focus-visible,.g-task:focus-visible,.g-choice:focus-visible,.g-scale button:focus-visible,.g-launcher:focus-visible,.g-input:focus-visible,.g-rc-item:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px}
791
791
  .g-image{display:block;width:100%;max-height:220px;object-fit:cover;border-radius:var(--g-radius) var(--g-radius) 0 0}
792
+ .g-video{position:relative;display:block;width:100%;aspect-ratio:16/9;max-height:240px;overflow:hidden;background:#0f172a;border-radius:var(--g-radius) var(--g-radius) 0 0;cursor:pointer}
793
+ .g-video-poster{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;pointer-events:none}
794
+ .g-video-play{position:absolute;left:50%;top:50%;width:56px;height:56px;margin:-28px 0 0 -28px;display:flex;align-items:center;justify-content:center;padding-left:3px;box-sizing:border-box;border-radius:999px;background:rgba(15,23,42,.72);color:#fff;box-shadow:0 4px 14px rgba(0,0,0,.3);transition:transform .15s ease,background .15s ease}
795
+ .g-video:hover .g-video-play,.g-video:focus-visible .g-video-play{transform:scale(1.08);background:var(--g-primary);color:var(--g-primary-text)}
796
+ .g-video:focus-visible{outline:2px solid var(--g-primary);outline-offset:-2px}
792
797
  .g-title{margin:0 28px 6px 0;font-size:16px;font-weight:600;line-height:1.3}
793
798
  .g-body{font-size:14px;color:var(--g-text)}
794
799
  .g-body p{margin:0 0 8px}.g-body p:last-child{margin-bottom:0}