@diegotsi/flint-react 0.5.1 → 1.0.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.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -22,21 +32,20 @@ var index_exports = {};
22
32
  __export(index_exports, {
23
33
  FlintModal: () => FlintModal,
24
34
  FlintWidget: () => FlintWidget,
25
- flint: () => flint
35
+ flint: () => import_core7.flint
26
36
  });
27
37
  module.exports = __toCommonJS(index_exports);
28
38
 
29
- // src/FlintWidget.tsx
30
- var import_react4 = require("react");
31
- var import_react_i18next3 = require("react-i18next");
32
-
33
39
  // src/FlintModal.tsx
34
40
  var import_react2 = require("react");
35
41
  var import_react_i18next = require("react-i18next");
36
42
 
43
+ // src/api.ts
44
+ var import_core = require("@flint/core");
45
+
37
46
  // src/ScreenAnnotator.tsx
38
- var import_react = require("react");
39
47
  var import_modern_screenshot = require("modern-screenshot");
48
+ var import_react = require("react");
40
49
  var import_jsx_runtime = require("react/jsx-runtime");
41
50
  function normalizeRect(startX, startY, endX, endY) {
42
51
  return {
@@ -81,9 +90,7 @@ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
81
90
  const dpr = window.devicePixelRatio ?? 1;
82
91
  const vw = window.innerWidth;
83
92
  const vh = window.innerHeight;
84
- await new Promise(
85
- (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
86
- );
93
+ await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
87
94
  try {
88
95
  const fullCanvas = await (0, import_modern_screenshot.domToCanvas)(document.documentElement, {
89
96
  scale: dpr,
@@ -98,17 +105,7 @@ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
98
105
  canvas.width = vw * dpr;
99
106
  canvas.height = vh * dpr;
100
107
  const ctx = canvas.getContext("2d");
101
- ctx.drawImage(
102
- fullCanvas,
103
- 0,
104
- 0,
105
- vw * dpr,
106
- vh * dpr,
107
- 0,
108
- 0,
109
- vw * dpr,
110
- vh * dpr
111
- );
108
+ ctx.drawImage(fullCanvas, 0, 0, vw * dpr, vh * dpr, 0, 0, vw * dpr, vh * dpr);
112
109
  ctx.fillStyle = "rgba(255,200,0,0.25)";
113
110
  ctx.fillRect(finalRect.x * dpr, finalRect.y * dpr, finalRect.w * dpr, finalRect.h * dpr);
114
111
  ctx.strokeStyle = "#f97316";
@@ -146,123 +143,50 @@ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
146
143
  pointerEvents: isCapturing ? "none" : "auto"
147
144
  },
148
145
  children: [
149
- !isCapturing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
150
- position: "absolute",
151
- top: 16,
152
- left: "50%",
153
- transform: "translateX(-50%)",
154
- background: "rgba(0,0,0,0.75)",
155
- color: "#fff",
156
- padding: "8px 18px",
157
- borderRadius: 8,
158
- fontSize: 14,
159
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
160
- pointerEvents: "none",
161
- whiteSpace: "nowrap",
162
- backdropFilter: "blur(4px)"
163
- }, children: "Drag to highlight the problem area \xA0\xB7\xA0 Esc to cancel" }),
164
- rect && phase === "selecting" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
165
- position: "absolute",
166
- left: rect.x,
167
- top: rect.y,
168
- width: rect.w,
169
- height: rect.h,
170
- background: "rgba(255,200,0,0.2)",
171
- border: "2px dashed #f97316",
172
- boxSizing: "border-box",
173
- pointerEvents: "none"
174
- } })
146
+ !isCapturing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
147
+ "div",
148
+ {
149
+ style: {
150
+ position: "absolute",
151
+ top: 16,
152
+ left: "50%",
153
+ transform: "translateX(-50%)",
154
+ background: "rgba(0,0,0,0.75)",
155
+ color: "#fff",
156
+ padding: "8px 18px",
157
+ borderRadius: 8,
158
+ fontSize: 14,
159
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
160
+ pointerEvents: "none",
161
+ whiteSpace: "nowrap",
162
+ backdropFilter: "blur(4px)"
163
+ },
164
+ children: "Drag to highlight the problem area \xA0\xB7\xA0 Esc to cancel"
165
+ }
166
+ ),
167
+ rect && phase === "selecting" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
168
+ "div",
169
+ {
170
+ style: {
171
+ position: "absolute",
172
+ left: rect.x,
173
+ top: rect.y,
174
+ width: rect.w,
175
+ height: rect.h,
176
+ background: "rgba(255,200,0,0.2)",
177
+ border: "2px dashed #f97316",
178
+ boxSizing: "border-box",
179
+ pointerEvents: "none"
180
+ }
181
+ }
182
+ )
175
183
  ]
176
184
  }
177
185
  );
178
186
  }
179
187
 
180
- // src/api.ts
181
- var import_fflate = require("fflate");
182
- async function submitReport(serverUrl, projectKey, payload, screenshot) {
183
- const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports`;
184
- let body;
185
- let headers = {
186
- "x-project-key": projectKey
187
- };
188
- if (screenshot) {
189
- const form = new FormData();
190
- form.append("reporterId", payload.reporterId);
191
- form.append("reporterName", payload.reporterName);
192
- form.append("description", payload.description);
193
- if (payload.expectedBehavior) form.append("expectedBehavior", payload.expectedBehavior);
194
- if (payload.stepsToReproduce) form.append("stepsToReproduce", JSON.stringify(payload.stepsToReproduce));
195
- if (payload.externalReplayUrl) form.append("externalReplayUrl", payload.externalReplayUrl);
196
- if (payload.additionalContext) form.append("additionalContext", payload.additionalContext);
197
- form.append("severity", payload.severity);
198
- if (payload.url) form.append("url", payload.url);
199
- if (payload.meta) form.append("meta", JSON.stringify(payload.meta));
200
- form.append("screenshot", screenshot);
201
- body = form;
202
- } else {
203
- body = JSON.stringify(payload);
204
- headers["Content-Type"] = "application/json";
205
- }
206
- const res = await fetch(url, { method: "POST", headers, body });
207
- if (!res.ok) {
208
- const err = await res.json().catch(() => ({ error: "Unknown error" }));
209
- throw new Error(err.error ?? `HTTP ${res.status}`);
210
- }
211
- return res.json();
212
- }
213
- async function submitReplay(serverUrl, projectKey, reportId, events) {
214
- const json = JSON.stringify(events);
215
- const encoded = new TextEncoder().encode(json);
216
- const compressed = (0, import_fflate.gzipSync)(encoded);
217
- const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports/${reportId}/replay`;
218
- await fetch(url, {
219
- method: "POST",
220
- headers: {
221
- "x-project-key": projectKey,
222
- "Content-Type": "application/octet-stream"
223
- },
224
- body: compressed.buffer
225
- });
226
- }
227
-
228
188
  // src/theme.ts
