@diegotsi/flint-react 0.2.1 → 0.3.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,11 +1,175 @@
1
1
  // src/FlintWidget.tsx
2
- import { useState as useState2, useEffect as useEffect2, useRef as useRef2 } from "react";
2
+ import { useState as useState3, useEffect as useEffect3, useRef as useRef3 } from "react";
3
3
  import { I18nextProvider, useTranslation as useTranslation2 } from "react-i18next";
4
4
 
5
5
  // src/FlintModal.tsx
6
- import { useRef, useState, useEffect, useCallback } from "react";
6
+ import { useRef as useRef2, useState as useState2, useEffect as useEffect2, useCallback } from "react";
7
7
  import { useTranslation } from "react-i18next";
8
8
 
9
+ // src/ScreenAnnotator.tsx
10
+ import { useEffect, useRef, useState } from "react";
11
+ import { jsx, jsxs } from "react/jsx-runtime";
12
+ function normalizeRect(startX, startY, endX, endY) {
13
+ return {
14
+ x: Math.min(startX, endX),
15
+ y: Math.min(startY, endY),
16
+ w: Math.abs(endX - startX),
17
+ h: Math.abs(endY - startY)
18
+ };
19
+ }
20
+ function patchGetComputedStyle() {
21
+ const orig = window.getComputedStyle;
22
+ const cvs = document.createElement("canvas");
23
+ cvs.width = cvs.height = 1;
24
+ const ctx = cvs.getContext("2d", { willReadFrequently: true });
25
+ function resolve(val) {
26
+ try {
27
+ ctx.clearRect(0, 0, 1, 1);
28
+ ctx.fillStyle = val;
29
+ ctx.fillRect(0, 0, 1, 1);
30
+ const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
31
+ return a < 255 ? `rgba(${r},${g},${b},${(a / 255).toFixed(3)})` : `rgb(${r},${g},${b})`;
32
+ } catch {
33
+ return "transparent";
34
+ }
35
+ }
36
+ function fix(val) {
37
+ if (typeof val !== "string") return val;
38
+ if (!val.includes("oklab") && !val.includes("oklch")) return val;
39
+ return val.replace(/(?:oklab|oklch)\s*\([^)]+\)/gi, resolve);
40
+ }
41
+ window.getComputedStyle = function(...args) {
42
+ const style = orig.apply(window, args);
43
+ return new Proxy(style, {
44
+ get(target, prop) {
45
+ const val = target[prop];
46
+ if (typeof val === "function") {
47
+ return (...fnArgs) => fix(val.apply(target, fnArgs));
48
+ }
49
+ return fix(val);
50
+ }
51
+ });
52
+ };
53
+ return () => {
54
+ window.getComputedStyle = orig;
55
+ };
56
+ }
57
+ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
58
+ const [phase, setPhase] = useState("idle");
59
+ const [rect, setRect] = useState(null);
60
+ const startRef = useRef(null);
61
+ const overlayRef = useRef(null);
62
+ useEffect(() => {
63
+ const onKey = (e) => {
64
+ if (e.key === "Escape") onCancel();
65
+ };
66
+ window.addEventListener("keydown", onKey);
67
+ return () => window.removeEventListener("keydown", onKey);
68
+ }, [onCancel]);
69
+ const onMouseDown = (e) => {
70
+ e.preventDefault();
71
+ startRef.current = { x: e.clientX, y: e.clientY };
72
+ setPhase("selecting");
73
+ setRect({ x: e.clientX, y: e.clientY, w: 0, h: 0 });
74
+ };
75
+ const onMouseMove = (e) => {
76
+ if (phase !== "selecting" || !startRef.current) return;
77
+ setRect(normalizeRect(startRef.current.x, startRef.current.y, e.clientX, e.clientY));
78
+ };
79
+ const onMouseUp = async (e) => {
80
+ if (phase !== "selecting" || !startRef.current) return;
81
+ const finalRect = normalizeRect(startRef.current.x, startRef.current.y, e.clientX, e.clientY);
82
+ startRef.current = null;
83
+ if (finalRect.w < 5 || finalRect.h < 5) {
84
+ setPhase("idle");
85
+ setRect(null);
86
+ return;
87
+ }
88
+ setPhase("capturing");
89
+ await new Promise(
90
+ (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
91
+ );
92
+ const unpatch = patchGetComputedStyle();
93
+ try {
94
+ const html2canvas = (await import("./html2canvas.esm-4TRSXIVS.js")).default;
95
+ const canvas = await html2canvas(document.body, {
96
+ useCORS: true,
97
+ logging: false,
98
+ allowTaint: true,
99
+ ignoreElements: (el) => el === overlayRef.current
100
+ });
101
+ unpatch();
102
+ const dpr = window.devicePixelRatio ?? 1;
103
+ const ctx = canvas.getContext("2d");
104
+ ctx.fillStyle = "rgba(255,200,0,0.25)";
105
+ ctx.fillRect(finalRect.x * dpr, finalRect.y * dpr, finalRect.w * dpr, finalRect.h * dpr);
106
+ ctx.strokeStyle = "#f97316";
107
+ ctx.lineWidth = 3 * dpr;
108
+ ctx.strokeRect(finalRect.x * dpr, finalRect.y * dpr, finalRect.w * dpr, finalRect.h * dpr);
109
+ canvas.toBlob((blob) => {
110
+ if (blob) {
111
+ onCapture(new File([blob], "annotation.png", { type: "image/png" }));
112
+ } else {
113
+ onCancel();
114
+ }
115
+ }, "image/png");
116
+ } catch (err) {
117
+ unpatch();
118
+ console.error("[Flint] ScreenAnnotator capture failed:", err);
119
+ onCancel();
120
+ }
121
+ };
122
+ const isCapturing = phase === "capturing";
123
+ return /* @__PURE__ */ jsxs(
124
+ "div",
125
+ {
126
+ ref: overlayRef,
127
+ onMouseDown,
128
+ onMouseMove,
129
+ onMouseUp,
130
+ style: {
131
+ position: "fixed",
132
+ inset: 0,
133
+ zIndex,
134
+ cursor: isCapturing ? "default" : "crosshair",
135
+ userSelect: "none",
136
+ background: phase === "selecting" ? "rgba(0,0,0,0.15)" : "rgba(0,0,0,0.05)",
137
+ transition: "background 0.1s",
138
+ opacity: isCapturing ? 0 : 1,
139
+ pointerEvents: isCapturing ? "none" : "auto"
140
+ },
141
+ children: [
142
+ !isCapturing && /* @__PURE__ */ jsx("div", { style: {
143
+ position: "absolute",
144
+ top: 16,
145
+ left: "50%",
146
+ transform: "translateX(-50%)",
147
+ background: "rgba(0,0,0,0.75)",
148
+ color: "#fff",
149
+ padding: "8px 18px",
150
+ borderRadius: 8,
151
+ fontSize: 14,
152
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
153
+ pointerEvents: "none",
154
+ whiteSpace: "nowrap",
155
+ backdropFilter: "blur(4px)"
156
+ }, children: "Drag to highlight the problem area \xA0\xB7\xA0 Esc to cancel" }),
157
+ rect && phase === "selecting" && /* @__PURE__ */ jsx("div", { style: {
158
+ position: "absolute",
159
+ left: rect.x,
160
+ top: rect.y,
161
+ width: rect.w,
162
+ height: rect.h,
163
+ background: "rgba(255,200,0,0.2)",
164
+ border: "2px dashed #f97316",
165
+ boxSizing: "border-box",
166
+ pointerEvents: "none"
167
+ } })
168
+ ]
169
+ }
170
+ );
171
+ }
172
+
9
173
  // src/api.ts
