@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.js CHANGED
@@ -1,14 +1,13 @@
1
- // src/FlintWidget.tsx
2
- import { useState as useState3, useEffect as useEffect3, useRef as useRef3, useCallback as useCallback2 } from "react";
3
- import { I18nextProvider, useTranslation as useTranslation2 } from "react-i18next";
4
-
5
1
  // src/FlintModal.tsx
6
- import { useRef as useRef2, useState as useState2, useEffect as useEffect2, useCallback } from "react";
2
+ import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
7
3
  import { useTranslation } from "react-i18next";
8
4
 
5
+ // src/api.ts
6
+ import { submitReplay, submitReport } from "@flint/core";
7
+
9
8
  // src/ScreenAnnotator.tsx
10
- import { useEffect, useRef, useState } from "react";
11
9
  import { domToCanvas } from "modern-screenshot";
10
+ import { useEffect, useRef, useState } from "react";
12
11
  import { jsx, jsxs } from "react/jsx-runtime";
13
12
  function normalizeRect(startX, startY, endX, endY) {
14
13
  return {
@@ -53,9 +52,7 @@ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
53
52
  const dpr = window.devicePixelRatio ?? 1;
54
53
  const vw = window.innerWidth;
55
54
  const vh = window.innerHeight;
56
- await new Promise(
57
- (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
58
- );
55
+ await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
59
56
  try {
60
57
  const fullCanvas = await domToCanvas(document.documentElement, {
61
58
  scale: dpr,
@@ -70,17 +67,7 @@ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
70
67
  canvas.width = vw * dpr;
71
68
  canvas.height = vh * dpr;
72
69
  const ctx = canvas.getContext("2d");
73
- ctx.drawImage(
74
- fullCanvas,
75
- 0,
76
- 0,
77
- vw * dpr,
78
- vh * dpr,
79
- 0,
80
- 0,
81
- vw * dpr,
82
- vh * dpr
83
- );
70
+ ctx.drawImage(fullCanvas, 0, 0, vw * dpr, vh * dpr, 0, 0, vw * dpr, vh * dpr);
84
71
  ctx.fillStyle = "rgba(255,200,0,0.25)";
85
72
  ctx.fillRect(finalRect.x * dpr, finalRect.y * dpr, finalRect.w * dpr, finalRect.h * dpr);
86
73
  ctx.strokeStyle = "#f97316";
@@ -118,123 +105,50 @@ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
118
105
  pointerEvents: isCapturing ? "none" : "auto"
119
106
  },
120
107
  children: [
121
- !isCapturing && /* @__PURE__ */ jsx("div", { style: {
122
- position: "absolute",
123
- top: 16,
124
- left: "50%",
125
- transform: "translateX(-50%)",
126
- background: "rgba(0,0,0,0.75)",
127
- color: "#fff",
128
- padding: "8px 18px",
129
- borderRadius: 8,
130
- fontSize: 14,
131
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
132
- pointerEvents: "none",
133
- whiteSpace: "nowrap",
134
- backdropFilter: "blur(4px)"
135
- }, children: "Drag to highlight the problem area \xA0\xB7\xA0 Esc to cancel" }),
136
- rect && phase === "selecting" && /* @__PURE__ */ jsx("div", { style: {
137
- position: "absolute",
138
- left: rect.x,
139
- top: rect.y,
140
- width: rect.w,
141
- height: rect.h,
142
- background: "rgba(255,200,0,0.2)",
143
- border: "2px dashed #f97316",
144
- boxSizing: "border-box",
145
- pointerEvents: "none"
146
- } })
108
+ !isCapturing && /* @__PURE__ */ jsx(
109
+ "div",
110
+ {
111
+ style: {
112
+ position: "absolute",
113
+ top: 16,
114
+ left: "50%",
115
+ transform: "translateX(-50%)",
116
+ background: "rgba(0,0,0,0.75)",
117
+ color: "#fff",
118
+ padding: "8px 18px",
119
+ borderRadius: 8,
120
+ fontSize: 14,
121
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
122
+ pointerEvents: "none",
123
+ whiteSpace: "nowrap",
124
+ backdropFilter: "blur(4px)"
125
+ },
126
+ children: "Drag to highlight the problem area \xA0\xB7\xA0 Esc to cancel"
127
+ }
128
+ ),
129
+ rect && phase === "selecting" && /* @__PURE__ */ jsx(
130
+ "div",
131
+ {
132
+ style: {
133
+ position: "absolute",
134
+ left: rect.x,
135
+ top: rect.y,
136
+ width: rect.w,
137
+ height: rect.h,
138
+ background: "rgba(255,200,0,0.2)",
139
+ border: "2px dashed #f97316",
140
+ boxSizing: "border-box",
141
+ pointerEvents: "none"
142
+ }
143
+ }
144
+ )
147
145
  ]
148
146
  }
149
147
  );
150
148
  }
151
149
 
152
- // src/api.ts
153
- import { gzipSync } from "fflate";
154
- async function submitReport(serverUrl, projectKey, payload, screenshot) {
155
- const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports`;
156
- let body;
157
- let headers = {
158
- "x-project-key": projectKey
159
- };
160
- if (screenshot) {
161
- const form = new FormData();
162
- form.append("reporterId", payload.reporterId);
163
- form.append("reporterName", payload.reporterName);
164
- form.append("description", payload.description);
165
- if (payload.expectedBehavior) form.append("expectedBehavior", payload.expectedBehavior);
166
- if (payload.stepsToReproduce) form.append("stepsToReproduce", JSON.stringify(payload.stepsToReproduce));
167
- if (payload.externalReplayUrl) form.append("externalReplayUrl", payload.externalReplayUrl);
168
- if (payload.additionalContext) form.append("additionalContext", payload.additionalContext);
169
- form.append("severity", payload.severity);
170
- if (payload.url) form.append("url", payload.url);
171
- if (payload.meta) form.append("meta", JSON.stringify(payload.meta));
172
- form.append("screenshot", screenshot);
173
- body = form;
174
- } else {
175
- body = JSON.stringify(payload);
176
- headers["Content-Type"] = "application/json";
177
- }
178
- const res = await fetch(url, { method: "POST", headers, body });
179
- if (!res.ok) {
180
- const err = await res.json().catch(() => ({ error: "Unknown error" }));
181
- throw new Error(err.error ?? `HTTP ${res.status}`);
182
- }
183
- return res.json();
184
- }
185
- async function submitReplay(serverUrl, projectKey, reportId, events) {
186
- const json = JSON.stringify(events);
187
- const encoded = new TextEncoder().encode(json);
188
- const compressed = gzipSync(encoded);
189
- const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports/${reportId}/replay`;
190
- await fetch(url, {
191
- method: "POST",
192
- headers: {
193
- "x-project-key": projectKey,
194
- "Content-Type": "application/octet-stream"
195
- },
196
- body: compressed.buffer
197
- });
198
- }
199
-
200
150
  // src/theme.ts
201
- var light = {
202
- background: "rgba(255,255,255,0.90)",
203
- backgroundSecondary: "rgba(249,250,251,0.75)",
204
- accent: "#2563eb",
205
- accentHover: "#1d4ed8",
206
- text: "#111827",
207
- textMuted: "#6b7280",
208
- border: "rgba(255,255,255,0.9)",
209
- 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)",
210
- buttonText: "#ffffff",
211
- backdropFilter: "blur(32px) saturate(1.8)"
212
- };
213
- var dark = {
214
- background: "rgba(15,20,35,0.88)",
215
- backgroundSecondary: "rgba(5,8,18,0.65)",
216
- accent: "#f97316",
217
- accentHover: "#ea6c0a",
218
- text: "#dde3ef",
219
- textMuted: "#6b7a93",
220
- border: "rgba(255,255,255,0.08)",
221
- shadow: "0 24px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04)",
222
- buttonText: "#ffffff",
223
- backdropFilter: "blur(32px) saturate(1.6)"
224
- };
225
- function resolveTheme(theme) {
226
- if (theme === "dark") return dark;
227
- if (theme === "light") return light;
228
- const override = theme;
229
- return {
230
- ...light,
231
- background: override.background ?? light.background,
232
- accent: override.accent ?? light.accent,
233
- accentHover: override.accent ?? light.accentHover,
234
- text: override.text ?? light.text,
235
- border: override.border ?? light.border
236
- };
237
- }
151
+ import { resolveTheme } from "@flint/core";
238
152
 
239
153
  // src/FlintModal.tsx
240
154
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
@@ -294,7 +208,12 @@ function FlintModal({
294
208
  getNetworkErrors,
295
209
  getReplayEvents,
296
210
  getExternalReplayUrl,
297
- initialSelection = ""
211
+ initialSelection = "",
212
+ enableScreenshot = true,
213
+ statusPageUrl,
214
+ onBeforeSubmit,
215
+ onSuccess,
216
+ onError
298
217
  }) {
299
218
  const { t } = useTranslation();
300
219
  const colors = resolveTheme(theme);
@@ -354,33 +273,40 @@ function FlintModal({
354
273
  lang: textLang
355
274
  };
356
275
  }
276
+ const payload = {
277
+ reporterId: user?.id ?? "anonymous",
278
+ reporterName: user?.name ?? "Anonymous",
279
+ reporterEmail: user?.email,
280
+ description: isText ? `[Text issue] "${textOriginal.trim()}" \u2192 "${textSuggested.trim()}"` : description.trim(),
281
+ expectedBehavior: !isText ? expectedBehavior.trim() || void 0 : void 0,
282
+ externalReplayUrl: getExternalReplayUrl() || void 0,
283
+ severity: isText ? "P3" : severity,
284
+ url: window.location.href,
285
+ meta: collectedMeta,
286
+ label: isText ? "TEXT" : void 0
287
+ };
288
+ if (onBeforeSubmit) {
289
+ const proceed = await onBeforeSubmit(payload);
290
+ if (!proceed) {
291
+ setStatus("idle");
292
+ return;
293
+ }
294
+ }
357
295
  try {
358
- const res = await submitReport(
359
- serverUrl,
360
- projectKey,
361
- {
362
- reporterId: user?.id ?? "anonymous",
363
- reporterName: user?.name ?? "Anonymous",
364
- description: isText ? `[Text issue] "${textOriginal.trim()}" \u2192 "${textSuggested.trim()}"` : description.trim(),
365
- expectedBehavior: !isText ? expectedBehavior.trim() || void 0 : void 0,
366
- externalReplayUrl: getExternalReplayUrl() || void 0,
367
- severity: isText ? "P3" : severity,
368
- url: window.location.href,
369
- meta: collectedMeta,
370
- label: isText ? "TEXT" : void 0
371
- },
372
- !isText ? screenshot ?? void 0 : void 0
373
- );
296
+ const res = await submitReport(serverUrl, projectKey, payload, !isText ? screenshot ?? void 0 : void 0);
374
297
  setResult(res);
375
298
  setStatus("success");
299
+ onSuccess?.(res);
376
300
  const events = getReplayEvents();
377
301
  if (events.length > 0) {
378
302
  submitReplay(serverUrl, projectKey, res.id, events).catch(() => {
379
303
  });
380
304
  }
381
305
  } catch (err) {
382
- setErrorMsg(err instanceof Error ? err.message : t("errorLabel"));
306
+ const error = err instanceof Error ? err : new Error(t("errorLabel"));
307
+ setErrorMsg(error.message);
383
308
  setStatus("error");
309
+ onError?.(error);
384
310
  }
385
311
  };
386
312
  const inputBorder = isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
@@ -430,157 +356,249 @@ function FlintModal({
430
356
  const isSuccess = status === "success";
431
357
  const ringBorder = isSuccess ? "3px solid #22c55e" : `3px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)"}`;
432
358
  const ringTopColor = isSuccess ? "#22c55e" : colors.accent;
433
- return /* @__PURE__ */ jsx2("div", { style: overlayStyle, children: /* @__PURE__ */ jsxs2("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-label": isSuccess ? t("successTitle") : t("sending"), children: [
434
- /* @__PURE__ */ jsx2(ModalHeader, { colors, inputBorder, showClose: false, onClose }),
435
- /* @__PURE__ */ jsxs2("div", { style: { padding: "40px 32px 48px", textAlign: "center", display: "flex", flexDirection: "column", alignItems: "center" }, children: [
436
- /* @__PURE__ */ jsxs2("div", { style: { position: "relative", width: 80, height: 80, marginBottom: 28 }, children: [
437
- /* @__PURE__ */ jsx2("div", { style: {
438
- position: "absolute",
439
- inset: -10,
440
- borderRadius: "50%",
441
- border: `1.5px solid ${colors.accent}`,
442
- animation: "_flint_ripple 1.8s ease-out infinite",
443
- opacity: isSuccess ? 0 : 1,
444
- transition: "opacity 0.3s ease"
445
- } }),
446
- /* @__PURE__ */ jsx2("div", { style: {
447
- position: "absolute",
448
- inset: -10,
449
- borderRadius: "50%",
450
- border: `1.5px solid ${colors.accent}`,
451
- animation: "_flint_ripple 1.8s ease-out infinite 0.6s",
452
- opacity: isSuccess ? 0 : 1,
453
- transition: "opacity 0.3s ease"
454
- } }),
455
- /* @__PURE__ */ jsx2("div", { style: {
456
- position: "absolute",
457
- inset: 0,
458
- borderRadius: "50%",
459
- border: ringBorder,
460
- borderTopColor: ringTopColor,
461
- animation: isSuccess ? "none" : "_flint_spin 0.85s linear infinite",
462
- transition: "border-color 0.45s ease, border-top-color 0.45s ease"
463
- } }),
464
- /* @__PURE__ */ jsxs2("div", { style: { position: "absolute", inset: 14, borderRadius: "50%" }, children: [
465
- /* @__PURE__ */ jsx2("div", { style: {
466
- position: "absolute",
467
- inset: 0,
468
- borderRadius: "50%",
469
- background: `linear-gradient(135deg, ${colors.accent}30, ${colors.accentHover}50)`,
470
- display: "flex",
471
- alignItems: "center",
472
- justifyContent: "center",
473
- animation: isSuccess ? "none" : "_flint_pulse 2s ease-in-out infinite",
474
- opacity: isSuccess ? 0 : 1,
475
- transition: "opacity 0.3s ease"
476
- }, children: /* @__PURE__ */ jsx2(SparkIcon, { color: colors.accent, size: 20 }) }),
477
- /* @__PURE__ */ jsx2("div", { style: {
478
- position: "absolute",
479
- inset: 0,
480
- borderRadius: "50%",
481
- background: "rgba(34,197,94,0.15)",
482
- display: "flex",
483
- alignItems: "center",
484
- justifyContent: "center",
485
- opacity: isSuccess ? 1 : 0,
486
- transform: isSuccess ? "scale(1)" : "scale(0.65)",
487
- transition: "opacity 0.35s ease 0.2s, transform 0.4s cubic-bezier(0.16,1,0.3,1) 0.2s"
488
- }, children: /* @__PURE__ */ jsx2(CheckIcon, { size: 20 }) })
489
- ] })
490
- ] }),
491
- /* @__PURE__ */ jsxs2("div", { style: { position: "relative", height: 26, width: "100%", marginBottom: 10 }, children: [
492
- /* @__PURE__ */ jsx2("div", { style: {
493
- position: "absolute",
494
- inset: 0,
495
- display: "flex",
496
- alignItems: "center",
497
- justifyContent: "center",
498
- fontSize: 19,
499
- fontWeight: 700,
500
- color: colors.text,
501
- letterSpacing: "-0.02em",
502
- opacity: isSuccess ? 0 : 1,
503
- transition: "opacity 0.25s ease",
504
- pointerEvents: "none"
505
- }, children: t("sending") }),
506
- /* @__PURE__ */ jsx2("div", { style: {
507
- position: "absolute",
508
- inset: 0,
509
- display: "flex",
510
- alignItems: "center",
511
- justifyContent: "center",
512
- fontSize: 19,
513
- fontWeight: 700,
514
- color: colors.text,
515
- letterSpacing: "-0.02em",
516
- opacity: isSuccess ? 1 : 0,
517
- transform: isSuccess ? "translateY(0)" : "translateY(6px)",
518
- transition: "opacity 0.35s ease 0.25s, transform 0.35s ease 0.25s",
519
- pointerEvents: isSuccess ? "auto" : "none"
520
- }, children: t("successTitle") })
521
- ] }),
522
- /* @__PURE__ */ jsxs2("div", { style: { position: "relative", minHeight: 76, width: "100%" }, children: [
523
- /* @__PURE__ */ jsxs2("div", { style: {
524
- position: "absolute",
525
- inset: 0,
526
- display: "flex",
527
- alignItems: "center",
528
- justifyContent: "center",
529
- gap: 6,
530
- color: colors.textMuted,
531
- fontSize: 15,
532
- opacity: isSuccess ? 0 : 1,
533
- transition: "opacity 0.2s ease",
534
- pointerEvents: "none"
535
- }, children: [
536
- /* @__PURE__ */ jsx2("span", { children: t("capturingContext") }),
537
- /* @__PURE__ */ jsx2(SendingDots, { color: colors.accent })
538
- ] }),
539
- /* @__PURE__ */ jsxs2("div", { style: {
540
- position: "absolute",
541
- inset: 0,
542
- display: "flex",
543
- flexDirection: "column",
544
- alignItems: "center",
545
- gap: 10,
546
- opacity: isSuccess ? 1 : 0,
547
- transform: isSuccess ? "translateY(0)" : "translateY(8px)",
548
- transition: "opacity 0.35s ease 0.35s, transform 0.35s ease 0.35s",
549
- pointerEvents: isSuccess ? "auto" : "none"
550
- }, children: [
551
- /* @__PURE__ */ jsx2("p", { style: { fontSize: 15, color: colors.textMuted, margin: 0 }, children: result ? `ID: ${result.id}` : "" }),
552
- /* @__PURE__ */ jsx2(
553
- "button",
554
- {
555
- onClick: onClose,
556
- style: {
557
- width: "100%",
558
- padding: "13px 20px",
559
- borderRadius: 12,
560
- border: "none",
561
- background: `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})`,
562
- color: colors.buttonText,
563
- fontSize: 17,
564
- fontWeight: 700,
565
- cursor: "pointer",
566
- letterSpacing: "-0.01em",
567
- boxShadow: accentGlow,
568
- fontFamily: "inherit",
569
- display: "flex",
570
- alignItems: "center",
571
- justifyContent: "center",
572
- gap: 8
573
- },
574
- children: t("close")
575
- }
576
- )
577
- ] })
578
- ] })
579
- ] })
580
- ] }) });
359
+ return /* @__PURE__ */ jsx2("div", { style: overlayStyle, children: /* @__PURE__ */ jsxs2(
360
+ "div",
361
+ {
362
+ style: modalStyle,
363
+ role: "dialog",
364
+ "aria-modal": "true",
365
+ "aria-label": isSuccess ? t("successTitle") : t("sending"),
366
+ children: [
367
+ /* @__PURE__ */ jsx2(ModalHeader, { colors, inputBorder, showClose: false, onClose }),
368
+ /* @__PURE__ */ jsxs2(
369
+ "div",
370
+ {
371
+ style: {
372
+ padding: "40px 32px 48px",
373
+ textAlign: "center",
374
+ display: "flex",
375
+ flexDirection: "column",
376
+ alignItems: "center"
377
+ },
378
+ children: [
379
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative", width: 80, height: 80, marginBottom: 28 }, children: [
380
+ /* @__PURE__ */ jsx2(
381
+ "div",
382
+ {
383
+ style: {
384
+ position: "absolute",
385
+ inset: -10,
386
+ borderRadius: "50%",
387
+ border: `1.5px solid ${colors.accent}`,
388
+ animation: "_flint_ripple 1.8s ease-out infinite",
389
+ opacity: isSuccess ? 0 : 1,
390
+ transition: "opacity 0.3s ease"
391
+ }
392
+ }
393
+ ),
394
+ /* @__PURE__ */ jsx2(
395
+ "div",
396
+ {
397
+ style: {
398
+ position: "absolute",
399
+ inset: -10,
400
+ borderRadius: "50%",
401
+ border: `1.5px solid ${colors.accent}`,
402
+ animation: "_flint_ripple 1.8s ease-out infinite 0.6s",
403
+ opacity: isSuccess ? 0 : 1,
404
+ transition: "opacity 0.3s ease"
405
+ }
406
+ }
407
+ ),
408
+ /* @__PURE__ */ jsx2(
409
+ "div",
410
+ {
411
+ style: {
412
+ position: "absolute",
413
+ inset: 0,
414
+ borderRadius: "50%",
415
+ border: ringBorder,
416
+ borderTopColor: ringTopColor,
417
+ animation: isSuccess ? "none" : "_flint_spin 0.85s linear infinite",
418
+ transition: "border-color 0.45s ease, border-top-color 0.45s ease"
419
+ }
420
+ }
421
+ ),
422
+ /* @__PURE__ */ jsxs2("div", { style: { position: "absolute", inset: 14, borderRadius: "50%" }, children: [
423
+ /* @__PURE__ */ jsx2(
424
+ "div",
425
+ {
426
+ style: {
427
+ position: "absolute",
428
+ inset: 0,
429
+ borderRadius: "50%",
430
+ background: `linear-gradient(135deg, ${colors.accent}30, ${colors.accentHover}50)`,
431
+ display: "flex",
432
+ alignItems: "center",
433
+ justifyContent: "center",
434
+ animation: isSuccess ? "none" : "_flint_pulse 2s ease-in-out infinite",
435
+ opacity: isSuccess ? 0 : 1,
436
+ transition: "opacity 0.3s ease"
437
+ },
438
+ children: /* @__PURE__ */ jsx2(SparkIcon, { color: colors.accent, size: 20 })
439
+ }
440
+ ),
441
+ /* @__PURE__ */ jsx2(
442
+ "div",
443
+ {
444
+ style: {
445
+ position: "absolute",
446
+ inset: 0,
447
+ borderRadius: "50%",
448
+ background: "rgba(34,197,94,0.15)",
449
+ display: "flex",
450
+ alignItems: "center",
451
+ justifyContent: "center",
452
+ opacity: isSuccess ? 1 : 0,
453
+ transform: isSuccess ? "scale(1)" : "scale(0.65)",
454
+ transition: "opacity 0.35s ease 0.2s, transform 0.4s cubic-bezier(0.16,1,0.3,1) 0.2s"
455
+ },
456
+ children: /* @__PURE__ */ jsx2(CheckIcon, { size: 20 })
457
+ }
458
+ )
459
+ ] })
460
+ ] }),
461
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative", height: 26, width: "100%", marginBottom: 10 }, children: [
462
+ /* @__PURE__ */ jsx2(
463
+ "div",
464
+ {
465
+ style: {
466
+ position: "absolute",
467
+ inset: 0,
468
+ display: "flex",
469
+ alignItems: "center",
470
+ justifyContent: "center",
471
+ fontSize: 19,
472
+ fontWeight: 700,
473
+ color: colors.text,
474
+ letterSpacing: "-0.02em",
475
+ opacity: isSuccess ? 0 : 1,
476
+ transition: "opacity 0.25s ease",
477
+ pointerEvents: "none"
478
+ },
479
+ children: t("sending")
480
+ }
481
+ ),
482
+ /* @__PURE__ */ jsx2(
483
+ "div",
484
+ {
485
+ style: {
486
+ position: "absolute",
487
+ inset: 0,
488
+ display: "flex",
489
+ alignItems: "center",
490
+ justifyContent: "center",
491
+ fontSize: 19,
492
+ fontWeight: 700,
493
+ color: colors.text,
494
+ letterSpacing: "-0.02em",
495
+ opacity: isSuccess ? 1 : 0,
496
+ transform: isSuccess ? "translateY(0)" : "translateY(6px)",
497
+ transition: "opacity 0.35s ease 0.25s, transform 0.35s ease 0.25s",
498
+ pointerEvents: isSuccess ? "auto" : "none"
499
+ },
500
+ children: t("successTitle")
501
+ }
502
+ )
503
+ ] }),
504
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative", minHeight: 76, width: "100%" }, children: [
505
+ /* @__PURE__ */ jsxs2(
506
+ "div",
507
+ {
508
+ style: {
509
+ position: "absolute",
510
+ inset: 0,
511
+ display: "flex",
512
+ alignItems: "center",
513
+ justifyContent: "center",
514
+ gap: 6,
515
+ color: colors.textMuted,
516
+ fontSize: 15,
517
+ opacity: isSuccess ? 0 : 1,
518
+ transition: "opacity 0.2s ease",
519
+ pointerEvents: "none"
520
+ },
521
+ children: [
522
+ /* @__PURE__ */ jsx2("span", { children: t("capturingContext") }),
523
+ /* @__PURE__ */ jsx2(SendingDots, { color: colors.accent })
524
+ ]
525
+ }
526
+ ),
527
+ /* @__PURE__ */ jsxs2(
528
+ "div",
529
+ {
530
+ style: {
531
+ position: "absolute",
532
+ inset: 0,
533
+ display: "flex",
534
+ flexDirection: "column",
535
+ alignItems: "center",
536
+ gap: 10,
537
+ opacity: isSuccess ? 1 : 0,
538
+ transform: isSuccess ? "translateY(0)" : "translateY(8px)",
539
+ transition: "opacity 0.35s ease 0.35s, transform 0.35s ease 0.35s",
540
+ pointerEvents: isSuccess ? "auto" : "none"
541
+ },
542
+ children: [
543
+ /* @__PURE__ */ jsx2("p", { style: { fontSize: 15, color: colors.textMuted, margin: 0 }, children: result ? `ID: ${result.id}` : "" }),
544
+ statusPageUrl && user?.id && /* @__PURE__ */ jsxs2(
545
+ "a",
546
+ {
547
+ href: `${statusPageUrl}/status?project_key=${encodeURIComponent(projectKey)}&reporter_id=${encodeURIComponent(user.id)}&server_url=${encodeURIComponent(serverUrl)}`,
548
+ target: "_blank",
549
+ rel: "noreferrer",
550
+ style: {
551
+ fontSize: 14,
552
+ color: colors.accent,
553
+ textDecoration: "none",
554
+ fontWeight: 600,
555
+ animation: "_flint_success_up 0.35s ease 0.4s both"
556
+ },
557
+ children: [
558
+ "\u{1F4CB}",
559
+ " Track your bugs ",
560
+ "\u2192"
561
+ ]
562
+ }
563
+ ),
564
+ /* @__PURE__ */ jsx2(
565
+ "button",
566
+ {
567
+ onClick: onClose,
568
+ style: {
569
+ width: "100%",
570
+ padding: "13px 20px",
571
+ borderRadius: 12,
572
+ border: "none",
573
+ background: `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})`,
574
+ color: colors.buttonText,
575
+ fontSize: 17,
576
+ fontWeight: 700,
577
+ cursor: "pointer",
578
+ letterSpacing: "-0.01em",
579
+ boxShadow: accentGlow,
580
+ fontFamily: "inherit",
581
+ display: "flex",
582
+ alignItems: "center",
583
+ justifyContent: "center",
584
+ gap: 8
585
+ },
586
+ children: t("close")
587
+ }
588
+ )
589
+ ]
590
+ }
591
+ )
592
+ ] })
593
+ ]
594
+ }
595
+ )
596
+ ]
597
+ }
598
+ ) });
581
599
  }