229
- var light = {
230
- background: "rgba(255,255,255,0.90)",
231
- backgroundSecondary: "rgba(249,250,251,0.75)",
232
- accent: "#2563eb",
233
- accentHover: "#1d4ed8",
234
- text: "#111827",
235
- textMuted: "#6b7280",
236
- border: "rgba(255,255,255,0.9)",
237
- shadow: "0 32px 80px rgba(0,0,0,0.18), 0 8px 32px rgba(0,0,0,0.1), 0 0 0 1px rgba(0,0,0,0.04)",
238
- buttonText: "#ffffff",
239
- backdropFilter: "blur(32px) saturate(1.8)"
240
- };
241
- var dark = {
242
- background: "rgba(15,20,35,0.88)",
243
- backgroundSecondary: "rgba(5,8,18,0.65)",
244
- accent: "#f97316",
245
- accentHover: "#ea6c0a",
246
- text: "#dde3ef",
247
- textMuted: "#6b7a93",
248
- border: "rgba(255,255,255,0.08)",
249
- shadow: "0 24px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04)",
250
- buttonText: "#ffffff",
251
- backdropFilter: "blur(32px) saturate(1.6)"
252
- };
253
- function resolveTheme(theme) {
254
- if (theme === "dark") return dark;
255
- if (theme === "light") return light;
256
- const override = theme;
257
- return {
258
- ...light,
259
- background: override.background ?? light.background,
260
- accent: override.accent ?? light.accent,
261
- accentHover: override.accent ?? light.accentHover,
262
- text: override.text ?? light.text,
263
- border: override.border ?? light.border
264
- };
265
- }
189
+ var import_core2 = require("@flint/core");
266
190
 
267
191
  // src/FlintModal.tsx