10
174
  import { gzipSync } from "fflate";
11
175
  async function submitReport(serverUrl, projectKey, payload, screenshot) {
@@ -94,7 +258,7 @@ function resolveTheme(theme) {
94
258
  }
95
259
 
96
260
  // src/FlintModal.tsx
97
- import { jsx, jsxs } from "react/jsx-runtime";
261
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
98
262
  var SEVERITIES = ["P1", "P2", "P3", "P4"];
99
263
  var SEV_COLOR = {
100
264
  P1: "#ef4444",
@@ -155,19 +319,20 @@ function FlintModal({
155
319
  const { t } = useTranslation();
156
320
  const colors = resolveTheme(theme);
157
321
  const isDark = theme === "dark";
158
- const [severity, setSeverity] = useState("P2");
159
- const [description, setDescription] = useState("");
160
- const [expectedBehavior, setExpectedBehavior] = useState("");
161
- const [screenshot, setScreenshot] = useState(null);
162
- const [status, setStatus] = useState("idle");
163
- const [result, setResult] = useState(null);
164
- const [errorMsg, setErrorMsg] = useState("");
165
- const fileRef = useRef(null);
166
- const overlayRef = useRef(null);
167
- useEffect(() => {
322
+ const [severity, setSeverity] = useState2("P2");
323
+ const [description, setDescription] = useState2("");
324
+ const [expectedBehavior, setExpectedBehavior] = useState2("");
325
+ const [screenshot, setScreenshot] = useState2(null);
326
+ const [annotating, setAnnotating] = useState2(false);
327
+ const [status, setStatus] = useState2("idle");
328
+ const [result, setResult] = useState2(null);
329
+ const [errorMsg, setErrorMsg] = useState2("");
330
+ const fileRef = useRef2(null);
331
+ const overlayRef = useRef2(null);
332
+ useEffect2(() => {
168
333
  injectKeyframes();
169
334
  }, []);
170
- useEffect(() => {
335
+ useEffect2(() => {
171
336
  const handler = (e) => {
172
337
  if (e.key === "Escape" && status !== "submitting") onClose();
173
338
  };
@@ -266,11 +431,11 @@ function FlintModal({
266
431
  const isSuccess = status === "success";
267
432
  const ringBorder = isSuccess ? "3px solid #22c55e" : `3px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)"}`;
268
433
  const ringTopColor = isSuccess ? "#22c55e" : colors.accent;
269
- return /* @__PURE__ */ jsx("div", { style: overlayStyle, children: /* @__PURE__ */ jsxs("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-label": isSuccess ? t("successTitle") : t("sending"), children: [
270
- /* @__PURE__ */ jsx(ModalHeader, { colors, inputBorder, showClose: false, onClose }),
271
- /* @__PURE__ */ jsxs("div", { style: { padding: "40px 32px 48px", textAlign: "center", display: "flex", flexDirection: "column", alignItems: "center" }, children: [
272
- /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: 80, height: 80, marginBottom: 28 }, children: [
273
- /* @__PURE__ */ jsx("div", { style: {
434
+ 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: [
435
+ /* @__PURE__ */ jsx2(ModalHeader, { colors, inputBorder, showClose: false, onClose }),
436
+ /* @__PURE__ */ jsxs2("div", { style: { padding: "40px 32px 48px", textAlign: "center", display: "flex", flexDirection: "column", alignItems: "center" }, children: [
437
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative", width: 80, height: 80, marginBottom: 28 }, children: [
438
+ /* @__PURE__ */ jsx2("div", { style: {
274
439
  position: "absolute",
275
440
  inset: -10,
276
441
  borderRadius: "50%",
@@ -279,7 +444,7 @@ function FlintModal({
279
444
  opacity: isSuccess ? 0 : 1,
280
445
  transition: "opacity 0.3s ease"
281
446
  } }),
282
- /* @__PURE__ */ jsx("div", { style: {
447
+ /* @__PURE__ */ jsx2("div", { style: {
283
448
  position: "absolute",
284
449
  inset: -10,
285
450
  borderRadius: "50%",
@@ -288,7 +453,7 @@ function FlintModal({
288
453
  opacity: isSuccess ? 0 : 1,
289
454
  transition: "opacity 0.3s ease"
290
455
  } }),
291
- /* @__PURE__ */ jsx("div", { style: {
456
+ /* @__PURE__ */ jsx2("div", { style: {
292
457
  position: "absolute",
293
458
  inset: 0,
294
459
  borderRadius: "50%",
@@ -297,8 +462,8 @@ function FlintModal({
297
462
  animation: isSuccess ? "none" : "_flint_spin 0.85s linear infinite",
298
463
  transition: "border-color 0.45s ease, border-top-color 0.45s ease"
299
464
  } }),
300
- /* @__PURE__ */ jsxs("div", { style: { position: "absolute", inset: 14, borderRadius: "50%" }, children: [
301
- /* @__PURE__ */ jsx("div", { style: {
465
+ /* @__PURE__ */ jsxs2("div", { style: { position: "absolute", inset: 14, borderRadius: "50%" }, children: [
466
+ /* @__PURE__ */ jsx2("div", { style: {
302
467
  position: "absolute",
303
468
  inset: 0,
304
469
  borderRadius: "50%",
@@ -309,8 +474,8 @@ function FlintModal({
309
474
  animation: isSuccess ? "none" : "_flint_pulse 2s ease-in-out infinite",
310
475
  opacity: isSuccess ? 0 : 1,
311
476
  transition: "opacity 0.3s ease"
312
- }, children: /* @__PURE__ */ jsx(SparkIcon, { color: colors.accent, size: 20 }) }),
313
- /* @__PURE__ */ jsx("div", { style: {
477
+ }, children: /* @__PURE__ */ jsx2(SparkIcon, { color: colors.accent, size: 20 }) }),
478
+ /* @__PURE__ */ jsx2("div", { style: {
314
479
  position: "absolute",
315
480
  inset: 0,
316
481
  borderRadius: "50%",
@@ -321,11 +486,11 @@ function FlintModal({
321
486
  opacity: isSuccess ? 1 : 0,
322
487
  transform: isSuccess ? "scale(1)" : "scale(0.65)",
323
488
  transition: "opacity 0.35s ease 0.2s, transform 0.4s cubic-bezier(0.16,1,0.3,1) 0.2s"
324
- }, children: /* @__PURE__ */ jsx(CheckIcon, { size: 20 }) })
489
+ }, children: /* @__PURE__ */ jsx2(CheckIcon, { size: 20 }) })
325
490
  ] })
326
491
  ] }),
327
- /* @__PURE__ */ jsxs("div", { style: { position: "relative", height: 26, width: "100%", marginBottom: 10 }, children: [
328
- /* @__PURE__ */ jsx("div", { style: {
492
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative", height: 26, width: "100%", marginBottom: 10 }, children: [
493
+ /* @__PURE__ */ jsx2("div", { style: {
329
494
  position: "absolute",
330
495
  inset: 0,
331
496
  display: "flex",
@@ -339,7 +504,7 @@ function FlintModal({
339
504
  transition: "opacity 0.25s ease",
340
505
  pointerEvents: "none"
341
506
  }, children: t("sending") }),
342
- /* @__PURE__ */ jsx("div", { style: {
507
+ /* @__PURE__ */ jsx2("div", { style: {
343
508
  position: "absolute",
344
509
  inset: 0,
345
510
  display: "flex",
@@ -355,8 +520,8 @@ function FlintModal({
355
520
  pointerEvents: isSuccess ? "auto" : "none"
356
521
  }, children: t("successTitle") })
357
522
  ] }),
358
- /* @__PURE__ */ jsxs("div", { style: { position: "relative", minHeight: 76, width: "100%" }, children: [
359
- /* @__PURE__ */ jsxs("div", { style: {
523
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative", minHeight: 76, width: "100%" }, children: [
524
+ /* @__PURE__ */ jsxs2("div", { style: {
360
525
  position: "absolute",
361
526
  inset: 0,
362
527
  display: "flex",
@@ -369,10 +534,10 @@ function FlintModal({
369
534
  transition: "opacity 0.2s ease",
370
535
  pointerEvents: "none"
371
536
  }, children: [
372
- /* @__PURE__ */ jsx("span", { children: t("capturingContext") }),
373
- /* @__PURE__ */ jsx(SendingDots, { color: colors.accent })
537
+ /* @__PURE__ */ jsx2("span", { children: t("capturingContext") }),
538
+ /* @__PURE__ */ jsx2(SendingDots, { color: colors.accent })
374
539
  ] }),
375
- /* @__PURE__ */ jsxs("div", { style: {
540
+ /* @__PURE__ */ jsxs2("div", { style: {
376
541
  position: "absolute",
377
542
  inset: 0,
378
543
  display: "flex",
@@ -384,8 +549,8 @@ function FlintModal({
384
549
  transition: "opacity 0.35s ease 0.35s, transform 0.35s ease 0.35s",
385
550
  pointerEvents: isSuccess ? "auto" : "none"
386
551
  }, children: [
387
- /* @__PURE__ */ jsx("p", { style: { fontSize: 15, color: colors.textMuted, margin: 0 }, children: result ? `ID: ${result.id}` : "" }),
388
- /* @__PURE__ */ jsx(
552
+ /* @__PURE__ */ jsx2("p", { style: { fontSize: 15, color: colors.textMuted, margin: 0 }, children: result ? `ID: ${result.id}` : "" }),
553
+ /* @__PURE__ */ jsx2(
389
554
  "button",
390
555
  {
391
556
  onClick: onClose,
@@ -415,176 +580,251 @@ function FlintModal({
415
580
  ] })
416
581
  ] }) });
417
582
  }
418
- return /* @__PURE__ */ jsx("div", { ref: overlayRef, style: overlayStyle, onClick: handleOverlayClick, children: /* @__PURE__ */ jsxs("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-labelledby": "flint-modal-title", children: [
419
- /* @__PURE__ */ jsx(
420
- ModalHeader,
583
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
584
+ annotating && /* @__PURE__ */ jsx2(
585
+ ScreenAnnotator,
421
586
  {
422
- colors,
423
- inputBorder,
424
- showClose: true,
425
- onClose,
426
- titleId: "flint-modal-title",
427
- title: t("modalTitle")
587
+ zIndex: zIndex + 1,
588
+ onCapture: (file) => {
589
+ setScreenshot(file);
590
+ setAnnotating(false);
591
+ },
592
+ onCancel: () => setAnnotating(false)
428
593
  }
429
594
  ),
430
- /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, style: { padding: "20px 24px 24px" }, children: [
431
- /* @__PURE__ */ jsxs("div", { style: { marginBottom: 18 }, children: [
432
- /* @__PURE__ */ jsx(FieldLabel, { colors, children: t("severityLabel") }),
433
- /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8 }, children: SEVERITIES.map((sev) => /* @__PURE__ */ jsx(
434
- SeverityButton,
435
- {
436
- sev,
437
- label: t(`severity_${sev}_label`),
438
- selected: severity === sev,
439
- hint: t(`severity_${sev}_hint`),
440
- color: SEV_COLOR[sev],
441
- accent: colors.accent,
442
- border: inputBorder,
443
- bg: colors.backgroundSecondary,
444
- text: colors.text,
445
- onClick: () => setSeverity(sev)
446
- },
447
- sev
448
- )) })
449
- ] }),
450
- /* @__PURE__ */ jsxs("div", { style: { marginBottom: 14 }, children: [
451
- /* @__PURE__ */ jsx(FieldLabel, { colors, htmlFor: "flint-description", children: t("whatIsBrokenLabel") }),
452
- /* @__PURE__ */ jsx(
453
- "textarea",
454
- {
455
- id: "flint-description",
456
- style: { ...inputStyle, resize: "vertical", minHeight: 80 },
457
- value: description,
458
- onChange: (e) => setDescription(e.target.value),
459
- placeholder: t("whatIsBrokenPlaceholder"),
460
- required: true
461
- }
462
- )
463
- ] }),
464
- /* @__PURE__ */ jsxs("div", { style: { marginBottom: 14 }, children: [
465
- /* @__PURE__ */ jsx(FieldLabel, { colors, htmlFor: "flint-expected", children: t("expectedBehaviorLabel") }),
466
- /* @__PURE__ */ jsx(
467
- "textarea",
468
- {
469
- id: "flint-expected",
470
- style: { ...inputStyle, resize: "vertical", minHeight: 72 },
471
- value: expectedBehavior,
472
- onChange: (e) => setExpectedBehavior(e.target.value),
473
- placeholder: t("expectedBehaviorPlaceholder")
474
- }
475
- )
476
- ] }),
477
- /* @__PURE__ */ jsxs("div", { style: { marginBottom: 20 }, children: [
478
- /* @__PURE__ */ jsx(FieldLabel, { colors, children: t("screenshotLabel") }),
479
- /* @__PURE__ */ jsxs(
480
- "label",
595
+ /* @__PURE__ */ jsx2("div", { ref: overlayRef, style: { ...overlayStyle, opacity: annotating ? 0 : 1, pointerEvents: annotating ? "none" : "auto" }, onClick: handleOverlayClick, children: /* @__PURE__ */ jsxs2("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-labelledby": "flint-modal-title", children: [
596
+ /* @__PURE__ */ jsx2(
597
+ ModalHeader,
598
+ {
599
+ colors,
600
+ inputBorder,
601
+ showClose: true,
602
+ onClose,
603
+ titleId: "flint-modal-title",
604
+ title: t("modalTitle")
605
+ }
606
+ ),
607
+ /* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, style: { padding: "20px 24px 24px" }, children: [
608
+ /* @__PURE__ */ jsxs2("div", { style: { marginBottom: 18 }, children: [
609
+ /* @__PURE__ */ jsx2(FieldLabel, { colors, children: t("severityLabel") }),
610
+ /* @__PURE__ */ jsx2("div", { style: { display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8 }, children: SEVERITIES.map((sev) => /* @__PURE__ */ jsx2(
611
+ SeverityButton,
612
+ {
613
+ sev,
614
+ label: t(`severity_${sev}_label`),
615
+ selected: severity === sev,
616
+ hint: t(`severity_${sev}_hint`),
617
+ color: SEV_COLOR[sev],
618
+ accent: colors.accent,
619
+ border: inputBorder,
620
+ bg: colors.backgroundSecondary,
621
+ text: colors.text,
622
+ onClick: () => setSeverity(sev)
623
+ },
624
+ sev
625
+ )) })
626
+ ] }),
627
+ /* @__PURE__ */ jsxs2("div", { style: { marginBottom: 14 }, children: [
628
+ /* @__PURE__ */ jsx2(FieldLabel, { colors, htmlFor: "flint-description", children: t("whatIsBrokenLabel") }),
629
+ /* @__PURE__ */ jsx2(
630
+ "textarea",
631
+ {
632
+ id: "flint-description",
633
+ style: { ...inputStyle, resize: "vertical", minHeight: 80 },
634
+ value: description,
635
+ onChange: (e) => setDescription(e.target.value),
636
+ placeholder: t("whatIsBrokenPlaceholder"),
637
+ required: true
638
+ }
639
+ )
640
+ ] }),
641
+ /* @__PURE__ */ jsxs2("div", { style: { marginBottom: 14 }, children: [
642
+ /* @__PURE__ */ jsx2(FieldLabel, { colors, htmlFor: "flint-expected", children: t("expectedBehaviorLabel") }),
643
+ /* @__PURE__ */ jsx2(
644
+ "textarea",
645
+ {
646
+ id: "flint-expected",
647
+ style: { ...inputStyle, resize: "vertical", minHeight: 72 },
648
+ value: expectedBehavior,
649
+ onChange: (e) => setExpectedBehavior(e.target.value),
650
+ placeholder: t("expectedBehaviorPlaceholder")
651
+ }
652
+ )
653
+ ] }),
654
+ /* @__PURE__ */ jsxs2("div", { style: { marginBottom: 20 }, children: [
655
+ /* @__PURE__ */ jsx2(FieldLabel, { colors, children: t("screenshotLabel") }),
656
+ screenshot ? /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
657
+ /* @__PURE__ */ jsx2(
658
+ "img",
659
+ {
660
+ src: URL.createObjectURL(screenshot),
661
+ alt: "Screenshot preview",
662
+ style: { height: 60, borderRadius: 8, objectFit: "cover", border: `1px solid ${inputBorder}` }
663
+ }
664
+ ),
665
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 13, color: colors.textMuted, flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: screenshot.name }),
666
+ /* @__PURE__ */ jsx2(
667
+ "button",
668
+ {
669
+ type: "button",
670
+ onClick: () => setScreenshot(null),
671
+ "aria-label": "Remove screenshot",
672
+ style: {
673
+ background: "none",
674
+ border: "none",
675
+ cursor: "pointer",
676
+ fontSize: 18,
677
+ color: colors.textMuted,
678
+ lineHeight: 1,
679
+ padding: "2px 6px",
680
+ borderRadius: 6,
681
+ fontFamily: "inherit"
682
+ },
683
+ children: "\xD7"
684
+ }
685
+ )
686
+ ] }) : /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8 }, children: [
687
+ /* @__PURE__ */ jsxs2(
688
+ "button",
689
+ {
690
+ type: "button",
691
+ onClick: () => fileRef.current?.click(),
692
+ style: {
693
+ flex: 1,
694
+ display: "flex",
695
+ alignItems: "center",
696
+ justifyContent: "center",
697
+ gap: 6,
698
+ padding: "10px 13px",
699
+ borderRadius: 10,
700
+ border: `1px dashed ${inputBorder}`,
701
+ cursor: "pointer",
702
+ fontSize: 14,
703
+ color: colors.textMuted,
704
+ background: colors.backgroundSecondary,
705
+ fontFamily: "inherit"
706
+ },
707
+ children: [
708
+ "\u{1F4CE} ",
709
+ t("screenshotAttachFile")
710
+ ]
711
+ }
712
+ ),
713
+ /* @__PURE__ */ jsxs2(
714
+ "button",
715
+ {
716
+ type: "button",
717
+ onClick: () => setAnnotating(true),
718
+ style: {
719
+ flex: 1,
720
+ display: "flex",
721
+ alignItems: "center",
722
+ justifyContent: "center",
723
+ gap: 6,
724
+ padding: "10px 13px",
725
+ borderRadius: 10,
726
+ border: `1px dashed ${inputBorder}`,
727
+ cursor: "pointer",
728
+ fontSize: 14,
729
+ color: colors.textMuted,
730
+ background: colors.backgroundSecondary,
731
+ fontFamily: "inherit"
732
+ },
733
+ children: [
734
+ "\u{1F532} ",
735
+ t("screenshotMarkOnScreen")
736
+ ]
737
+ }
738
+ )
739
+ ] }),
740
+ /* @__PURE__ */ jsx2(
741
+ "input",
742
+ {
743
+ id: "flint-screenshot",
744
+ ref: fileRef,
745
+ type: "file",
746
+ accept: "image/*",
747
+ style: { display: "none" },
748
+ onChange: (e) => setScreenshot(e.target.files?.[0] ?? null)
749
+ }
750
+ )
751
+ ] }),
752
+ /* @__PURE__ */ jsxs2("div", { style: {
753
+ display: "flex",
754
+ alignItems: "center",
755
+ gap: 8,
756
+ padding: "9px 12px",
757
+ borderRadius: 10,
758
+ background: isDark ? "rgba(255,255,255,0.04)" : "rgba(0,77,240,0.04)",
759
+ border: `1px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,77,240,0.1)"}`,
760
+ marginBottom: 16
761
+ }, children: [
762
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 16 }, children: "\u{1F3A5}" }),
763
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 14, color: colors.textMuted, lineHeight: 1.4 }, children: t("replayInfo") })
764
+ ] }),
765
+ status === "error" && /* @__PURE__ */ jsxs2("div", { style: {
766
+ padding: "10px 13px",
767
+ borderRadius: 10,
768
+ background: "rgba(239,68,68,0.08)",
769
+ border: "1px solid rgba(239,68,68,0.2)",
770
+ color: "#f87171",
771
+ fontSize: 14,
772
+ marginBottom: 16
773
+ }, children: [
774
+ "\u26A0\uFE0F ",
775
+ errorMsg || t("errorLabel")
776
+ ] }),
777
+ /* @__PURE__ */ jsxs2(
778
+ "button",
481
779
  {
482
- htmlFor: "flint-screenshot",
780
+ type: "submit",
483
781
  style: {
782
+ width: "100%",
783
+ padding: "13px 20px",
784
+ borderRadius: 12,
785
+ border: "none",
786
+ background: `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})`,
787
+ color: colors.buttonText,
788
+ fontSize: 17,
789
+ fontWeight: 700,
790
+ cursor: "pointer",
791
+ letterSpacing: "-0.01em",
792
+ boxShadow: accentGlow,
793
+ fontFamily: "inherit",
484
794
  display: "flex",
485
795
  alignItems: "center",
486
- gap: 8,
487
- padding: "10px 13px",
488
- borderRadius: 10,
489
- border: `1px dashed ${inputBorder}`,
490
- cursor: "pointer",
491
- fontSize: 15,
492
- color: colors.textMuted,
493
- background: colors.backgroundSecondary
796
+ justifyContent: "center",
797
+ gap: 8
494
798
  },
495
799
  children: [
496
- "\u{1F4CE} ",
497
- screenshot ? screenshot.name : t("screenshotPlaceholder")
800
+ /* @__PURE__ */ jsx2(SparkIcon, { color: colors.buttonText, size: 15 }),
801
+ t("submitLabel")
498
802
  ]
499
803
  }
500
804
  ),
501
- /* @__PURE__ */ jsx(
502
- "input",
805
+ /* @__PURE__ */ jsx2(
806
+ "button",
503
807
  {
504
- id: "flint-screenshot",
505
- ref: fileRef,
506
- type: "file",
507
- accept: "image/*",
508
- style: { display: "none" },
509
- onChange: (e) => setScreenshot(e.target.files?.[0] ?? null)
808
+ type: "button",
809
+ onClick: onClose,
810
+ style: {
811
+ width: "100%",
812
+ padding: "10px",
813
+ marginTop: 8,
814
+ background: "none",
815
+ border: "none",
816
+ cursor: "pointer",
817
+ fontSize: 15,
818
+ color: colors.textMuted,
819
+ fontFamily: "inherit",
820
+ borderRadius: 8
821
+ },
822
+ children: t("cancel")
510
823
  }
511
824
  )
512
- ] }),
513
- /* @__PURE__ */ jsxs("div", { style: {
514
- display: "flex",
515
- alignItems: "center",
516
- gap: 8,
517
- padding: "9px 12px",
518
- borderRadius: 10,
519
- background: isDark ? "rgba(255,255,255,0.04)" : "rgba(0,77,240,0.04)",
520
- border: `1px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,77,240,0.1)"}`,
521
- marginBottom: 16
522
- }, children: [
523
- /* @__PURE__ */ jsx("span", { style: { fontSize: 16 }, children: "\u{1F3A5}" }),
524
- /* @__PURE__ */ jsx("span", { style: { fontSize: 14, color: colors.textMuted, lineHeight: 1.4 }, children: t("replayInfo") })
525
- ] }),
526
- status === "error" && /* @__PURE__ */ jsxs("div", { style: {
527
- padding: "10px 13px",
528
- borderRadius: 10,
529
- background: "rgba(239,68,68,0.08)",
530
- border: "1px solid rgba(239,68,68,0.2)",
531
- color: "#f87171",
532
- fontSize: 14,
533
- marginBottom: 16
534
- }, children: [
535
- "\u26A0\uFE0F ",
536
- errorMsg || t("errorLabel")
537
- ] }),
538
- /* @__PURE__ */ jsxs(
539
- "button",
540
- {
541
- type: "submit",
542
- style: {
543
- width: "100%",
544
- padding: "13px 20px",
545
- borderRadius: 12,
546
- border: "none",
547
- background: `linear-gradient(135deg, ${colors.accent}, ${colors.accentHover})`,
548
- color: colors.buttonText,
549
- fontSize: 17,
550
- fontWeight: 700,
551
- cursor: "pointer",
552
- letterSpacing: "-0.01em",
553
- boxShadow: accentGlow,
554
- fontFamily: "inherit",
555
- display: "flex",
556
- alignItems: "center",
557
- justifyContent: "center",
558
- gap: 8
559
- },
560
- children: [
561
- /* @__PURE__ */ jsx(SparkIcon, { color: colors.buttonText, size: 15 }),
562
- t("submitLabel")
563
- ]
564
- }
565
- ),
566
- /* @__PURE__ */ jsx(
567
- "button",
568
- {
569
- type: "button",
570
- onClick: onClose,
571
- style: {
572
- width: "100%",
573
- padding: "10px",
574
- marginTop: 8,
575
- background: "none",
576
- border: "none",
577
- cursor: "pointer",
578
- fontSize: 15,
579
- color: colors.textMuted,
580
- fontFamily: "inherit",
581
- borderRadius: 8
582
- },
583
- children: t("cancel")
584
- }
585
- )
586
- ] })
587
- ] }) });
825
+ ] })
826
+ ] }) })
827
+ ] });
588
828
  }
589
829
  function ModalHeader({
590
830
  colors,
@@ -594,14 +834,14 @@ function ModalHeader({
594
834
  titleId,
595
835
  title
596
836
  }) {
597
- return /* @__PURE__ */ jsxs("div", { style: {
837
+ return /* @__PURE__ */ jsxs2("div", { style: {
598
838
  display: "flex",
599
839
  alignItems: "center",
600
840
  gap: 10,
601
841
  padding: "16px 20px 14px",
602
842
  borderBottom: `1px solid ${inputBorder}`
603
843
  }, children: [
604
- /* @__PURE__ */ jsx("div", { style: {
844
+ /* @__PURE__ */ jsx2("div", { style: {
605
845
  width: 28,
606
846
  height: 28,
607
847
  borderRadius: 8,
@@ -611,9 +851,9 @@ function ModalHeader({
611
851
  alignItems: "center",
612
852
  justifyContent: "center",
613
853
  flexShrink: 0
614
- }, children: /* @__PURE__ */ jsx(SparkIcon, { color: colors.accent, size: 13 }) }),
615
- titleId && title ? /* @__PURE__ */ jsx("h2", { id: titleId, style: { margin: 0, fontSize: 16, fontWeight: 600, color: colors.text, letterSpacing: "-0.01em", flex: 1 }, children: title }) : /* @__PURE__ */ jsx("span", { style: { flex: 1, fontSize: 15, fontWeight: 600, color: colors.textMuted }, children: "Flint" }),
616
- showClose && /* @__PURE__ */ jsx(
854
+ }, children: /* @__PURE__ */ jsx2(SparkIcon, { color: colors.accent, size: 13 }) }),
855
+ 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" }),
856
+ showClose && /* @__PURE__ */ jsx2(
617
857
  "button",
618
858
  {
619
859
  onClick: onClose,
@@ -643,7 +883,7 @@ function FieldLabel({
643
883
  colors,
644
884
  htmlFor
645
885
  }) {
646
- return /* @__PURE__ */ jsx(
886
+ return /* @__PURE__ */ jsx2(
647
887
  "label",
648
888
  {
649
889
  htmlFor,
@@ -661,7 +901,7 @@ function FieldLabel({
661
901
  );
662
902
  }
663
903
  function SendingDots({ color }) {
664
- return /* @__PURE__ */ jsx("span", { style: { display: "inline-flex", gap: 3, alignItems: "center" }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
904
+ return /* @__PURE__ */ jsx2("span", { style: { display: "inline-flex", gap: 3, alignItems: "center" }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx2(
665
905
  "span",
666
906
  {
667
907
  style: {
@@ -677,7 +917,7 @@ function SendingDots({ color }) {
677
917
  )) });
678
918
  }
679
919
  function SeverityButton({ sev, label, selected, hint, color, accent, border, bg, text, onClick }) {
680
- return /* @__PURE__ */ jsxs(
920
+ return /* @__PURE__ */ jsxs2(
681
921
  "button",
682
922
  {
683
923
  type: "button",
@@ -697,18 +937,18 @@ function SeverityButton({ sev, label, selected, hint, color, accent, border, bg,
697
937
  fontFamily: "inherit"
698
938
  },
699
939
  children: [
700
- /* @__PURE__ */ jsx("span", { style: { width: 8, height: 8, borderRadius: "50%", background: color, display: "block" } }),
701
- /* @__PURE__ */ jsx("span", { style: { fontSize: 13, fontWeight: selected ? 700 : 500, color: selected ? accent : text, letterSpacing: "0.02em" }, children: sev }),
702
- /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: selected ? accent : text, opacity: 0.6, letterSpacing: "0.02em" }, children: label })
940
+ /* @__PURE__ */ jsx2("span", { style: { width: 8, height: 8, borderRadius: "50%", background: color, display: "block" } }),
941
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 13, fontWeight: selected ? 700 : 500, color: selected ? accent : text, letterSpacing: "0.02em" }, children: sev }),
942
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 11, color: selected ? accent : text, opacity: 0.6, letterSpacing: "0.02em" }, children: label })
703
943
  ]
704
944
  }
705
945
  );
706
946
  }
707
947
  function SparkIcon({ color = "currentColor", size = 14 }) {
708
- return /* @__PURE__ */ 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__ */ jsx("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" }) });
948
+ 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" }) });
709
949
  }
710
950
  function CheckIcon({ size = 20 }) {
711
- return /* @__PURE__ */ 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__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) });
951
+ 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" }) });
712
952
  }
713
953
 
714
954
  // src/i18n/index.ts
@@ -734,6 +974,8 @@ var en_default = {
734
974
  expectedBehaviorPlaceholder: "Describe exactly what the user should see or receive after the fix.",
735
975
  screenshotLabel: "Screenshot (optional)",
736
976
  screenshotPlaceholder: "Click to attach...",
977
+ screenshotAttachFile: "Attach file",
978
+ screenshotMarkOnScreen: "Mark on screen",
737
979
  submitLabel: "Submit",
738
980
  close: "Close",
739
981
  successTitle: "Bug reported!",
@@ -882,6 +1124,20 @@ function createConsoleCollector() {
882
1124
 
883
1125
  // src/collectors/network.ts
884
1126
  var MAX_ENTRIES2 = 20;
1127
+ var BLOCKED_HOSTS = /* @__PURE__ */ new Set([
1128
+ "browser-intake-datadoghq.com",
1129
+ "rum.browser-intake-datadoghq.com",
1130
+ "logs.browser-intake-datadoghq.com",
1131
+ "session-replay.browser-intake-datadoghq.com"
1132
+ ]);
1133
+ function isBlockedUrl(url) {
1134
+ try {
1135
+ const host = new URL(url, location.href).hostname;
1136
+ return BLOCKED_HOSTS.has(host) || [...BLOCKED_HOSTS].some((b) => host.endsWith("." + b));
1137
+ } catch {
1138
+ return false;
1139
+ }
1140
+ }
885
1141
  function truncateUrl(url) {
886
1142
  try {
887
1143
  const u = new URL(url, location.href);
@@ -910,13 +1166,15 @@ function createNetworkCollector() {
910
1166
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
911
1167
  const startTime = Date.now();
912
1168
  const res = await origFetch.call(window, input, init);
913
- push({
914
- method,
915
- url: truncateUrl(url),
916
- status: res.status,
917
- duration: Date.now() - startTime,
918
- timestamp: startTime
919
- });
1169
+ if (!isBlockedUrl(url)) {
1170
+ push({
1171
+ method,
1172
+ url: truncateUrl(url),
1173
+ status: res.status,
1174
+ duration: Date.now() - startTime,
1175
+ timestamp: startTime
1176
+ });
1177
+ }
920
1178
  return res;
921
1179
  };
922
1180
  origXHROpen = XMLHttpRequest.prototype.open;
@@ -924,13 +1182,15 @@ function createNetworkCollector() {
924
1182
  const startTime = Date.now();
925
1183
  const urlStr = typeof url === "string" ? url : url.href;
926
1184
  this.addEventListener("load", () => {
927
- push({
928
- method: method.toUpperCase(),
929
- url: truncateUrl(urlStr),
930
- status: this.status,
931
- duration: Date.now() - startTime,
932
- timestamp: startTime
933
- });
1185
+ if (!isBlockedUrl(urlStr)) {
1186
+ push({
1187
+ method: method.toUpperCase(),
1188
+ url: truncateUrl(urlStr),
1189
+ status: this.status,
1190
+ duration: Date.now() - startTime,
1191
+ timestamp: startTime
1192
+ });
1193
+ }
934
1194
  });
935
1195
  return origXHROpen.apply(this, [
936
1196
  method,
@@ -983,14 +1243,14 @@ var flint = {
983
1243
 
984
1244
  // src/FlintWidget.tsx
985
1245
  import { record } from "rrweb";
986
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1246
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
987
1247
  var REPLAY_WINDOW_MS = 6e4;
988
1248
  function FlintWidget(props) {
989
1249
  const { locale = "en-US" } = props;
990
- useEffect2(() => {
1250
+ useEffect3(() => {
991
1251
  i18n_default.changeLanguage(locale);
992
1252
  }, [locale]);
993
- return /* @__PURE__ */ jsx2(I18nextProvider, { i18n: i18n_default, children: /* @__PURE__ */ jsx2(WidgetContent, { ...props }) });
1253
+ return /* @__PURE__ */ jsx3(I18nextProvider, { i18n: i18n_default, children: /* @__PURE__ */ jsx3(WidgetContent, { ...props }) });
994
1254
  }
995
1255
  function WidgetContent({
996
1256
  projectKey,
@@ -1010,13 +1270,13 @@ function WidgetContent({
1010
1270
  return typeof src === "function" ? src() : src;
1011
1271
  };
1012
1272
  const { t } = useTranslation2();
1013
- const [open, setOpen] = useState2(false);
1014
- const [hovered, setHovered] = useState2(false);
1273
+ const [open, setOpen] = useState3(false);
1274
+ const [hovered, setHovered] = useState3(false);
1015
1275
  const colors = resolveTheme(theme);
1016
- const consoleCollector = useRef2(null);
1017
- const networkCollector = useRef2(null);
1018
- const replayEvents = useRef2([]);
1019
- const stopReplay = useRef2(null);
1276
+ const consoleCollector = useRef3(null);
1277
+ const networkCollector = useRef3(null);
1278
+ const replayEvents = useRef3([]);
1279
+ const stopReplay = useRef3(null);
1020
1280
  if (!consoleCollector.current) {
1021
1281
  consoleCollector.current = createConsoleCollector();
1022
1282
  consoleCollector.current.start();
@@ -1025,7 +1285,7 @@ function WidgetContent({
1025
1285
  networkCollector.current = createNetworkCollector();
1026
1286
  networkCollector.current.start();
1027
1287
  }
1028
- useEffect2(() => {
1288
+ useEffect3(() => {
1029
1289
  const stopFn = record({
1030
1290
  emit(event) {
1031
1291
  replayEvents.current.push(event);
@@ -1043,8 +1303,8 @@ function WidgetContent({
1043
1303
  };
1044
1304
  }, []);
1045
1305
  const label = buttonLabel ?? t("buttonLabel");
1046
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
1047
- /* @__PURE__ */ jsxs2(
1306
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1307
+ /* @__PURE__ */ jsxs3(
1048
1308
  "button",
1049
1309
  {
1050
1310
  onClick: () => setOpen(true),
@@ -1074,12 +1334,12 @@ function WidgetContent({
1074
1334
  letterSpacing: "0.01em"
1075
1335
  },
1076
1336
  children: [
1077
- /* @__PURE__ */ jsx2(SparkIcon2, {}),
1337
+ /* @__PURE__ */ jsx3(SparkIcon2, {}),
1078
1338
  label
1079
1339
  ]
1080
1340
  }
1081
1341
  ),
1082
- open && /* @__PURE__ */ jsx2(
1342
+ open && /* @__PURE__ */ jsx3(
1083
1343
  FlintModal,
1084
1344
  {
1085
1345
  projectKey,
@@ -1099,7 +1359,7 @@ function WidgetContent({
1099
1359
  ] });
1100
1360
  }
1101
1361
  function SparkIcon2() {
1102
- return /* @__PURE__ */ jsx2(
1362
+ return /* @__PURE__ */ jsx3(
1103
1363
  "svg",
1104
1364
  {
1105
1365
  width: "14",
@@ -1111,7 +1371,7 @@ function SparkIcon2() {
1111
1371
  strokeLinecap: "round",
1112
1372
  strokeLinejoin: "round",
1113
1373
  "aria-hidden": "true",
1114
- children: /* @__PURE__ */ jsx2("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" })
1374
+ children: /* @__PURE__ */ jsx3("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" })
1115
1375
  }
1116
1376
  );
1117
1377
  }