582
600
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
583
- annotating && /* @__PURE__ */ jsx2(
601
+ enableScreenshot && annotating && /* @__PURE__ */ jsx2(
584
602
  ScreenAnnotator,
585
603
  {
586
604
  zIndex: zIndex + 1,
@@ -604,37 +622,43 @@ function FlintModal({
604
622
  }
605
623
  ),
606
624
  /* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, style: { padding: "20px 24px 24px" }, children: [
607
- /* @__PURE__ */ jsx2("div", { style: {
608
- display: "flex",
609
- gap: 4,
610
- marginBottom: 20,
611
- background: colors.backgroundSecondary,
612
- borderRadius: 12,
613
- padding: 4,
614
- border: `1px solid ${inputBorder}`
615
- }, children: ["bug", "text"].map((m) => /* @__PURE__ */ jsx2(
616
- "button",
625
+ /* @__PURE__ */ jsx2(
626
+ "div",
617
627
  {
618
- type: "button",
619
- onClick: () => setMode(m),
620
628
  style: {
621
- flex: 1,
622
- padding: "8px 10px",
623
- borderRadius: 9,
624
- border: "none",
625
- cursor: "pointer",
626
- fontSize: 13,
627
- fontWeight: mode === m ? 700 : 500,
628
- fontFamily: "inherit",
629
- transition: "background 0.15s, color 0.15s",
630
- background: mode === m ? `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})` : "transparent",
631
- color: mode === m ? colors.buttonText : colors.textMuted,
632
- boxShadow: mode === m ? `0 2px 8px ${colors.accent}30` : "none"
629
+ display: "flex",
630
+ gap: 4,
631
+ marginBottom: 20,
632
+ background: colors.backgroundSecondary,
633
+ borderRadius: 12,
634
+ padding: 4,
635
+ border: `1px solid ${inputBorder}`
633
636
  },
634
- children: m === "bug" ? "\u{1F41B} Bug" : "\u{1F524} Text / Translation"
635
- },
636
- m
637
- )) }),
637
+ children: ["bug", "text"].map((m) => /* @__PURE__ */ jsx2(
638
+ "button",
639
+ {
640
+ type: "button",
641
+ onClick: () => setMode(m),
642
+ style: {
643
+ flex: 1,
644
+ padding: "8px 10px",
645
+ borderRadius: 9,
646
+ border: "none",
647
+ cursor: "pointer",
648
+ fontSize: 13,
649
+ fontWeight: mode === m ? 700 : 500,
650
+ fontFamily: "inherit",
651
+ transition: "background 0.15s, color 0.15s",
652
+ background: mode === m ? `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})` : "transparent",
653
+ color: mode === m ? colors.buttonText : colors.textMuted,
654
+ boxShadow: mode === m ? `0 2px 8px ${colors.accent}30` : "none"
655
+ },
656
+ children: m === "bug" ? "\u{1F41B} Bug" : "\u{1F524} Text / Translation"
657
+ },
658
+ m
659
+ ))
660
+ }
661
+ ),
638
662
  mode === "text" && /* @__PURE__ */ jsxs2(Fragment, { children: [
639
663
  /* @__PURE__ */ jsxs2("div", { style: { marginBottom: 14 }, children: [
640
664
  /* @__PURE__ */ jsx2(FieldLabel, { colors, htmlFor: "flint-text-original", children: "Original text" }),
@@ -731,7 +755,7 @@ function FlintModal({
731
755
  }
732
756
  )
733
757
  ] }),
734
- /* @__PURE__ */ jsxs2("div", { style: { marginBottom: 20, display: mode === "text" ? "none" : void 0 }, children: [
758
+ /* @__PURE__ */ jsxs2("div", { style: { marginBottom: 20, display: mode === "text" || !enableScreenshot ? "none" : void 0 }, children: [
735
759
  /* @__PURE__ */ jsx2(FieldLabel, { colors, children: t("screenshotLabel") }),
736
760
  screenshot ? /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
737
761
  /* @__PURE__ */ jsx2(
@@ -742,7 +766,20 @@ function FlintModal({
742
766
  style: { height: 60, borderRadius: 8, objectFit: "cover", border: `1px solid ${inputBorder}` }
743
767
  }
744
768
  ),
745
- /* @__PURE__ */ jsx2("span", { style: { fontSize: 13, color: colors.textMuted, flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: screenshot.name }),
769
+ /* @__PURE__ */ jsx2(
770
+ "span",
771
+ {
772
+ style: {
773
+ fontSize: 13,
774
+ color: colors.textMuted,
775
+ flex: 1,
776
+ overflow: "hidden",
777
+ textOverflow: "ellipsis",
778
+ whiteSpace: "nowrap"
779
+ },
780
+ children: screenshot.name
781
+ }
782
+ ),
746
783
  /* @__PURE__ */ jsx2(
747
784
  "button",
748
785
  {
@@ -829,31 +866,43 @@ function FlintModal({
829
866
  }
830
867
  )
831
868
  ] }),
832
- /* @__PURE__ */ jsxs2("div", { style: {
833
- display: "flex",
834
- alignItems: "center",
835
- gap: 8,
836
- padding: "9px 12px",
837
- borderRadius: 10,
838
- background: isDark ? "rgba(255,255,255,0.04)" : "rgba(0,77,240,0.04)",
839
- border: `1px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,77,240,0.1)"}`,
840
- marginBottom: 16
841
- }, children: [
842
- /* @__PURE__ */ jsx2("span", { style: { fontSize: 16 }, children: "\u{1F3A5}" }),
843
- /* @__PURE__ */ jsx2("span", { style: { fontSize: 14, color: colors.textMuted, lineHeight: 1.4 }, children: t("replayInfo") })
844
- ] }),
845
- status === "error" && /* @__PURE__ */ jsxs2("div", { style: {
846
- padding: "10px 13px",
847
- borderRadius: 10,
848
- background: "rgba(239,68,68,0.08)",
849
- border: "1px solid rgba(239,68,68,0.2)",
850
- color: "#f87171",
851
- fontSize: 14,
852
- marginBottom: 16
853
- }, children: [
854
- "\u26A0\uFE0F ",
855
- errorMsg || t("errorLabel")
856
- ] }),
869
+ /* @__PURE__ */ jsxs2(
870
+ "div",
871
+ {
872
+ style: {
873
+ display: "flex",
874
+ alignItems: "center",
875
+ gap: 8,
876
+ padding: "9px 12px",
877
+ borderRadius: 10,
878
+ background: isDark ? "rgba(255,255,255,0.04)" : "rgba(0,77,240,0.04)",
879
+ border: `1px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,77,240,0.1)"}`,
880
+ marginBottom: 16
881
+ },
882
+ children: [
883
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 16 }, children: "\u{1F3A5}" }),
884
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 14, color: colors.textMuted, lineHeight: 1.4 }, children: t("replayInfo") })
885
+ ]
886
+ }
887
+ ),
888
+ status === "error" && /* @__PURE__ */ jsxs2(
889
+ "div",
890
+ {
891
+ style: {
892
+ padding: "10px 13px",
893
+ borderRadius: 10,
894
+ background: "rgba(239,68,68,0.08)",
895
+ border: "1px solid rgba(239,68,68,0.2)",
896
+ color: "#f87171",
897
+ fontSize: 14,
898
+ marginBottom: 16
899
+ },
900
+ children: [
901
+ "\u26A0\uFE0F ",
902
+ errorMsg || t("errorLabel")
903
+ ]
904
+ }
905
+ ),
857
906
  /* @__PURE__ */ jsxs2(
858
907
  "button",
859
908
  {
@@ -914,49 +963,68 @@ function ModalHeader({
914
963
  titleId,
915
964
  title
916
965
  }) {
917
- return /* @__PURE__ */ jsxs2("div", { style: {
918
- display: "flex",
919
- alignItems: "center",
920
- gap: 10,
921
- padding: "16px 20px 14px",
922
- borderBottom: `1px solid ${inputBorder}`
923
- }, children: [
924
- /* @__PURE__ */ jsx2("div", { style: {
925
- width: 28,
926
- height: 28,
927
- borderRadius: 8,
928
- background: `linear-gradient(135deg, ${colors.accent}20, ${colors.accentHover}35)`,
929
- border: `1px solid ${colors.accent}30`,
930
- display: "flex",
931
- alignItems: "center",
932
- justifyContent: "center",
933
- flexShrink: 0
934
- }, children: /* @__PURE__ */ jsx2(SparkIcon, { color: colors.accent, size: 13 }) }),
935
- titleId && title ? /* @__PURE__ */ jsx2("h2", { id: titleId, style: { margin: 0, fontSize: 16, fontWeight: 600, color: colors.text, letterSpacing: "-0.01em", flex: 1 }, children: title }) : /* @__PURE__ */ jsx2("span", { style: { flex: 1, fontSize: 15, fontWeight: 600, color: colors.textMuted }, children: "Flint" }),
936
- showClose && /* @__PURE__ */ jsx2(
937
- "button",
938
- {
939
- onClick: onClose,
940
- "aria-label": "Close",
941
- style: {
942
- background: "none",
943
- border: "none",
944
- cursor: "pointer",
945
- padding: 4,
946
- color: colors.textMuted,
947
- fontSize: 22,
948
- lineHeight: 1,
949
- borderRadius: 6,
950
- display: "flex",
951
- alignItems: "center",
952
- justifyContent: "center",
953
- opacity: 0.6,
954
- fontFamily: "inherit"
955
- },
956
- children: "\xD7"
957
- }
958
- )
959
- ] });
966
+ return /* @__PURE__ */ jsxs2(
967
+ "div",
968
+ {
969
+ style: {
970
+ display: "flex",
971
+ alignItems: "center",
972
+ gap: 10,
973
+ padding: "16px 20px 14px",
974
+ borderBottom: `1px solid ${inputBorder}`
975
+ },
976
+ children: [
977
+ /* @__PURE__ */ jsx2(
978
+ "div",
979
+ {
980
+ style: {
981
+ width: 28,
982
+ height: 28,
983
+ borderRadius: 8,
984
+ background: `linear-gradient(135deg, ${colors.accent}20, ${colors.accentHover}35)`,
985
+ border: `1px solid ${colors.accent}30`,
986
+ display: "flex",
987
+ alignItems: "center",
988
+ justifyContent: "center",
989
+ flexShrink: 0
990
+ },
991
+ children: /* @__PURE__ */ jsx2(SparkIcon, { color: colors.accent, size: 13 })
992
+ }
993
+ ),
994
+ titleId && title ? /* @__PURE__ */ jsx2(
995
+ "h2",
996
+ {
997
+ id: titleId,
998
+ style: { margin: 0, fontSize: 16, fontWeight: 600, color: colors.text, letterSpacing: "-0.01em", flex: 1 },
999
+ children: title
1000
+ }
1001
+ ) : /* @__PURE__ */ jsx2("span", { style: { flex: 1, fontSize: 15, fontWeight: 600, color: colors.textMuted }, children: "Flint" }),
1002
+ showClose && /* @__PURE__ */ jsx2(
1003
+ "button",
1004
+ {
1005
+ onClick: onClose,
1006
+ "aria-label": "Close",
1007
+ style: {
1008
+ background: "none",
1009
+ border: "none",
1010
+ cursor: "pointer",
1011
+ padding: 4,
1012
+ color: colors.textMuted,
1013
+ fontSize: 22,
1014
+ lineHeight: 1,
1015
+ borderRadius: 6,
1016
+ display: "flex",
1017
+ alignItems: "center",
1018
+ justifyContent: "center",
1019
+ opacity: 0.6,
1020
+ fontFamily: "inherit"
1021
+ },
1022
+ children: "\xD7"
1023
+ }
1024
+ )
1025
+ ]
1026
+ }
1027
+ );
960
1028
  }
961
1029
  function FieldLabel({
962
1030
  children,
@@ -1018,19 +1086,71 @@ function SeverityButton({ sev, label, selected, hint, color, accent, border, bg,
1018
1086
  },
1019
1087
  children: [
1020
1088
  /* @__PURE__ */ jsx2("span", { style: { width: 8, height: 8, borderRadius: "50%", background: color, display: "block" } }),
1021
- /* @__PURE__ */ jsx2("span", { style: { fontSize: 13, fontWeight: selected ? 700 : 500, color: selected ? accent : text, letterSpacing: "0.02em" }, children: sev }),
1089
+ /* @__PURE__ */ jsx2(
1090
+ "span",
1091
+ {
1092
+ style: {
1093
+ fontSize: 13,
1094
+ fontWeight: selected ? 700 : 500,
1095
+ color: selected ? accent : text,
1096
+ letterSpacing: "0.02em"
1097
+ },
1098
+ children: sev
1099
+ }
1100
+ ),
1022
1101
  /* @__PURE__ */ jsx2("span", { style: { fontSize: 11, color: selected ? accent : text, opacity: 0.6, letterSpacing: "0.02em" }, children: label })
1023
1102
  ]
1024
1103
  }
1025
1104
  );
1026
1105
  }
1027
1106
  function SparkIcon({ color = "currentColor", size = 14 }) {
1028
- return /* @__PURE__ */ jsx2("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__ */ jsx2("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" }) });
1107
+ return /* @__PURE__ */ jsx2(
1108
+ "svg",
1109
+ {
1110
+ width: size,
1111
+ height: size,
1112
+ viewBox: "0 0 24 24",
1113
+ fill: "none",
1114
+ stroke: color,
1115
+ strokeWidth: "2.2",
1116
+ strokeLinecap: "round",
1117
+ strokeLinejoin: "round",
1118
+ "aria-hidden": "true",
1119
+ children: /* @__PURE__ */ jsx2("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" })
1120
+ }
1121
+ );
1029
1122
  }
1030
1123
  function CheckIcon({ size = 20 }) {
1031
- return /* @__PURE__ */ jsx2("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__ */ jsx2("polyline", { points: "20 6 9 17 4 12" }) });
1124
+ return /* @__PURE__ */ jsx2(
1125
+ "svg",
1126
+ {
1127
+ width: size,
1128
+ height: size,
1129
+ viewBox: "0 0 24 24",
1130
+ fill: "none",
1131
+ stroke: "#22c55e",
1132
+ strokeWidth: "2.5",
1133
+ strokeLinecap: "round",
1134
+ strokeLinejoin: "round",
1135
+ "aria-hidden": "true",
1136
+ children: /* @__PURE__ */ jsx2("polyline", { points: "20 6 9 17 4 12" })
1137
+ }
1138
+ );
1032
1139
  }
1033
1140
 
1141
+ // src/FlintWidget.tsx
1142
+ import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
1143
+ import { I18nextProvider, useTranslation as useTranslation2 } from "react-i18next";
1144
+
1145
+ // src/collectors/console.ts
1146
+ import { createConsoleCollector } from "@flint/core";
1147
+
1148
+ // src/collectors/environment.ts
1149
+ import { collectEnvironment } from "@flint/core";
1150
+
1151
+ // src/collectors/network.ts
1152
+ import { createNetworkCollector } from "@flint/core";
1153
+
1034
1154
  // src/i18n/index.ts
1035
1155
  import { createInstance } from "i18next";
1036
1156
  import { initReactI18next } from "react-i18next";
@@ -1080,251 +1200,15 @@ widgetI18n.use(initReactI18next).init({
1080
1200
  });
1081
1201
  var i18n_default = widgetI18n;
1082
1202
 
1083
- // src/collectors/environment.ts
1084
- function collectEnvironment() {
1085
- const ua = navigator.userAgent;
1086
- let browser = "Unknown";
1087
- const chromeM = ua.match(/Chrome\/(\d+)/);
1088
- const firefoxM = ua.match(/Firefox\/(\d+)/);
1089
- const edgeM = ua.match(/Edg\/(\d+)/);
1090
- const safariM = ua.match(/Version\/(\d+)/);
1091
- const operaM = ua.match(/OPR\/(\d+)/);
1092
- if (operaM) {
1093
- browser = `Opera ${operaM[1]}`;
1094
- } else if (edgeM) {
1095
- browser = `Edge ${edgeM[1]}`;
1096
- } else if (chromeM && !/Edg|OPR/.test(ua)) {
1097
- browser = `Chrome ${chromeM[1]}`;
1098
- } else if (firefoxM) {
1099
- browser = `Firefox ${firefoxM[1]}`;
1100
- } else if (safariM && /Safari\//.test(ua)) {
1101
- browser = `Safari ${safariM[1]}`;
1102
- }
1103
- let os = "Unknown";
1104
- const macM = ua.match(/Mac OS X (\d+[._]\d+)/);
1105
- const winM = ua.match(/Windows NT (\d+\.\d+)/);
1106
- const androidM = ua.match(/Android (\d+)/);
1107
- const iosM = ua.match(/iPhone OS (\d+[._]\d+)/);
1108
- if (macM) {
1109
- os = `macOS ${macM[1].replace("_", ".")}`;
1110
- } else if (winM) {
1111
- const winMap = {
1112
- "10.0": "10/11",
1113
- "6.3": "8.1",
1114
- "6.2": "8",
1115
- "6.1": "7"
1116
- };
1117
- os = `Windows ${winMap[winM[1]] ?? winM[1]}`;
1118
- } else if (androidM) {
1119
- os = `Android ${androidM[1]}`;
1120
- } else if (iosM) {
1121
- os = `iOS ${iosM[1].replace(/_/g, ".")}`;
1122
- } else if (/Linux/.test(ua)) {
1123
- os = "Linux";
1124
- }
1125
- return {
1126
- browser,
1127
- os,
1128
- viewport: `${window.innerWidth}x${window.innerHeight}`,
1129
- screen: `${screen.width}x${screen.height}`,
1130
- language: navigator.language,
1131
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
1132
- online: navigator.onLine
1133
- };
1134
- }
1135
-
1136
- // src/collectors/console.ts
1137
- var MAX_ENTRIES = 50;
1138
- function createConsoleCollector() {
1139
- const entries = [];
1140
- let active = false;
1141
- const originals = {
1142
- log: console.log.bind(console),
1143
- warn: console.warn.bind(console),
1144
- error: console.error.bind(console)
1145
- };
1146
- let origOnerror = null;
1147
- let origUnhandled = null;
1148
- function push(level, args) {
1149
- let str;
1150
- try {
1151
- str = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
1152
- } catch {
1153
- str = String(args[0]);
1154
- }
1155
- if (str.length > 500) str = str.slice(0, 500) + "\u2026";
1156
- entries.push({ level, args: str, timestamp: Date.now() });
1157
- if (entries.length > MAX_ENTRIES) entries.shift();
1158
- }
1159
- return {
1160
- start() {
1161
- if (active) return;
1162
- active = true;
1163
- console.log = (...args) => {
1164
- push("log", args);
1165
- originals.log(...args);
1166
- };
1167
- console.warn = (...args) => {
1168
- push("warn", args);
1169
- originals.warn(...args);
1170
- };
1171
- console.error = (...args) => {
1172
- push("error", args);
1173
- originals.error(...args);
1174
- };
1175
- origOnerror = window.onerror;
1176
- window.onerror = (msg, src, line, col, err) => {
1177
- push("error", [err?.message ?? String(msg), `${src}:${line}:${col}`]);
1178
- if (typeof origOnerror === "function")
1179
- return origOnerror(msg, src, line, col, err);
1180
- return false;
1181
- };
1182
- origUnhandled = window.onunhandledrejection;
1183
- window.onunhandledrejection = (event) => {
1184
- const reason = event.reason instanceof Error ? event.reason.message : JSON.stringify(event.reason);
1185
- push("error", ["UnhandledRejection:", reason]);
1186
- if (typeof origUnhandled === "function")
1187
- origUnhandled.call(window, event);
1188
- };
1189
- },
1190
- stop() {
1191
- if (!active) return;
1192
- active = false;
1193
- console.log = originals.log;
1194
- console.warn = originals.warn;
1195
- console.error = originals.error;
1196
- window.onerror = origOnerror;
1197
- window.onunhandledrejection = origUnhandled;
1198
- },
1199
- getEntries() {
1200
- return [...entries];
1201
- }
1202
- };
1203
- }
1204
-
1205
- // src/collectors/network.ts
1206
- var MAX_ENTRIES2 = 20;
1207
- var BLOCKED_HOSTS = /* @__PURE__ */ new Set([
1208
- "browser-intake-datadoghq.com",
1209
- "rum.browser-intake-datadoghq.com",
1210
- "logs.browser-intake-datadoghq.com",
1211
- "session-replay.browser-intake-datadoghq.com"
1212
- ]);
1213
- function isBlockedUrl(url, extra) {
1214
- try {
1215
- const host = new URL(url, location.href).hostname;
1216
- const all = [...BLOCKED_HOSTS, ...extra];
1217
- return all.some((b) => host === b || host.endsWith("." + b));
1218
- } catch {
1219
- return false;
1220
- }
1221
- }
1222
- function truncateUrl(url) {
1223
- try {
1224
- const u = new URL(url, location.href);
1225
- const base = `${u.origin}${u.pathname}`;
1226
- return base.length > 200 ? base.slice(0, 200) + "\u2026" : base;
1227
- } catch {
1228
- return url.length > 200 ? url.slice(0, 200) + "\u2026" : url;
1229
- }
1230
- }
1231
- function createNetworkCollector(extraBlockedHosts = []) {
1232
- const entries = [];
1233
- const blocked = new Set(extraBlockedHosts);
1234
- let origFetch = null;
1235
- let origXHROpen = null;
1236
- let active = false;
1237
- function push(entry) {
1238
- entries.push(entry);
1239
- if (entries.length > MAX_ENTRIES2) entries.shift();
1240
- }
1241
- return {
1242
- start() {
1243
- if (active) return;
1244
- active = true;
1245
- origFetch = window.fetch;
1246
- window.fetch = async (input, init) => {
1247
- const method = (init?.method ?? "GET").toUpperCase();
1248
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
1249
- const startTime = Date.now();
1250
- const res = await origFetch.call(window, input, init);
1251
- if (!isBlockedUrl(url, blocked)) {
1252
- push({
1253
- method,
1254
- url: truncateUrl(url),
1255
- status: res.status,
1256
- duration: Date.now() - startTime,
1257
- timestamp: startTime
1258
- });
1259
- }
1260
- return res;
1261
- };
1262
- origXHROpen = XMLHttpRequest.prototype.open;
1263
- XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
1264
- const startTime = Date.now();
1265
- const urlStr = typeof url === "string" ? url : url.href;
1266
- this.addEventListener("load", () => {
1267
- if (!isBlockedUrl(urlStr, blocked)) {
1268
- push({
1269
- method: method.toUpperCase(),
1270
- url: truncateUrl(urlStr),
1271
- status: this.status,
1272
- duration: Date.now() - startTime,
1273
- timestamp: startTime
1274
- });
1275
- }
1276
- });
1277
- return origXHROpen.apply(this, [
1278
- method,
1279
- url,
1280
- async ?? true,
1281
- username,
1282
- password
1283
- ]);
1284
- };
1285
- },
1286
- stop() {
1287
- if (!active) return;
1288
- active = false;
1289
- if (origFetch) window.fetch = origFetch;
1290
- if (origXHROpen) XMLHttpRequest.prototype.open = origXHROpen;
1291
- },
1292
- getEntries() {
1293
- return [...entries];
1294
- }
1295
- };
1296
- }
1297
-
1298
1203
  // src/store.ts
1204
+ import { getSnapshot, subscribe } from "@flint/core";
1299
1205
  import { useSyncExternalStore } from "react";
1300
- var state = { user: void 0, sessionReplay: void 0 };
1301
- var listeners = /* @__PURE__ */ new Set();
1302
- function emit() {
1303
- listeners.forEach((fn) => fn());
1304
- }
1305
- function subscribeStore(fn) {
1306
- listeners.add(fn);
1307
- return () => listeners.delete(fn);
1308
- }
1309
- function getSnapshot() {
1310
- return state;
1311
- }
1206
+ import { flint as flint2 } from "@flint/core";
1312
1207
  function useFlintStore() {
1313
- return useSyncExternalStore(subscribeStore, getSnapshot);
1208
+ return useSyncExternalStore(subscribe, getSnapshot);
1314
1209
  }
1315
- var flint = {
1316
- setUser(user) {
1317
- state = { ...state, user: user ?? void 0 };
1318
- emit();
1319
- },
1320
- setSessionReplay(url) {
1321
- state = { ...state, sessionReplay: url };
1322
- emit();
1323
- }
1324
- };
1325
1210
 
1326
1211
  // src/FlintWidget.tsx
1327
- import { record } from "rrweb";
1328
1212
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1329
1213
  var REPLAY_WINDOW_MS = 6e4;
1330
1214
  function FlintWidget(props) {
@@ -1342,14 +1226,40 @@ function WidgetContent({
1342
1226
  extraFields,
1343
1227
  buttonLabel,
1344
1228
  theme = "dark",
1345
- zIndex = 9999
1229
+ zIndex = 9999,
1230
+ statusPageUrl,
1231
+ datadogSite,
1232
+ enableReplay = true,
1233
+ enableScreenshot = true,
1234
+ enableConsole = true,
1235
+ enableNetwork = true,
1236
+ onBeforeSubmit,
1237
+ onSuccess,
1238
+ onError,
1239
+ onOpen,
1240
+ onClose
1346
1241
  }) {
1347
1242
  const globalState = useFlintStore();
1348
1243
  const resolvedUser = user ?? globalState.user;
1349
1244
  const resolvedSessionReplay = extraFields?.sessionReplay ?? globalState.sessionReplay;
1350
1245
  const getExternalReplayUrl = () => {
1351
1246
  const src = resolvedSessionReplay;
1352
- return typeof src === "function" ? src() : src;
1247
+ const explicit = typeof src === "function" ? src() : src;
1248
+ if (explicit) return explicit;
1249
+ if (datadogSite) {
1250
+ try {
1251
+ const ddRum = window.DD_RUM;
1252
+ const ctx = ddRum?.getInternalContext?.();
1253
+ if (ctx?.session_id) {
1254
+ const now = Math.floor(Date.now() / 1e3);
1255
+ const fromTs = now - 30;
1256
+ const toTs = now + 5;
1257
+ return `https://${datadogSite}/rum/replay/sessions/${ctx.session_id}?from_ts=${fromTs}&to_ts=${toTs}&tab=replay&live=false`;
1258
+ }
1259
+ } catch {
1260
+ }
1261
+ }
1262
+ return void 0;
1353
1263
  };
1354
1264
  const { t } = useTranslation2();
1355
1265
  const [open, setOpen] = useState3(false);
@@ -1396,16 +1306,17 @@ function WidgetContent({
1396
1306
  pendingSelection.current = text;
1397
1307
  setSelectionTooltip(null);
1398
1308
  setOpen(true);
1309
+ onOpen?.();
1399
1310
  };
1400
1311
  const consoleCollector = useRef3(null);
1401
1312
  const networkCollector = useRef3(null);
1402
1313
  const replayEvents = useRef3([]);
1403
1314
  const stopReplay = useRef3(null);
1404
- if (!consoleCollector.current) {
1315
+ if (enableConsole && !consoleCollector.current) {
1405
1316
  consoleCollector.current = createConsoleCollector();
1406
1317
  consoleCollector.current.start();
1407
1318
  }
1408
- if (!networkCollector.current) {
1319
+ if (enableNetwork && !networkCollector.current) {
1409
1320
  const flintHost = (() => {
1410
1321
  try {
1411
1322
  return new URL(serverUrl).hostname;
@@ -1417,22 +1328,29 @@ function WidgetContent({
1417
1328
  networkCollector.current.start();
1418
1329
  }
1419
1330
  useEffect3(() => {
1420
- const stopFn = record({
1421
- emit(event) {
1422
- replayEvents.current.push(event);
1423
- const cutoff = Date.now() - REPLAY_WINDOW_MS;
1424
- while (replayEvents.current.length > 0 && replayEvents.current[0].timestamp < cutoff) {
1425
- replayEvents.current.shift();
1426
- }
1427
- }
1428
- });
1429
- stopReplay.current = stopFn ?? null;
1331
+ let cancelled = false;
1332
+ if (enableReplay) {
1333
+ import("rrweb").then(({ record }) => {
1334
+ if (cancelled) return;
1335
+ const stopFn = record({
1336
+ emit(event) {
1337
+ replayEvents.current.push(event);
1338
+ const cutoff = Date.now() - REPLAY_WINDOW_MS;
1339
+ while (replayEvents.current.length > 0 && replayEvents.current[0].timestamp < cutoff) {
1340
+ replayEvents.current.shift();
1341
+ }
1342
+ }
1343
+ });
1344
+ stopReplay.current = stopFn ?? null;
1345
+ });
1346
+ }
1430
1347
  return () => {
1348
+ cancelled = true;
1431
1349
  consoleCollector.current?.stop();
1432
1350
  networkCollector.current?.stop();
1433
1351
  stopReplay.current?.();
1434
1352
  };
1435
- }, []);
1353
+ }, [enableReplay]);
1436
1354
  const label = buttonLabel ?? t("buttonLabel");
1437
1355
  return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1438
1356
  /* @__PURE__ */ jsxs3(
@@ -1442,7 +1360,10 @@ function WidgetContent({
1442
1360
  onMouseDown: () => {
1443
1361
  pendingSelection.current = window.getSelection()?.toString().trim() ?? "";
1444
1362
  },
1445
- onClick: () => setOpen(true),
1363
+ onClick: () => {
1364
+ setOpen(true);
1365
+ onOpen?.();
1366
+ },
1446
1367
  onMouseEnter: () => setHovered(true),
1447
1368
  onMouseLeave: () => setHovered(false),
1448
1369
  "aria-label": label,
@@ -1523,6 +1444,7 @@ function WidgetContent({
1523
1444
  zIndex,
1524
1445
  onClose: () => {
1525
1446
  setOpen(false);
1447
+ onClose?.();
1526
1448
  pendingSelection.current = "";
1527
1449
  },
1528
1450
  getEnvironment: collectEnvironment,
@@ -1530,18 +1452,37 @@ function WidgetContent({
1530
1452
  getNetworkErrors: () => networkCollector.current?.getEntries() ?? [],
1531
1453
  getReplayEvents: () => [...replayEvents.current],
1532
1454
  getExternalReplayUrl,
1533
- initialSelection: pendingSelection.current
1455
+ initialSelection: pendingSelection.current,
1456
+ enableScreenshot,
1457
+ statusPageUrl,
1458
+ onBeforeSubmit,
1459
+ onSuccess,
1460
+ onError
1534
1461
  }
1535
1462
  )
1536
1463
  ] });
1537
1464
  }
1538
1465
  function TextIcon() {
1539
- return /* @__PURE__ */ jsxs3("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: [
1540
- /* @__PURE__ */ jsx3("path", { d: "M17 10H3" }),
1541
- /* @__PURE__ */ jsx3("path", { d: "M21 6H3" }),
1542
- /* @__PURE__ */ jsx3("path", { d: "M21 14H3" }),
1543
- /* @__PURE__ */ jsx3("path", { d: "M17 18H3" })
1544
- ] });
1466
+ return /* @__PURE__ */ jsxs3(
1467
+ "svg",
1468
+ {
1469
+ width: "12",
1470
+ height: "12",
1471
+ viewBox: "0 0 24 24",
1472
+ fill: "none",
1473
+ stroke: "currentColor",
1474
+ strokeWidth: "2.2",
1475
+ strokeLinecap: "round",
1476
+ strokeLinejoin: "round",
1477
+ "aria-hidden": "true",
1478
+ children: [
1479
+ /* @__PURE__ */ jsx3("path", { d: "M17 10H3" }),
1480
+ /* @__PURE__ */ jsx3("path", { d: "M21 6H3" }),
1481
+ /* @__PURE__ */ jsx3("path", { d: "M21 14H3" }),
1482
+ /* @__PURE__ */ jsx3("path", { d: "M17 18H3" })
1483
+ ]
1484
+ }
1485
+ );
1545
1486
  }
1546
1487
  function SparkIcon2() {
1547
1488
  return /* @__PURE__ */ jsx3(
@@ -1563,6 +1504,6 @@ function SparkIcon2() {
1563
1504
  export {
1564
1505
  FlintModal,
1565
1506
  FlintWidget,
1566
- flint
1507
+ flint2 as flint
1567
1508
  };
1568
1509
  //# sourceMappingURL=index.js.map