268
192
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -322,10 +246,15 @@ function FlintModal({
322
246
  getNetworkErrors,
323
247
  getReplayEvents,
324
248
  getExternalReplayUrl,
325
- initialSelection = ""
249
+ initialSelection = "",
250
+ enableScreenshot = true,
251
+ statusPageUrl,
252
+ onBeforeSubmit,
253
+ onSuccess,
254
+ onError
326
255
  }) {
327
256
  const { t } = (0, import_react_i18next.useTranslation)();
328
- const colors = resolveTheme(theme);
257
+ const colors = (0, import_core2.resolveTheme)(theme);
329
258
  const isDark = theme === "dark";
330
259
  const [severity, setSeverity] = (0, import_react2.useState)("P2");
331
260
  const [description, setDescription] = (0, import_react2.useState)("");
@@ -382,33 +311,40 @@ function FlintModal({
382
311
  lang: textLang
383
312
  };
384
313
  }
314
+ const payload = {
315
+ reporterId: user?.id ?? "anonymous",
316
+ reporterName: user?.name ?? "Anonymous",
317
+ reporterEmail: user?.email,
318
+ description: isText ? `[Text issue] "${textOriginal.trim()}" \u2192 "${textSuggested.trim()}"` : description.trim(),
319
+ expectedBehavior: !isText ? expectedBehavior.trim() || void 0 : void 0,
320
+ externalReplayUrl: getExternalReplayUrl() || void 0,
321
+ severity: isText ? "P3" : severity,
322
+ url: window.location.href,
323
+ meta: collectedMeta,
324
+ label: isText ? "TEXT" : void 0
325
+ };
326
+ if (onBeforeSubmit) {
327
+ const proceed = await onBeforeSubmit(payload);
328
+ if (!proceed) {
329
+ setStatus("idle");
330
+ return;
331
+ }
332
+ }
385
333
  try {
386
- const res = await submitReport(
387
- serverUrl,
388
- projectKey,
389
- {
390
- reporterId: user?.id ?? "anonymous",
391
- reporterName: user?.name ?? "Anonymous",
392
- description: isText ? `[Text issue] "${textOriginal.trim()}" \u2192 "${textSuggested.trim()}"` : description.trim(),
393
- expectedBehavior: !isText ? expectedBehavior.trim() || void 0 : void 0,
394
- externalReplayUrl: getExternalReplayUrl() || void 0,
395
- severity: isText ? "P3" : severity,
396
- url: window.location.href,
397
- meta: collectedMeta,
398
- label: isText ? "TEXT" : void 0
399
- },
400
- !isText ? screenshot ?? void 0 : void 0
401
- );
334
+ const res = await (0, import_core.submitReport)(serverUrl, projectKey, payload, !isText ? screenshot ?? void 0 : void 0);
402
335
  setResult(res);
403
336
  setStatus("success");
337
+ onSuccess?.(res);
404
338
  const events = getReplayEvents();
405
339
  if (events.length > 0) {
406
- submitReplay(serverUrl, projectKey, res.id, events).catch(() => {
340
+ (0, import_core.submitReplay)(serverUrl, projectKey, res.id, events).catch(() => {
407
341
  });
408
342
  }
409
343
  } catch (err) {
410
- setErrorMsg(err instanceof Error ? err.message : t("errorLabel"));
344
+ const error = err instanceof Error ? err : new Error(t("errorLabel"));
345
+ setErrorMsg(error.message);
411
346
  setStatus("error");
347
+ onError?.(error);
412
348
  }
413
349
  };
414
350
  const inputBorder = isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
@@ -458,157 +394,249 @@ function FlintModal({
458
394
  const isSuccess = status === "success";
459
395
  const ringBorder = isSuccess ? "3px solid #22c55e" : `3px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)"}`;
460
396
  const ringTopColor = isSuccess ? "#22c55e" : colors.accent;
461
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: overlayStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-label": isSuccess ? t("successTitle") : t("sending"), children: [
462
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ModalHeader, { colors, inputBorder, showClose: false, onClose }),
463
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { padding: "40px 32px 48px", textAlign: "center", display: "flex", flexDirection: "column", alignItems: "center" }, children: [
464
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", width: 80, height: 80, marginBottom: 28 }, children: [
465
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
466
- position: "absolute",
467
- inset: -10,
468
- borderRadius: "50%",
469
- border: `1.5px solid ${colors.accent}`,
470
- animation: "_flint_ripple 1.8s ease-out infinite",
471
- opacity: isSuccess ? 0 : 1,
472
- transition: "opacity 0.3s ease"
473
- } }),
474
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
475
- position: "absolute",
476
- inset: -10,
477
- borderRadius: "50%",
478
- border: `1.5px solid ${colors.accent}`,
479
- animation: "_flint_ripple 1.8s ease-out infinite 0.6s",
480
- opacity: isSuccess ? 0 : 1,
481
- transition: "opacity 0.3s ease"
482
- } }),
483
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
484
- position: "absolute",
485
- inset: 0,
486
- borderRadius: "50%",
487
- border: ringBorder,
488
- borderTopColor: ringTopColor,
489
- animation: isSuccess ? "none" : "_flint_spin 0.85s linear infinite",
490
- transition: "border-color 0.45s ease, border-top-color 0.45s ease"
491
- } }),
492
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "absolute", inset: 14, borderRadius: "50%" }, children: [
493
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
494
- position: "absolute",
495
- inset: 0,
496
- borderRadius: "50%",
497
- background: `linear-gradient(135deg, ${colors.accent}30, ${colors.accentHover}50)`,
498
- display: "flex",
499
- alignItems: "center",
500
- justifyContent: "center",
501
- animation: isSuccess ? "none" : "_flint_pulse 2s ease-in-out infinite",
502
- opacity: isSuccess ? 0 : 1,
503
- transition: "opacity 0.3s ease"
504
- }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SparkIcon, { color: colors.accent, size: 20 }) }),
505
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
506
- position: "absolute",
507
- inset: 0,
508
- borderRadius: "50%",
509
- background: "rgba(34,197,94,0.15)",
510
- display: "flex",
511
- alignItems: "center",
512
- justifyContent: "center",
513
- opacity: isSuccess ? 1 : 0,
514
- transform: isSuccess ? "scale(1)" : "scale(0.65)",
515
- transition: "opacity 0.35s ease 0.2s, transform 0.4s cubic-bezier(0.16,1,0.3,1) 0.2s"
516
- }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CheckIcon, { size: 20 }) })
517
- ] })
518
- ] }),
519
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", height: 26, width: "100%", marginBottom: 10 }, children: [
520
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
521
- position: "absolute",
522
- inset: 0,
523
- display: "flex",
524
- alignItems: "center",
525
- justifyContent: "center",
526
- fontSize: 19,
527
- fontWeight: 700,
528
- color: colors.text,
529
- letterSpacing: "-0.02em",
530
- opacity: isSuccess ? 0 : 1,
531
- transition: "opacity 0.25s ease",
532
- pointerEvents: "none"
533
- }, children: t("sending") }),
534
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
535
- position: "absolute",
536
- inset: 0,
537
- display: "flex",
538
- alignItems: "center",
539
- justifyContent: "center",
540
- fontSize: 19,
541
- fontWeight: 700,
542
- color: colors.text,
543
- letterSpacing: "-0.02em",
544
- opacity: isSuccess ? 1 : 0,
545
- transform: isSuccess ? "translateY(0)" : "translateY(6px)",
546
- transition: "opacity 0.35s ease 0.25s, transform 0.35s ease 0.25s",
547
- pointerEvents: isSuccess ? "auto" : "none"
548
- }, children: t("successTitle") })
549
- ] }),
550
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", minHeight: 76, width: "100%" }, children: [
551
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
552
- position: "absolute",
553
- inset: 0,
554
- display: "flex",
555
- alignItems: "center",
556
- justifyContent: "center",
557
- gap: 6,
558
- color: colors.textMuted,
559
- fontSize: 15,
560
- opacity: isSuccess ? 0 : 1,
561
- transition: "opacity 0.2s ease",
562
- pointerEvents: "none"
563
- }, children: [
564
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("capturingContext") }),
565
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendingDots, { color: colors.accent })
566
- ] }),
567
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
568
- position: "absolute",
569
- inset: 0,
570
- display: "flex",
571
- flexDirection: "column",
572
- alignItems: "center",
573
- gap: 10,
574
- opacity: isSuccess ? 1 : 0,
575
- transform: isSuccess ? "translateY(0)" : "translateY(8px)",
576
- transition: "opacity 0.35s ease 0.35s, transform 0.35s ease 0.35s",
577
- pointerEvents: isSuccess ? "auto" : "none"
578
- }, children: [
579
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: 15, color: colors.textMuted, margin: 0 }, children: result ? `ID: ${result.id}` : "" }),
580
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
581
- "button",
582
- {
583
- onClick: onClose,
584
- style: {
585
- width: "100%",
586
- padding: "13px 20px",
587
- borderRadius: 12,
588
- border: "none",
589
- background: `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})`,
590
- color: colors.buttonText,
591
- fontSize: 17,
592
- fontWeight: 700,
593
- cursor: "pointer",
594
- letterSpacing: "-0.01em",
595
- boxShadow: accentGlow,
596
- fontFamily: "inherit",
597
- display: "flex",
598
- alignItems: "center",
599
- justifyContent: "center",
600
- gap: 8
601
- },
602
- children: t("close")
603
- }
604
- )
605
- ] })
606
- ] })
607
- ] })
608
- ] }) });
397
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: overlayStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
398
+ "div",
399
+ {
400
+ style: modalStyle,
401
+ role: "dialog",
402
+ "aria-modal": "true",
403
+ "aria-label": isSuccess ? t("successTitle") : t("sending"),
404
+ children: [
405
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ModalHeader, { colors, inputBorder, showClose: false, onClose }),
406
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
407
+ "div",
408
+ {
409
+ style: {
410
+ padding: "40px 32px 48px",
411
+ textAlign: "center",
412
+ display: "flex",
413
+ flexDirection: "column",
414
+ alignItems: "center"
415
+ },
416
+ children: [
417
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", width: 80, height: 80, marginBottom: 28 }, children: [
418
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
419
+ "div",
420
+ {
421
+ style: {
422
+ position: "absolute",
423
+ inset: -10,
424
+ borderRadius: "50%",
425
+ border: `1.5px solid ${colors.accent}`,
426
+ animation: "_flint_ripple 1.8s ease-out infinite",
427
+ opacity: isSuccess ? 0 : 1,
428
+ transition: "opacity 0.3s ease"
429
+ }
430
+ }
431
+ ),
432
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
433
+ "div",
434
+ {
435
+ style: {
436
+ position: "absolute",
437
+ inset: -10,
438
+ borderRadius: "50%",
439
+ border: `1.5px solid ${colors.accent}`,
440
+ animation: "_flint_ripple 1.8s ease-out infinite 0.6s",
441
+ opacity: isSuccess ? 0 : 1,
442
+ transition: "opacity 0.3s ease"
443
+ }
444
+ }
445
+ ),
446
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
447
+ "div",
448
+ {
449
+ style: {
450
+ position: "absolute",
451
+ inset: 0,
452
+ borderRadius: "50%",
453
+ border: ringBorder,
454
+ borderTopColor: ringTopColor,
455
+ animation: isSuccess ? "none" : "_flint_spin 0.85s linear infinite",
456
+ transition: "border-color 0.45s ease, border-top-color 0.45s ease"
457
+ }
458
+ }
459
+ ),
460
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "absolute", inset: 14, borderRadius: "50%" }, children: [
461
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
462
+ "div",
463
+ {
464
+ style: {
465
+ position: "absolute",
466
+ inset: 0,
467
+ borderRadius: "50%",
468
+ background: `linear-gradient(135deg, ${colors.accent}30, ${colors.accentHover}50)`,
469
+ display: "flex",
470
+ alignItems: "center",
471
+ justifyContent: "center",
472
+ animation: isSuccess ? "none" : "_flint_pulse 2s ease-in-out infinite",
473
+ opacity: isSuccess ? 0 : 1,
474
+ transition: "opacity 0.3s ease"
475
+ },
476
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SparkIcon, { color: colors.accent, size: 20 })
477
+ }
478
+ ),
479
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
480
+ "div",
481
+ {
482
+ style: {
483
+ position: "absolute",
484
+ inset: 0,
485
+ borderRadius: "50%",
486
+ background: "rgba(34,197,94,0.15)",
487
+ display: "flex",
488
+ alignItems: "center",
489
+ justifyContent: "center",
490
+ opacity: isSuccess ? 1 : 0,
491
+ transform: isSuccess ? "scale(1)" : "scale(0.65)",
492
+ transition: "opacity 0.35s ease 0.2s, transform 0.4s cubic-bezier(0.16,1,0.3,1) 0.2s"
493
+ },
494
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CheckIcon, { size: 20 })
495
+ }
496
+ )
497
+ ] })
498
+ ] }),
499
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", height: 26, width: "100%", marginBottom: 10 }, children: [
500
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
501
+ "div",
502
+ {
503
+ style: {
504
+ position: "absolute",
505
+ inset: 0,
506
+ display: "flex",
507
+ alignItems: "center",
508
+ justifyContent: "center",
509
+ fontSize: 19,
510
+ fontWeight: 700,
511
+ color: colors.text,
512
+ letterSpacing: "-0.02em",
513
+ opacity: isSuccess ? 0 : 1,
514
+ transition: "opacity 0.25s ease",
515
+ pointerEvents: "none"
516
+ },
517
+ children: t("sending")
518
+ }
519
+ ),
520
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
521
+ "div",
522
+ {
523
+ style: {
524
+ position: "absolute",
525
+ inset: 0,
526
+ display: "flex",
527
+ alignItems: "center",
528
+ justifyContent: "center",
529
+ fontSize: 19,
530
+ fontWeight: 700,
531
+ color: colors.text,
532
+ letterSpacing: "-0.02em",
533
+ opacity: isSuccess ? 1 : 0,
534
+ transform: isSuccess ? "translateY(0)" : "translateY(6px)",
535
+ transition: "opacity 0.35s ease 0.25s, transform 0.35s ease 0.25s",
536
+ pointerEvents: isSuccess ? "auto" : "none"
537
+ },
538
+ children: t("successTitle")
539
+ }
540
+ )
541
+ ] }),
542
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", minHeight: 76, width: "100%" }, children: [
543
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
544
+ "div",
545
+ {
546
+ style: {
547
+ position: "absolute",
548
+ inset: 0,
549
+ display: "flex",
550
+ alignItems: "center",
551
+ justifyContent: "center",
552
+ gap: 6,
553
+ color: colors.textMuted,
554
+ fontSize: 15,
555
+ opacity: isSuccess ? 0 : 1,
556
+ transition: "opacity 0.2s ease",
557
+ pointerEvents: "none"
558
+ },
559
+ children: [
560
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("capturingContext") }),
561
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendingDots, { color: colors.accent })
562
+ ]
563
+ }
564
+ ),
565
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
566
+ "div",
567
+ {
568
+ style: {
569
+ position: "absolute",
570
+ inset: 0,
571
+ display: "flex",
572
+ flexDirection: "column",
573
+ alignItems: "center",
574
+ gap: 10,
575
+ opacity: isSuccess ? 1 : 0,
576
+ transform: isSuccess ? "translateY(0)" : "translateY(8px)",
577
+ transition: "opacity 0.35s ease 0.35s, transform 0.35s ease 0.35s",
578
+ pointerEvents: isSuccess ? "auto" : "none"
579
+ },
580
+ children: [
581
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: 15, color: colors.textMuted, margin: 0 }, children: result ? `ID: ${result.id}` : "" }),
582
+ statusPageUrl && user?.id && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
583
+ "a",
584
+ {
585
+ href: `${statusPageUrl}/status?project_key=${encodeURIComponent(projectKey)}&reporter_id=${encodeURIComponent(user.id)}&server_url=${encodeURIComponent(serverUrl)}`,
586
+ target: "_blank",
587
+ rel: "noreferrer",
588
+ style: {
589
+ fontSize: 14,
590
+ color: colors.accent,
591
+ textDecoration: "none",
592
+ fontWeight: 600,
593
+ animation: "_flint_success_up 0.35s ease 0.4s both"
594
+ },
595
+ children: [
596
+ "\u{1F4CB}",
597
+ " Track your bugs ",
598
+ "\u2192"
599
+ ]
600
+ }
601
+ ),
602
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
603
+ "button",
604
+ {
605
+ onClick: onClose,
606
+ style: {
607
+ width: "100%",
608
+ padding: "13px 20px",
609
+ borderRadius: 12,
610
+ border: "none",
611
+ background: `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})`,
612
+ color: colors.buttonText,
613
+ fontSize: 17,
614
+ fontWeight: 700,
615
+ cursor: "pointer",
616
+ letterSpacing: "-0.01em",
617
+ boxShadow: accentGlow,
618
+ fontFamily: "inherit",
619
+ display: "flex",
620
+ alignItems: "center",
621
+ justifyContent: "center",
622
+ gap: 8
623
+ },
624
+ children: t("close")
625
+ }
626
+ )
627
+ ]
628
+ }
629
+ )
630
+ ] })
631
+ ]
632
+ }
633
+ )
634
+ ]
635
+ }
636
+ ) });
609
637
  }
610
638
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
611
- annotating && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
639
+ enableScreenshot && annotating && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
612
640
  ScreenAnnotator,
613
641
  {
614
642
  zIndex: zIndex + 1,
@@ -632,37 +660,43 @@ function FlintModal({
632
660
  }
633
661
  ),
634
662
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, style: { padding: "20px 24px 24px" }, children: [
635
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
636
- display: "flex",
637
- gap: 4,
638
- marginBottom: 20,
639
- background: colors.backgroundSecondary,
640
- borderRadius: 12,
641
- padding: 4,
642
- border: `1px solid ${inputBorder}`
643
- }, children: ["bug", "text"].map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
644
- "button",
663
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
664
+ "div",
645
665
  {
646
- type: "button",
647
- onClick: () => setMode(m),
648
666
  style: {
649
- flex: 1,
650
- padding: "8px 10px",
651
- borderRadius: 9,
652
- border: "none",
653
- cursor: "pointer",
654
- fontSize: 13,
655
- fontWeight: mode === m ? 700 : 500,
656
- fontFamily: "inherit",
657
- transition: "background 0.15s, color 0.15s",
658
- background: mode === m ? `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})` : "transparent",
659
- color: mode === m ? colors.buttonText : colors.textMuted,
660
- boxShadow: mode === m ? `0 2px 8px ${colors.accent}30` : "none"
667
+ display: "flex",
668
+ gap: 4,
669
+ marginBottom: 20,
670
+ background: colors.backgroundSecondary,
671
+ borderRadius: 12,
672
+ padding: 4,
673
+ border: `1px solid ${inputBorder}`
661
674
  },
662
- children: m === "bug" ? "\u{1F41B} Bug" : "\u{1F524} Text / Translation"
663
- },
664
- m
665
- )) }),
675
+ children: ["bug", "text"].map((m) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
676
+ "button",
677
+ {
678
+ type: "button",
679
+ onClick: () => setMode(m),
680
+ style: {
681
+ flex: 1,
682
+ padding: "8px 10px",
683
+ borderRadius: 9,
684
+ border: "none",
685
+ cursor: "pointer",
686
+ fontSize: 13,
687
+ fontWeight: mode === m ? 700 : 500,
688
+ fontFamily: "inherit",
689
+ transition: "background 0.15s, color 0.15s",
690
+ background: mode === m ? `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})` : "transparent",
691
+ color: mode === m ? colors.buttonText : colors.textMuted,
692
+ boxShadow: mode === m ? `0 2px 8px ${colors.accent}30` : "none"
693
+ },
694
+ children: m === "bug" ? "\u{1F41B} Bug" : "\u{1F524} Text / Translation"
695
+ },
696
+ m
697
+ ))
698
+ }
699
+ ),
666
700
  mode === "text" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
667
701
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 14 }, children: [
668
702
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, htmlFor: "flint-text-original", children: "Original text" }),
@@ -759,7 +793,7 @@ function FlintModal({
759
793
  }
760
794
  )
761
795
  ] }),
762
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 20, display: mode === "text" ? "none" : void 0 }, children: [
796
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: 20, display: mode === "text" || !enableScreenshot ? "none" : void 0 }, children: [
763
797
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FieldLabel, { colors, children: t("screenshotLabel") }),
764
798
  screenshot ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
765
799
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -770,7 +804,20 @@ function FlintModal({
770
804
  style: { height: 60, borderRadius: 8, objectFit: "cover", border: `1px solid ${inputBorder}` }
771
805
  }
772
806
  ),
773
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 13, color: colors.textMuted, flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: screenshot.name }),
807
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
808
+ "span",
809
+ {
810
+ style: {
811
+ fontSize: 13,
812
+ color: colors.textMuted,
813
+ flex: 1,
814
+ overflow: "hidden",
815
+ textOverflow: "ellipsis",
816
+ whiteSpace: "nowrap"
817
+ },
818
+ children: screenshot.name
819
+ }
820
+ ),
774
821
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
775
822
  "button",
776
823
  {
@@ -857,31 +904,43 @@ function FlintModal({
857
904
  }
858
905
  )
859
906
  ] }),
860
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
861
- display: "flex",
862
- alignItems: "center",
863
- gap: 8,
864
- padding: "9px 12px",
865
- borderRadius: 10,
866
- background: isDark ? "rgba(255,255,255,0.04)" : "rgba(0,77,240,0.04)",
867
- border: `1px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,77,240,0.1)"}`,
868
- marginBottom: 16
869
- }, children: [
870
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 16 }, children: "\u{1F3A5}" }),
871
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 14, color: colors.textMuted, lineHeight: 1.4 }, children: t("replayInfo") })
872
- ] }),
873
- status === "error" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
874
- padding: "10px 13px",
875
- borderRadius: 10,
876
- background: "rgba(239,68,68,0.08)",
877
- border: "1px solid rgba(239,68,68,0.2)",
878
- color: "#f87171",
879
- fontSize: 14,
880
- marginBottom: 16
881
- }, children: [
882
- "\u26A0\uFE0F ",
883
- errorMsg || t("errorLabel")
884
- ] }),
907
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
908
+ "div",
909
+ {
910
+ style: {
911
+ display: "flex",
912
+ alignItems: "center",
913
+ gap: 8,
914
+ padding: "9px 12px",
915
+ borderRadius: 10,
916
+ background: isDark ? "rgba(255,255,255,0.04)" : "rgba(0,77,240,0.04)",
917
+ border: `1px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,77,240,0.1)"}`,
918
+ marginBottom: 16
919
+ },
920
+ children: [
921
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 16 }, children: "\u{1F3A5}" }),
922
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 14, color: colors.textMuted, lineHeight: 1.4 }, children: t("replayInfo") })
923
+ ]
924
+ }
925
+ ),
926
+ status === "error" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
927
+ "div",
928
+ {
929
+ style: {
930
+ padding: "10px 13px",
931
+ borderRadius: 10,
932
+ background: "rgba(239,68,68,0.08)",
933
+ border: "1px solid rgba(239,68,68,0.2)",
934
+ color: "#f87171",
935
+ fontSize: 14,
936
+ marginBottom: 16
937
+ },
938
+ children: [
939
+ "\u26A0\uFE0F ",
940
+ errorMsg || t("errorLabel")
941
+ ]
942
+ }
943
+ ),
885
944
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
886
945
  "button",
887
946
  {
@@ -942,49 +1001,68 @@ function ModalHeader({
942
1001
  titleId,
943
1002
  title
944
1003
  }) {
945
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
946
- display: "flex",
947
- alignItems: "center",
948
- gap: 10,
949
- padding: "16px 20px 14px",
950
- borderBottom: `1px solid ${inputBorder}`
951
- }, children: [
952
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
953
- width: 28,
954
- height: 28,
955
- borderRadius: 8,
956
- background: `linear-gradient(135deg, ${colors.accent}20, ${colors.accentHover}35)`,
957
- border: `1px solid ${colors.accent}30`,
958
- display: "flex",
959
- alignItems: "center",
960
- justifyContent: "center",
961
- flexShrink: 0
962
- }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SparkIcon, { color: colors.accent, size: 13 }) }),
963
- titleId && title ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { id: titleId, style: { margin: 0, fontSize: 16, fontWeight: 600, color: colors.text, letterSpacing: "-0.01em", flex: 1 }, children: title }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { flex: 1, fontSize: 15, fontWeight: 600, color: colors.textMuted }, children: "Flint" }),
964
- showClose && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
965
- "button",
966
- {
967
- onClick: onClose,
968
- "aria-label": "Close",
969
- style: {
970
- background: "none",
971
- border: "none",
972
- cursor: "pointer",
973
- padding: 4,
974
- color: colors.textMuted,
975
- fontSize: 22,
976
- lineHeight: 1,
977
- borderRadius: 6,
978
- display: "flex",
979
- alignItems: "center",
980
- justifyContent: "center",
981
- opacity: 0.6,
982
- fontFamily: "inherit"
983
- },
984
- children: "\xD7"
985
- }
986
- )
987
- ] });
1004
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1005
+ "div",
1006
+ {
1007
+ style: {
1008
+ display: "flex",
1009
+ alignItems: "center",
1010
+ gap: 10,
1011
+ padding: "16px 20px 14px",
1012
+ borderBottom: `1px solid ${inputBorder}`
1013
+ },
1014
+ children: [
1015
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1016
+ "div",
1017
+ {
1018
+ style: {
1019
+ width: 28,
1020
+ height: 28,
1021
+ borderRadius: 8,
1022
+ background: `linear-gradient(135deg, ${colors.accent}20, ${colors.accentHover}35)`,
1023
+ border: `1px solid ${colors.accent}30`,
1024
+ display: "flex",
1025
+ alignItems: "center",
1026
+ justifyContent: "center",
1027
+ flexShrink: 0
1028
+ },
1029
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SparkIcon, { color: colors.accent, size: 13 })
1030
+ }
1031
+ ),
1032
+ titleId && title ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1033
+ "h2",
1034
+ {
1035
+ id: titleId,
1036
+ style: { margin: 0, fontSize: 16, fontWeight: 600, color: colors.text, letterSpacing: "-0.01em", flex: 1 },
1037
+ children: title
1038
+ }
1039
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { flex: 1, fontSize: 15, fontWeight: 600, color: colors.textMuted }, children: "Flint" }),
1040
+ showClose && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1041
+ "button",
1042
+ {
1043
+ onClick: onClose,
1044
+ "aria-label": "Close",
1045
+ style: {
1046
+ background: "none",
1047
+ border: "none",
1048
+ cursor: "pointer",
1049
+ padding: 4,
1050
+ color: colors.textMuted,
1051
+ fontSize: 22,
1052
+ lineHeight: 1,
1053
+ borderRadius: 6,
1054
+ display: "flex",
1055
+ alignItems: "center",
1056
+ justifyContent: "center",
1057
+ opacity: 0.6,
1058
+ fontFamily: "inherit"
1059
+ },
1060
+ children: "\xD7"
1061
+ }
1062
+ )
1063
+ ]
1064
+ }
1065
+ );
988
1066
  }
989
1067
  function FieldLabel({
990
1068
  children,
@@ -1046,19 +1124,71 @@ function SeverityButton({ sev, label, selected, hint, color, accent, border, bg,
1046
1124
  },
1047
1125
  children: [
1048
1126
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { width: 8, height: 8, borderRadius: "50%", background: color, display: "block" } }),
1049
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 13, fontWeight: selected ? 700 : 500, color: selected ? accent : text, letterSpacing: "0.02em" }, children: sev }),
1127
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1128
+ "span",
1129
+ {
1130
+ style: {
1131
+ fontSize: 13,
1132
+ fontWeight: selected ? 700 : 500,
1133
+ color: selected ? accent : text,
1134
+ letterSpacing: "0.02em"
1135
+ },
1136
+ children: sev
1137
+ }
1138
+ ),
1050
1139
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 11, color: selected ? accent : text, opacity: 0.6, letterSpacing: "0.02em" }, children: label })
1051
1140
  ]
1052
1141
  }
1053
1142
  );
1054
1143
  }
1055
1144
  function SparkIcon({ color = "currentColor", size = 14 }) {
1056
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" }) });
1145
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1146
+ "svg",
1147
+ {
1148
+ width: size,
1149
+ height: size,
1150
+ viewBox: "0 0 24 24",
1151
+ fill: "none",
1152
+ stroke: color,
1153
+ strokeWidth: "2.2",
1154
+ strokeLinecap: "round",
1155
+ strokeLinejoin: "round",
1156
+ "aria-hidden": "true",
1157
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" })
1158
+ }
1159
+ );
1057
1160
  }
1058
1161
  function CheckIcon({ size = 20 }) {
1059
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "#22c55e", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "20 6 9 17 4 12" }) });
1162
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1163
+ "svg",
1164
+ {
1165
+ width: size,
1166
+ height: size,
1167
+ viewBox: "0 0 24 24",
1168
+ fill: "none",
1169
+ stroke: "#22c55e",
1170
+ strokeWidth: "2.5",
1171
+ strokeLinecap: "round",
1172
+ strokeLinejoin: "round",
1173
+ "aria-hidden": "true",
1174
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "20 6 9 17 4 12" })
1175
+ }
1176
+ );
1060
1177
  }
1061
1178
 
1179
+ // src/FlintWidget.tsx
1180
+ var import_react4 = require("react");
1181
+ var import_react_i18next3 = require("react-i18next");
1182
+
1183
+ // src/collectors/console.ts
1184
+ var import_core3 = require("@flint/core");
1185
+
1186
+ // src/collectors/environment.ts
1187
+ var import_core4 = require("@flint/core");
1188
+
1189
+ // src/collectors/network.ts
1190
+ var import_core5 = require("@flint/core");
1191
+
1062
1192
  // src/i18n/index.ts
1063
1193
  var import_i18next = require("i18next");
1064
1194
  var import_react_i18next2 = require("react-i18next");
@@ -1108,251 +1238,15 @@ widgetI18n.use(import_react_i18next2.initReactI18next).init({
1108
1238
  });
1109
1239
  var i18n_default = widgetI18n;
1110
1240
 
1111
- // src/collectors/environment.ts
1112
- function collectEnvironment() {
1113
- const ua = navigator.userAgent;
1114
- let browser = "Unknown";
1115
- const chromeM = ua.match(/Chrome\/(\d+)/);
1116
- const firefoxM = ua.match(/Firefox\/(\d+)/);
1117
- const edgeM = ua.match(/Edg\/(\d+)/);
1118
- const safariM = ua.match(/Version\/(\d+)/);
1119
- const operaM = ua.match(/OPR\/(\d+)/);
1120
- if (operaM) {
1121
- browser = `Opera ${operaM[1]}`;
1122
- } else if (edgeM) {
1123
- browser = `Edge ${edgeM[1]}`;
1124
- } else if (chromeM && !/Edg|OPR/.test(ua)) {
1125
- browser = `Chrome ${chromeM[1]}`;
1126
- } else if (firefoxM) {
1127
- browser = `Firefox ${firefoxM[1]}`;
1128
- } else if (safariM && /Safari\//.test(ua)) {
1129
- browser = `Safari ${safariM[1]}`;
1130
- }
1131
- let os = "Unknown";
1132
- const macM = ua.match(/Mac OS X (\d+[._]\d+)/);
1133
- const winM = ua.match(/Windows NT (\d+\.\d+)/);
1134
- const androidM = ua.match(/Android (\d+)/);
1135
- const iosM = ua.match(/iPhone OS (\d+[._]\d+)/);
1136
- if (macM) {
1137
- os = `macOS ${macM[1].replace("_", ".")}`;
1138
- } else if (winM) {
1139
- const winMap = {
1140
- "10.0": "10/11",
1141
- "6.3": "8.1",
1142
- "6.2": "8",
1143
- "6.1": "7"
1144
- };
1145
- os = `Windows ${winMap[winM[1]] ?? winM[1]}`;
1146
- } else if (androidM) {
1147
- os = `Android ${androidM[1]}`;
1148
- } else if (iosM) {
1149
- os = `iOS ${iosM[1].replace(/_/g, ".")}`;
1150
- } else if (/Linux/.test(ua)) {
1151
- os = "Linux";
1152
- }
1153
- return {
1154
- browser,
1155
- os,
1156
- viewport: `${window.innerWidth}x${window.innerHeight}`,
1157
- screen: `${screen.width}x${screen.height}`,
1158
- language: navigator.language,
1159
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
1160
- online: navigator.onLine
1161
- };
1162
- }
1163
-
1164
- // src/collectors/console.ts
1165
- var MAX_ENTRIES = 50;
1166
- function createConsoleCollector() {
1167
- const entries = [];
1168
- let active = false;
1169
- const originals = {
1170
- log: console.log.bind(console),
1171
- warn: console.warn.bind(console),
1172
- error: console.error.bind(console)
1173
- };
1174
- let origOnerror = null;
1175
- let origUnhandled = null;
1176
- function push(level, args) {
1177
- let str;
1178
- try {
1179
- str = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
1180
- } catch {
1181
- str = String(args[0]);
1182
- }
1183
- if (str.length > 500) str = str.slice(0, 500) + "\u2026";
1184
- entries.push({ level, args: str, timestamp: Date.now() });
1185
- if (entries.length > MAX_ENTRIES) entries.shift();
1186
- }
1187
- return {
1188
- start() {
1189
- if (active) return;
1190
- active = true;
1191
- console.log = (...args) => {
1192
- push("log", args);
1193
- originals.log(...args);
1194
- };
1195
- console.warn = (...args) => {
1196
- push("warn", args);
1197
- originals.warn(...args);
1198
- };
1199
- console.error = (...args) => {
1200
- push("error", args);
1201
- originals.error(...args);
1202
- };
1203
- origOnerror = window.onerror;
1204
- window.onerror = (msg, src, line, col, err) => {
1205
- push("error", [err?.message ?? String(msg), `${src}:${line}:${col}`]);
1206
- if (typeof origOnerror === "function")
1207
- return origOnerror(msg, src, line, col, err);
1208
- return false;
1209
- };
1210
- origUnhandled = window.onunhandledrejection;
1211
- window.onunhandledrejection = (event) => {
1212
- const reason = event.reason instanceof Error ? event.reason.message : JSON.stringify(event.reason);
1213
- push("error", ["UnhandledRejection:", reason]);
1214
- if (typeof origUnhandled === "function")
1215
- origUnhandled.call(window, event);
1216
- };
1217
- },
1218
- stop() {
1219
- if (!active) return;
1220
- active = false;
1221
- console.log = originals.log;
1222
- console.warn = originals.warn;
1223
- console.error = originals.error;
1224
- window.onerror = origOnerror;
1225
- window.onunhandledrejection = origUnhandled;
1226
- },
1227
- getEntries() {
1228
- return [...entries];
1229
- }
1230
- };
1231
- }
1232
-
1233
- // src/collectors/network.ts
1234
- var MAX_ENTRIES2 = 20;
1235
- var BLOCKED_HOSTS = /* @__PURE__ */ new Set([
1236
- "browser-intake-datadoghq.com",
1237
- "rum.browser-intake-datadoghq.com",
1238
- "logs.browser-intake-datadoghq.com",
1239
- "session-replay.browser-intake-datadoghq.com"
1240
- ]);
1241
- function isBlockedUrl(url, extra) {
1242
- try {
1243
- const host = new URL(url, location.href).hostname;
1244
- const all = [...BLOCKED_HOSTS, ...extra];
1245
- return all.some((b) => host === b || host.endsWith("." + b));
1246
- } catch {
1247
- return false;
1248
- }
1249
- }
1250
- function truncateUrl(url) {
1251
- try {
1252
- const u = new URL(url, location.href);
1253
- const base = `${u.origin}${u.pathname}`;
1254
- return base.length > 200 ? base.slice(0, 200) + "\u2026" : base;
1255
- } catch {
1256
- return url.length > 200 ? url.slice(0, 200) + "\u2026" : url;
1257
- }
1258
- }
1259
- function createNetworkCollector(extraBlockedHosts = []) {
1260
- const entries = [];
1261
- const blocked = new Set(extraBlockedHosts);
1262
- let origFetch = null;
1263
- let origXHROpen = null;
1264
- let active = false;
1265
- function push(entry) {
1266
- entries.push(entry);
1267
- if (entries.length > MAX_ENTRIES2) entries.shift();
1268
- }
1269
- return {
1270
- start() {
1271
- if (active) return;
1272
- active = true;
1273
- origFetch = window.fetch;
1274
- window.fetch = async (input, init) => {
1275
- const method = (init?.method ?? "GET").toUpperCase();
1276
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
1277
- const startTime = Date.now();
1278
- const res = await origFetch.call(window, input, init);
1279
- if (!isBlockedUrl(url, blocked)) {
1280
- push({
1281
- method,
1282
- url: truncateUrl(url),
1283
- status: res.status,
1284
- duration: Date.now() - startTime,
1285
- timestamp: startTime
1286
- });
1287
- }
1288
- return res;
1289
- };
1290
- origXHROpen = XMLHttpRequest.prototype.open;
1291
- XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
1292
- const startTime = Date.now();
1293
- const urlStr = typeof url === "string" ? url : url.href;
1294
- this.addEventListener("load", () => {
1295
- if (!isBlockedUrl(urlStr, blocked)) {
1296
- push({
1297
- method: method.toUpperCase(),
1298
- url: truncateUrl(urlStr),
1299
- status: this.status,
1300
- duration: Date.now() - startTime,
1301
- timestamp: startTime
1302
- });
1303
- }
1304
- });
1305
- return origXHROpen.apply(this, [
1306
- method,
1307
- url,
1308
- async ?? true,
1309
- username,
1310
- password
1311
- ]);
1312
- };
1313
- },
1314
- stop() {
1315
- if (!active) return;
1316
- active = false;
1317
- if (origFetch) window.fetch = origFetch;
1318
- if (origXHROpen) XMLHttpRequest.prototype.open = origXHROpen;
1319
- },
1320
- getEntries() {
1321
- return [...entries];
1322
- }
1323
- };
1324
- }
1325
-
1326
1241
  // src/store.ts
1242
+ var import_core6 = require("@flint/core");
1327
1243
  var import_react3 = require("react");
1328
- var state = { user: void 0, sessionReplay: void 0 };
1329
- var listeners = /* @__PURE__ */ new Set();
1330
- function emit() {
1331
- listeners.forEach((fn) => fn());
1332
- }
1333
- function subscribeStore(fn) {
1334
- listeners.add(fn);
1335
- return () => listeners.delete(fn);
1336
- }
1337
- function getSnapshot() {
1338
- return state;
1339
- }
1244
+ var import_core7 = require("@flint/core");
1340
1245
  function useFlintStore() {
1341
- return (0, import_react3.useSyncExternalStore)(subscribeStore, getSnapshot);
1246
+ return (0, import_react3.useSyncExternalStore)(import_core6.subscribe, import_core6.getSnapshot);
1342
1247
  }
1343
- var flint = {
1344
- setUser(user) {
1345
- state = { ...state, user: user ?? void 0 };
1346
- emit();
1347
- },
1348
- setSessionReplay(url) {
1349
- state = { ...state, sessionReplay: url };
1350
- emit();
1351
- }
1352
- };
1353
1248
 
1354
1249
  // src/FlintWidget.tsx
1355
- var import_rrweb = require("rrweb");
1356
1250
  var import_jsx_runtime3 = require("react/jsx-runtime");
1357
1251
  var REPLAY_WINDOW_MS = 6e4;
1358
1252
  function FlintWidget(props) {
@@ -1370,20 +1264,46 @@ function WidgetContent({
1370
1264
  extraFields,
1371
1265
  buttonLabel,
1372
1266
  theme = "dark",
1373
- zIndex = 9999
1267
+ zIndex = 9999,
1268
+ statusPageUrl,
1269
+ datadogSite,
1270
+ enableReplay = true,
1271
+ enableScreenshot = true,
1272
+ enableConsole = true,
1273
+ enableNetwork = true,
1274
+ onBeforeSubmit,
1275
+ onSuccess,
1276
+ onError,
1277
+ onOpen,
1278
+ onClose
1374
1279
  }) {
1375
1280
  const globalState = useFlintStore();
1376
1281
  const resolvedUser = user ?? globalState.user;
1377
1282
  const resolvedSessionReplay = extraFields?.sessionReplay ?? globalState.sessionReplay;
1378
1283
  const getExternalReplayUrl = () => {
1379
1284
  const src = resolvedSessionReplay;
1380
- return typeof src === "function" ? src() : src;
1285
+ const explicit = typeof src === "function" ? src() : src;
1286
+ if (explicit) return explicit;
1287
+ if (datadogSite) {
1288
+ try {
1289
+ const ddRum = window.DD_RUM;
1290
+ const ctx = ddRum?.getInternalContext?.();
1291
+ if (ctx?.session_id) {
1292
+ const now = Math.floor(Date.now() / 1e3);
1293
+ const fromTs = now - 30;
1294
+ const toTs = now + 5;
1295
+ return `https://${datadogSite}/rum/replay/sessions/${ctx.session_id}?from_ts=${fromTs}&to_ts=${toTs}&tab=replay&live=false`;
1296
+ }
1297
+ } catch {
1298
+ }
1299
+ }
1300
+ return void 0;
1381
1301
  };
1382
1302
  const { t } = (0, import_react_i18next3.useTranslation)();
1383
1303
  const [open, setOpen] = (0, import_react4.useState)(false);
1384
1304
  const [hovered, setHovered] = (0, import_react4.useState)(false);
1385
1305
  const pendingSelection = (0, import_react4.useRef)("");
1386
- const colors = resolveTheme(theme);
1306
+ const colors = (0, import_core2.resolveTheme)(theme);
1387
1307
  const [selectionTooltip, setSelectionTooltip] = (0, import_react4.useState)(null);
1388
1308
  const tooltipRef = (0, import_react4.useRef)(null);
1389
1309
  const triggerRef = (0, import_react4.useRef)(null);
@@ -1424,16 +1344,17 @@ function WidgetContent({
1424
1344
  pendingSelection.current = text;
1425
1345
  setSelectionTooltip(null);
1426
1346
  setOpen(true);
1347
+ onOpen?.();
1427
1348
  };
1428
1349
  const consoleCollector = (0, import_react4.useRef)(null);
1429
1350
  const networkCollector = (0, import_react4.useRef)(null);
1430
1351
  const replayEvents = (0, import_react4.useRef)([]);
1431
1352
  const stopReplay = (0, import_react4.useRef)(null);
1432
- if (!consoleCollector.current) {
1433
- consoleCollector.current = createConsoleCollector();
1353
+ if (enableConsole && !consoleCollector.current) {
1354
+ consoleCollector.current = (0, import_core3.createConsoleCollector)();
1434
1355
  consoleCollector.current.start();
1435
1356
  }
1436
- if (!networkCollector.current) {
1357
+ if (enableNetwork && !networkCollector.current) {
1437
1358
  const flintHost = (() => {
1438
1359
  try {
1439
1360
  return new URL(serverUrl).hostname;
@@ -1441,26 +1362,33 @@ function WidgetContent({
1441
1362
  return "";
1442
1363
  }
1443
1364
  })();
1444
- networkCollector.current = createNetworkCollector(flintHost ? [flintHost] : []);
1365
+ networkCollector.current = (0, import_core5.createNetworkCollector)(flintHost ? [flintHost] : []);
1445
1366
  networkCollector.current.start();
1446
1367
  }
1447
1368
  (0, import_react4.useEffect)(() => {
1448
- const stopFn = (0, import_rrweb.record)({
1449
- emit(event) {
1450
- replayEvents.current.push(event);
1451
- const cutoff = Date.now() - REPLAY_WINDOW_MS;
1452
- while (replayEvents.current.length > 0 && replayEvents.current[0].timestamp < cutoff) {
1453
- replayEvents.current.shift();
1454
- }
1455
- }
1456
- });
1457
- stopReplay.current = stopFn ?? null;
1369
+ let cancelled = false;
1370
+ if (enableReplay) {
1371
+ import("rrweb").then(({ record }) => {
1372
+ if (cancelled) return;
1373
+ const stopFn = record({
1374
+ emit(event) {
1375
+ replayEvents.current.push(event);
1376
+ const cutoff = Date.now() - REPLAY_WINDOW_MS;
1377
+ while (replayEvents.current.length > 0 && replayEvents.current[0].timestamp < cutoff) {
1378
+ replayEvents.current.shift();
1379
+ }
1380
+ }
1381
+ });
1382
+ stopReplay.current = stopFn ?? null;
1383
+ });
1384
+ }
1458
1385
  return () => {
1386
+ cancelled = true;
1459
1387
  consoleCollector.current?.stop();
1460
1388
  networkCollector.current?.stop();
1461
1389
  stopReplay.current?.();
1462
1390
  };
1463
- }, []);
1391
+ }, [enableReplay]);
1464
1392
  const label = buttonLabel ?? t("buttonLabel");
1465
1393
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1466
1394
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
@@ -1470,7 +1398,10 @@ function WidgetContent({
1470
1398
  onMouseDown: () => {
1471
1399
  pendingSelection.current = window.getSelection()?.toString().trim() ?? "";
1472
1400
  },
1473
- onClick: () => setOpen(true),
1401
+ onClick: () => {
1402
+ setOpen(true);
1403
+ onOpen?.();
1404
+ },
1474
1405
  onMouseEnter: () => setHovered(true),
1475
1406
  onMouseLeave: () => setHovered(false),
1476
1407
  "aria-label": label,
@@ -1551,25 +1482,45 @@ function WidgetContent({
1551
1482
  zIndex,
1552
1483
  onClose: () => {
1553
1484
  setOpen(false);
1485
+ onClose?.();
1554
1486
  pendingSelection.current = "";
1555
1487
  },
1556
- getEnvironment: collectEnvironment,
1488
+ getEnvironment: import_core4.collectEnvironment,
1557
1489
  getConsoleLogs: () => consoleCollector.current?.getEntries() ?? [],
1558
1490
  getNetworkErrors: () => networkCollector.current?.getEntries() ?? [],
1559
1491
  getReplayEvents: () => [...replayEvents.current],
1560
1492
  getExternalReplayUrl,
1561
- initialSelection: pendingSelection.current
1493
+ initialSelection: pendingSelection.current,
1494
+ enableScreenshot,
1495
+ statusPageUrl,
1496
+ onBeforeSubmit,
1497
+ onSuccess,
1498
+ onError
1562
1499
  }
1563
1500
  )
1564
1501
  ] });
1565
1502
  }
1566
1503
  function TextIcon() {
1567
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
1568
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M17 10H3" }),
1569
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 6H3" }),
1570
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 14H3" }),
1571
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M17 18H3" })
1572
- ] });
1504
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1505
+ "svg",
1506
+ {
1507
+ width: "12",
1508
+ height: "12",
1509
+ viewBox: "0 0 24 24",
1510
+ fill: "none",
1511
+ stroke: "currentColor",
1512
+ strokeWidth: "2.2",
1513
+ strokeLinecap: "round",
1514
+ strokeLinejoin: "round",
1515
+ "aria-hidden": "true",
1516
+ children: [
1517
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M17 10H3" }),
1518
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 6H3" }),
1519
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 14H3" }),
1520
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M17 18H3" })
1521
+ ]
1522
+ }
1523
+ );
1573
1524
  }
1574
1525
  function SparkIcon2() {
1575
1526
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(