@tra-bilisim/report-issue 0.1.1 → 0.1.2

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/README.md CHANGED
@@ -41,6 +41,7 @@ const adapter: ReportIssueAdapter = {
41
41
  getCurrentUrl: () => window.location.href,
42
42
  pageOptions: [{ value: 'https://app/x', label: '/x' }],
43
43
  pageInputMode: 'select', // 'select' (default, uses pageOptions) | 'input' (free-text URL)
44
+ maskCapture: true, // default true (KVKK-safe); set false for unmasked screenshots/recordings
44
45
  environmentResolver: url => (url.includes('localhost') ? 'Locale' : 'Prod'),
45
46
  t: key => i18n.translate(key), // English keys are the default fallback text
46
47
  locale: 'tr',
@@ -57,6 +58,105 @@ function App() {
57
58
  }
58
59
  ```
59
60
 
61
+ ## Localization (i18n)
62
+
63
+ The widget never bundles its own i18n library — it calls `t(key)` from your adapter
64
+ for every user-facing string. **The key itself is the English fallback text**, so if
65
+ you omit `t`, the UI renders in English as-is.
66
+
67
+ Wire it to whatever i18n system you already use:
68
+
69
+ ```tsx
70
+ const adapter: ReportIssueAdapter = {
71
+ submit: ...,
72
+ t: key => i18n.translate(key), // e.g. react-i18next: (key) => i18next.t(key)
73
+ locale: 'tr',
74
+ };
75
+ ```
76
+
77
+ Below is the **complete list of keys** used by the widget, with Turkish
78
+ translations. Drop this straight into your translation resource file (adjust the
79
+ shape to whatever your `i18n.translate`/`t` implementation expects — this is a plain
80
+ key → value map):
81
+
82
+ ```ts
83
+ {
84
+ // Annotation editor toolbar
85
+ 'Select': 'Seç',
86
+ 'Draw': 'Çizim',
87
+ 'Line': 'Çizgi',
88
+ 'Arrow': 'Ok',
89
+ 'Rectangle': 'Dikdörtgen',
90
+ 'Circle': 'Daire',
91
+ 'Text': 'Metin',
92
+ 'Highlight': 'Vurgula',
93
+ 'Thickness': 'Kalınlık',
94
+ 'Undo': 'Geri al',
95
+ 'Redo': 'Yinele',
96
+ 'Cancel': 'İptal',
97
+ 'Confirm': 'Onayla',
98
+
99
+ // Floating button / dialog title
100
+ 'Report an Issue': 'Sorun Bildir',
101
+ 'Describe the problem and attach screenshots or a screen recording.': 'Sorunu açıklayın ve ekran görüntüsü veya ekran kaydı ekleyin.',
102
+
103
+ // Attachments
104
+ 'Attachments': 'Ekler',
105
+ 'Take a Screenshot': 'Ekran Görüntüsü Al',
106
+ 'Record Video': 'Video Kaydet',
107
+ '{name} exceeds the size limit.': '{name} boyut sınırını aşıyor.',
108
+ 'You can attach at most {count} files.': 'En fazla {count} dosya ekleyebilirsiniz.',
109
+ 'Failed to capture screenshot.': 'Ekran görüntüsü alınamadı.',
110
+
111
+ // Screen recording
112
+ 'Screen recording is only supported on Chrome and Edge browsers.': 'Ekran kaydı yalnızca Chrome ve Edge tarayıcılarında desteklenir.',
113
+ 'Only this application tab can be recorded. Please share this tab.': 'Yalnızca bu uygulama sekmesi kaydedilebilir. Lütfen bu sekmeyi paylaşın.',
114
+ 'Screen recording permission was denied.': 'Ekran kaydı izni reddedildi.',
115
+ 'Stop': 'Durdur',
116
+
117
+ // Recording / screenshot consent dialog (KVKK)
118
+ 'Screenshot Consent': 'Ekran Görüntüsü Onayı',
119
+ 'Screen Recording Consent': 'Ekran Kaydı Onayı',
120
+ 'Before the screenshot is taken, please review which data is collected and give your consent.': 'Ekran görüntüsü alınmadan önce, hangi verilerin toplandığını inceleyip onayınızı verin.',
121
+ 'Before the recording starts, please review which data is collected and give your consent.': 'Kayıt başlamadan önce, hangi verilerin toplandığını inceleyip onayınızı verin.',
122
+ 'To diagnose the issue you reported, a screenshot of this application screen will be captured. Sensitive on-screen data is masked before capture.': 'Bildirdiğiniz sorunu teşhis edebilmek için bu uygulama ekranının bir görüntüsü alınacaktır. Hassas veriler görüntü alınmadan önce maskelenir.',
123
+ 'To diagnose the issue you reported, a short screen recording of only this tab will be captured. All on-screen data is masked during recording; you may temporarily reveal it.': 'Bildirdiğiniz sorunu teşhis edebilmek için yalnızca bu sekmenin kısa bir ekran kaydı alınacaktır. Kayıt sırasında tüm ekran verileri maskelenir; isterseniz geçici olarak görünür hale getirebilirsiniz.',
124
+ 'Data that will be collected:': 'Toplanacak veriler:',
125
+ 'A masked screenshot of this application screen': 'Bu uygulama ekranının maskelenmiş bir görüntüsü',
126
+ 'A masked screen recording limited to this application tab': 'Yalnızca bu sekmeyle sınırlı, maskelenmiş bir ekran kaydı',
127
+ 'Console and network logs of this session': 'Bu oturuma ait konsol ve ağ kayıtları',
128
+ 'Session metadata (user, roles, browser, page)': 'Oturum meta verileri (kullanıcı, roller, tarayıcı, sayfa)',
129
+ 'This data is used solely to diagnose and resolve the reported issue and is shared only with the relevant technical team. You may withdraw your consent by not submitting the report.': 'Bu veriler yalnızca bildirdiğiniz sorunu teşhis edip çözmek amacıyla kullanılır ve yalnızca ilgili teknik ekiple paylaşılır. Raporu göndermeyerek onayınızı geri çekebilirsiniz.',
130
+ 'I have read the above and give my explicit consent to the processing and sharing of this data.': 'Yukarıdakileri okudum ve bu verilerin işlenmesine ve paylaşılmasına açıkça onay veriyorum.',
131
+ 'Take Screenshot': 'Ekran Görüntüsü Al',
132
+ 'Start Recording': 'Kaydı Başlat',
133
+
134
+ // Page field
135
+ 'The page where the issue occurred': 'Sorunun oluştuğu sayfa',
136
+ 'Current page': 'Geçerli sayfa',
137
+ 'Enter the page where the issue occurred': 'Sorunun oluştuğu sayfayı girin',
138
+ 'Please specify the page.': 'Lütfen sayfayı belirtin.',
139
+
140
+ // Category / description
141
+ 'Category': 'Kategori',
142
+ 'optional': 'opsiyonel',
143
+ 'Description': 'Açıklama',
144
+ 'Describe what happened...': 'Ne olduğunu açıklayın...',
145
+
146
+ // Select control
147
+ 'Search': 'Ara',
148
+ 'No results': 'Sonuç bulunamadı',
149
+
150
+ // Submit
151
+ 'Submit': 'Gönder',
152
+ 'An error occurred!': 'Bir hata oluştu!',
153
+ }
154
+ ```
155
+
156
+ `{name}` and `{count}` are literal placeholders — the widget does simple
157
+ `.replace('{name}', ...)` / `.replace('{count}', ...)` substitution itself, so keep
158
+ those tokens verbatim in your translated value.
159
+
60
160
  ## Network log capture (optional)
61
161
 
62
162
  `attachAxiosNetworkLogging(instance, options?)` installs request/response
@@ -156,13 +256,62 @@ async function loggedFetch(url: string, init?: RequestInit) {
156
256
 
157
257
  `submit` must resolve to `{ ok: boolean; message?: string | null }`.
158
258
 
159
- ## Masking controls (data attributes)
259
+ ## Masking (KVKK privacy)
260
+
261
+ By default, **everything is masked**: page text is replaced with `●●●●●●` and
262
+ input/textarea values are hidden before a screenshot is taken or a recording
263
+ starts. This is the safe default — turn it off only if your app has no
264
+ sensitive on-screen data.
265
+
266
+ ```ts
267
+ const adapter: ReportIssueAdapter = {
268
+ submit: ...,
269
+ maskCapture: false, // disable masking entirely — screenshots/recordings capture the real UI
270
+ };
271
+ ```
272
+
273
+ `maskCapture` is a single on/off switch for **both** capture types (screenshot and
274
+ screen recording); there's no separate flag per type. When `true` (default), the
275
+ per-element data attributes below give you fine-grained control over what stays
276
+ masked/visible within that overall mask:
277
+
278
+ ### Masking controls (data attributes)
160
279
 
161
280
  - `data-report-mask-ignore` — never mask this subtree (also excluded from the video mask)
162
281
  - `data-report-mask-text-ignore` — keep text visible (still masks inputs)
163
282
  - `data-report-ignore-capture` — exclude from screenshots entirely
164
283
  - `data-report-mask-control` / `data-report-mask-value` — force text masking
165
284
 
285
+ ### Masking controls for third-party markup (CSS selectors)
286
+
287
+ The attributes above require you to add them to your own JSX. If the element you
288
+ need to exclude is rendered by a component you don't control — a `<legend>` from a
289
+ form library, a chart tooltip, a portal from another package — you can't attach an
290
+ attribute to it. `maskSelectors` gives you the same four controls as **CSS
291
+ selectors** instead, so you can target it from outside without touching its markup:
292
+
293
+ ```ts
294
+ const adapter: ReportIssueAdapter = {
295
+ submit: ...,
296
+ maskSelectors: {
297
+ ignore: ['legend', '.third-party-lib__tooltip'], // == data-report-mask-ignore
298
+ textIgnore: ['.some-lib-readonly-label'], // == data-report-mask-text-ignore
299
+ forceTextMask: [], // == data-report-mask-control
300
+ ignoreCapture: [], // == data-report-ignore-capture
301
+ },
302
+ };
303
+ ```
304
+
305
+ All four arrays are optional and additive — they extend the built-in attributes,
306
+ they don't replace them. Selectors are matched the same way the attributes are:
307
+ against the element itself when masking text (a `<legend>Personal info</legend>`
308
+ selector directly excludes its own text) and against **ancestors** when masking
309
+ inputs (`ignore: ['.card']` protects inputs nested inside `.card`, not a `.card`
310
+ element that is itself an input). Prefer a selector specific enough to avoid
311
+ unintentionally un-masking real user data elsewhere on the page (e.g. `'legend'` is
312
+ fine if you have no other sensitive `<legend>` elements; otherwise scope it further,
313
+ e.g. `'.my-form legend'`).
314
+
166
315
  ## Entry points
167
316
 
168
317
  - `@tra-bilisim/report-issue` — React provider, button, dialog, hooks, types
@@ -1,5 +1,6 @@
1
- import { useReportIssueConfig, Button } from './chunk-JMQUG5Q7.js';
1
+ import { useReportIssueConfig, Button } from './chunk-6IHDBCB5.js';
2
2
  import './chunk-EXDFVVYA.js';
3
+ import './chunk-7ONPNSFH.js';
3
4
  import { useRef, useState, useEffect, useCallback } from 'react';
4
5
  import { Canvas, PencilBrush, FabricImage, Rect, Ellipse, Line, IText, Triangle, Group } from 'fabric';
5
6
  import { MousePointer2, Pencil, Minus, MoveRight, Square, Circle, Type, Highlighter, Undo2, Redo2 } from 'lucide-react';
@@ -1,4 +1,5 @@
1
1
  import { patchConsole } from './chunk-EXDFVVYA.js';
2
+ import { configureMaskSelectors } from './chunk-7ONPNSFH.js';
2
3
  import { createContext, forwardRef, useState, useMemo, useEffect, useContext } from 'react';
3
4
  import { jsxs, jsx } from 'react/jsx-runtime';
4
5
 
@@ -32,7 +33,8 @@ function resolveConfig(adapter) {
32
33
  getCurrentUrl: adapter.getCurrentUrl ?? (() => typeof window !== "undefined" ? window.location.href : ""),
33
34
  maxFiles: adapter.maxFiles ?? DEFAULT_MAX_FILES,
34
35
  maxFileSizeBytes: adapter.maxFileSizeBytes ?? DEFAULT_MAX_FILE_SIZE,
35
- maxRecordingSeconds: adapter.maxRecordingSeconds ?? DEFAULT_MAX_RECORDING_SECONDS
36
+ maxRecordingSeconds: adapter.maxRecordingSeconds ?? DEFAULT_MAX_RECORDING_SECONDS,
37
+ maskCapture: adapter.maskCapture ?? true
36
38
  };
37
39
  }
38
40
  var ReportIssueContext = createContext(void 0);
@@ -45,6 +47,9 @@ var ReportIssueProvider = ({ config, children }) => {
45
47
  useEffect(() => {
46
48
  if (config.captureConsole !== false) patchConsole();
47
49
  }, [config.captureConsole]);
50
+ useEffect(() => {
51
+ configureMaskSelectors(config.maskSelectors);
52
+ }, [config.maskSelectors]);
48
53
  const captureMode = isCapturing || isRecording;
49
54
  const value = useMemo(() => ({
50
55
  config: resolved,
@@ -7,8 +7,26 @@ var MEDIA_TAGS = /* @__PURE__ */ new Set(["IMG", "PICTURE", "SVG", "CANVAS", "VI
7
7
  var MASK_IGNORE_SELECTOR = "[data-report-mask-ignore], [data-sonner-toaster], [data-sonner-toast]";
8
8
  var TEXT_MASK_IGNORE_SELECTOR = "[data-report-mask-text-ignore]";
9
9
  var FORCE_TEXT_MASK_SELECTOR = "[data-report-mask-control], [data-report-mask-value]";
10
+ var CAPTURE_IGNORE_SELECTOR = "[data-report-ignore-capture]";
10
11
  var IGNORED_INPUT_TYPES = /* @__PURE__ */ new Set(["hidden", "checkbox", "radio", "file", "button", "submit", "reset", "image", "range", "color", "password"]);
11
12
  var maskedTextNodes = /* @__PURE__ */ new Map();
13
+ var extraSelectors = {
14
+ ignore: [],
15
+ textIgnore: [],
16
+ forceTextMask: [],
17
+ ignoreCapture: []
18
+ };
19
+ function configureMaskSelectors(overrides = {}) {
20
+ extraSelectors.ignore = overrides.ignore ?? [];
21
+ extraSelectors.textIgnore = overrides.textIgnore ?? [];
22
+ extraSelectors.forceTextMask = overrides.forceTextMask ?? [];
23
+ extraSelectors.ignoreCapture = overrides.ignoreCapture ?? [];
24
+ }
25
+ var combineSelector = (base, extra) => extra.length ? `${base}, ${extra.join(", ")}` : base;
26
+ var ignoreSelector = () => combineSelector(MASK_IGNORE_SELECTOR, extraSelectors.ignore);
27
+ var textIgnoreSelector = () => combineSelector(TEXT_MASK_IGNORE_SELECTOR, extraSelectors.textIgnore);
28
+ var forceTextMaskSelector = () => combineSelector(FORCE_TEXT_MASK_SELECTOR, extraSelectors.forceTextMask);
29
+ var captureIgnoreSelector = () => combineSelector(CAPTURE_IGNORE_SELECTOR, extraSelectors.ignoreCapture);
12
30
  function isInputElement(el) {
13
31
  return el.tagName === "INPUT";
14
32
  }
@@ -20,32 +38,36 @@ function isFormField(el) {
20
38
  }
21
39
  function hasIgnoreAncestor(node) {
22
40
  let cur = node.parentNode;
41
+ const selector = ignoreSelector();
23
42
  while (cur && cur !== document.body) {
24
- if (cur instanceof HTMLElement && cur.matches(MASK_IGNORE_SELECTOR)) return true;
43
+ if (cur instanceof HTMLElement && cur.matches(selector)) return true;
25
44
  cur = cur.parentNode;
26
45
  }
27
46
  return false;
28
47
  }
29
48
  function hasTextMaskIgnoreAncestor(node) {
30
49
  let cur = node.parentNode;
50
+ const selector = textIgnoreSelector();
31
51
  while (cur && cur !== document.body) {
32
- if (cur instanceof HTMLElement && cur.matches(TEXT_MASK_IGNORE_SELECTOR)) return true;
52
+ if (cur instanceof HTMLElement && cur.matches(selector)) return true;
33
53
  cur = cur.parentNode;
34
54
  }
35
55
  return false;
36
56
  }
37
57
  function hasForceTextMaskAncestor(node) {
38
58
  let cur = node.parentNode;
59
+ const selector = forceTextMaskSelector();
39
60
  while (cur && cur !== document.body) {
40
- if (cur instanceof HTMLElement && cur.matches(FORCE_TEXT_MASK_SELECTOR)) return true;
61
+ if (cur instanceof HTMLElement && cur.matches(selector)) return true;
41
62
  cur = cur.parentNode;
42
63
  }
43
64
  return false;
44
65
  }
45
66
  function hasCaptureIgnoreAncestor(el) {
46
67
  let cur = el.parentElement;
68
+ const selector = captureIgnoreSelector();
47
69
  while (cur && cur !== document.body) {
48
- if (cur.hasAttribute("data-report-ignore-capture")) return true;
70
+ if (cur.matches(selector)) return true;
49
71
  cur = cur.parentElement;
50
72
  }
51
73
  return false;
@@ -132,9 +154,10 @@ function toggleVideoMask(enable) {
132
154
  if (!document.getElementById(VIDEO_STYLE_ID)) {
133
155
  const style = document.createElement("style");
134
156
  style.id = VIDEO_STYLE_ID;
157
+ const notIgnored = ignoreSelector();
135
158
  style.textContent = `
136
- .report-video-mask input:not([data-report-mask-ignore]),
137
- .report-video-mask textarea:not([data-report-mask-ignore]) {
159
+ .report-video-mask input:not(${notIgnored}),
160
+ .report-video-mask textarea:not(${notIgnored}) {
138
161
  -webkit-text-security: disc !important;
139
162
  }
140
163
  `;
@@ -159,4 +182,4 @@ function toggleVideoMask(enable) {
159
182
  }
160
183
  }
161
184
 
162
- export { applyInputMask, applyMask, removeMask, toggleVideoMask };
185
+ export { applyInputMask, applyMask, captureIgnoreSelector, configureMaskSelectors, removeMask, toggleVideoMask };
@@ -1,10 +1,10 @@
1
- import { applyMask, applyInputMask, removeMask } from './chunk-ZYF6UFBB.js';
1
+ import { applyMask, applyInputMask, captureIgnoreSelector, removeMask } from './chunk-7ONPNSFH.js';
2
2
  import html2canvas from 'html2canvas-pro';
3
3
 
4
4
  var HIDE_DIALOG_CLASS = "report-hide-dialog";
5
5
  var waitForCaptureImages = async () => {
6
6
  const images = Array.from(document.images).filter((img) => {
7
- if (img.closest("[data-report-ignore-capture]")) return false;
7
+ if (img.closest(captureIgnoreSelector())) return false;
8
8
  const style = window.getComputedStyle(img);
9
9
  return img.getClientRects().length > 0 && style.display !== "none" && style.visibility !== "hidden";
10
10
  });
@@ -23,7 +23,7 @@ var waitForCaptureImages = async () => {
23
23
  var inlineCaptureSvgImages = async () => {
24
24
  const restores = [];
25
25
  const svgImages = Array.from(document.images).filter((img) => {
26
- if (img.closest("[data-report-ignore-capture]")) return false;
26
+ if (img.closest(captureIgnoreSelector())) return false;
27
27
  const src = img.currentSrc || img.src;
28
28
  if (!src) return false;
29
29
  try {
@@ -55,16 +55,18 @@ var inlineCaptureSvgImages = async () => {
55
55
  }));
56
56
  return () => restores.forEach((restore) => restore());
57
57
  };
58
- async function captureMaskedScreenshot() {
58
+ async function captureMaskedScreenshot(mask = true) {
59
59
  let restoreCaptureSvgImages = () => {
60
60
  };
61
61
  document.documentElement.classList.add(HIDE_DIALOG_CLASS);
62
- applyMask();
63
- applyInputMask();
62
+ if (mask) {
63
+ applyMask();
64
+ applyInputMask();
65
+ }
64
66
  await new Promise((resolve) => {
65
67
  setTimeout(resolve, 450);
66
68
  });
67
- applyInputMask();
69
+ if (mask) applyInputMask();
68
70
  restoreCaptureSvgImages = await inlineCaptureSvgImages();
69
71
  await waitForCaptureImages();
70
72
  const docEl = document.documentElement;
@@ -83,11 +85,11 @@ async function captureMaskedScreenshot() {
83
85
  height: pageScrolls ? fullHeight : viewportHeight,
84
86
  windowWidth: viewportWidth,
85
87
  windowHeight: pageScrolls ? fullHeight : viewportHeight,
86
- ignoreElements: (el) => el.hasAttribute?.("data-report-ignore-capture") || el.getAttribute?.("data-slot") === "dialog-overlay",
88
+ ignoreElements: (el) => el.matches?.(captureIgnoreSelector()) || el.getAttribute?.("data-slot") === "dialog-overlay",
87
89
  // onclone runs after the DOM is cloned but before rendering. React cannot
88
90
  // reset values in the clone, so masking is reliable here.
89
91
  onclone: (clonedDoc) => {
90
- applyInputMask(clonedDoc);
92
+ if (mask) applyInputMask(clonedDoc);
91
93
  }
92
94
  });
93
95
  return canvas.toDataURL("image/png");
@@ -30,6 +30,24 @@ interface PageOption {
30
30
  value: string;
31
31
  label: string;
32
32
  }
33
+ /**
34
+ * CSS-selector-based equivalents of the `data-report-*` masking attributes, for
35
+ * elements you cannot add attributes to (e.g. markup rendered by a third-party
36
+ * component — a `<legend>` from a form library, a chart tooltip, etc.). Each
37
+ * array is additive: it extends, rather than replaces, the corresponding
38
+ * attribute. Selectors are matched against ancestors the same way the
39
+ * attributes are — put them on a wrapper that contains the target content.
40
+ */
41
+ interface MaskSelectorOverrides {
42
+ /** Extra CSS selectors whose subtree is never masked. Same effect as `data-report-mask-ignore`. */
43
+ ignore?: string[];
44
+ /** Extra CSS selectors whose text stays visible (inputs still masked). Same effect as `data-report-mask-text-ignore`. */
45
+ textIgnore?: string[];
46
+ /** Extra CSS selectors whose text is force-masked. Same effect as `data-report-mask-control`. */
47
+ forceTextMask?: string[];
48
+ /** Extra CSS selectors excluded entirely from screenshots. Same effect as `data-report-ignore-capture`. */
49
+ ignoreCapture?: string[];
50
+ }
33
51
  interface ReportMetadata {
34
52
  userId?: string | number | null;
35
53
  userName?: string | null;
@@ -99,6 +117,14 @@ interface ReportIssueAdapter {
99
117
  maxRecordingSeconds?: number;
100
118
  /** Patch `console.*` to capture logs when the provider mounts. Default true. */
101
119
  captureConsole?: boolean;
120
+ /**
121
+ * Mask sensitive on-screen text/input values (KVKK-style privacy mask) during
122
+ * screenshot capture and screen recording. Default `true`. Set to `false` if
123
+ * your app has no sensitive on-screen data and you want unmasked captures.
124
+ */
125
+ maskCapture?: boolean;
126
+ /** CSS-selector overrides for elements you can't attach `data-report-*` attributes to. */
127
+ maskSelectors?: MaskSelectorOverrides;
102
128
  }
103
129
 
104
130
  declare function pushConsoleLog(level: ConsoleEntry['level'], args: unknown[]): void;
@@ -138,4 +164,4 @@ declare function createVideoRecorder(callbacks: VideoRecorderCallbacks, options?
138
164
  declare const CONSENT_VERSION = "1.0";
139
165
  declare function createConsent(captureType: CaptureType, version?: string): RecordingConsent;
140
166
 
141
- export { CONSENT_VERSION as C, type NetworkEntry as N, type PageOption as P, type RecorderStartResult as R, type VideoRecorderCallbacks as V, type CaptureType as a, type ConsoleEntry as b, type RecordingConsent as c, type ReportCategoryOption as d, type ReportIssueAdapter as e, type ReportIssueToast as f, type ReportMetadata as g, type ReportSubmitPayload as h, type ReportSubmitResult as i, type VideoRecorderController as j, beginNetworkRequest as k, completeNetworkRequest as l, createConsent as m, createVideoRecorder as n, failNetworkRequest as o, getConsoleLogs as p, getNetworkLogs as q, patchConsole as r, pushConsoleLog as s };
167
+ export { CONSENT_VERSION as C, type MaskSelectorOverrides as M, type NetworkEntry as N, type PageOption as P, type RecorderStartResult as R, type VideoRecorderCallbacks as V, type CaptureType as a, type ConsoleEntry as b, type RecordingConsent as c, type ReportCategoryOption as d, type ReportIssueAdapter as e, type ReportIssueToast as f, type ReportMetadata as g, type ReportSubmitPayload as h, type ReportSubmitResult as i, type VideoRecorderController as j, beginNetworkRequest as k, completeNetworkRequest as l, createConsent as m, createVideoRecorder as n, failNetworkRequest as o, getConsoleLogs as p, getNetworkLogs as q, patchConsole as r, pushConsoleLog as s };
package/dist/core.d.ts CHANGED
@@ -1,16 +1,21 @@
1
- export { C as CONSENT_VERSION, a as CaptureType, b as ConsoleEntry, N as NetworkEntry, P as PageOption, R as RecorderStartResult, c as RecordingConsent, d as ReportCategoryOption, e as ReportIssueAdapter, f as ReportIssueToast, g as ReportMetadata, h as ReportSubmitPayload, i as ReportSubmitResult, V as VideoRecorderCallbacks, j as VideoRecorderController, k as beginNetworkRequest, l as completeNetworkRequest, m as createConsent, n as createVideoRecorder, o as failNetworkRequest, p as getConsoleLogs, q as getNetworkLogs, r as patchConsole, s as pushConsoleLog } from './consent-CfFPLwnF.js';
1
+ import { M as MaskSelectorOverrides } from './consent-BSWTFAIV.js';
2
+ export { C as CONSENT_VERSION, a as CaptureType, b as ConsoleEntry, N as NetworkEntry, P as PageOption, R as RecorderStartResult, c as RecordingConsent, d as ReportCategoryOption, e as ReportIssueAdapter, f as ReportIssueToast, g as ReportMetadata, h as ReportSubmitPayload, i as ReportSubmitResult, V as VideoRecorderCallbacks, j as VideoRecorderController, k as beginNetworkRequest, l as completeNetworkRequest, m as createConsent, n as createVideoRecorder, o as failNetworkRequest, p as getConsoleLogs, q as getNetworkLogs, r as patchConsole, s as pushConsoleLog } from './consent-BSWTFAIV.js';
2
3
 
4
+ declare function configureMaskSelectors(overrides?: MaskSelectorOverrides): void;
5
+ /** CSS selector matching everything excluded from screenshots — built-in attribute plus any `maskSelectors.ignoreCapture` overrides. */
6
+ declare const captureIgnoreSelector: () => string;
3
7
  declare function applyInputMask(targetDoc?: Document): void;
4
8
  declare function applyMask(): void;
5
9
  declare function removeMask(): void;
6
10
  declare function toggleVideoMask(enable: boolean): void;
7
11
 
8
12
  /**
9
- * Captures a masked PNG screenshot of the whole document body and returns a
10
- * data URL. Applies KVKK text/input masking and hides any element marked with
11
- * `data-report-ignore-capture` (the dialog itself) during the capture, then
12
- * restores everything before resolving. Framework-agnostic.
13
+ * Captures a PNG screenshot of the whole document body and returns a data URL.
14
+ * Hides any element marked with `data-report-ignore-capture` (the dialog itself)
15
+ * during the capture, then restores everything before resolving. Framework-agnostic.
16
+ *
17
+ * @param mask Apply KVKK text/input masking before capturing. Default `true`.
13
18
  */
14
- declare function captureMaskedScreenshot(): Promise<string>;
19
+ declare function captureMaskedScreenshot(mask?: boolean): Promise<string>;
15
20
 
16
- export { applyInputMask, applyMask, captureMaskedScreenshot, removeMask, toggleVideoMask };
21
+ export { MaskSelectorOverrides, applyInputMask, applyMask, captureIgnoreSelector, captureMaskedScreenshot, configureMaskSelectors, removeMask, toggleVideoMask };
package/dist/core.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { CONSENT_VERSION, createConsent, createVideoRecorder } from './chunk-5S66KGBW.js';
2
2
  export { beginNetworkRequest, completeNetworkRequest, failNetworkRequest, getConsoleLogs, getNetworkLogs, patchConsole, pushConsoleLog } from './chunk-EXDFVVYA.js';
3
- export { captureMaskedScreenshot } from './chunk-KY2IRP36.js';
4
- export { applyInputMask, applyMask, removeMask, toggleVideoMask } from './chunk-ZYF6UFBB.js';
3
+ export { captureMaskedScreenshot } from './chunk-DCNBXNWY.js';
4
+ export { applyInputMask, applyMask, captureIgnoreSelector, configureMaskSelectors, removeMask, toggleVideoMask } from './chunk-7ONPNSFH.js';
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { e as ReportIssueAdapter, f as ReportIssueToast, a as CaptureType, c as RecordingConsent, b as ConsoleEntry, N as NetworkEntry, R as RecorderStartResult } from './consent-CfFPLwnF.js';
4
- export { C as CONSENT_VERSION, P as PageOption, d as ReportCategoryOption, g as ReportMetadata, h as ReportSubmitPayload, i as ReportSubmitResult, p as getConsoleLogs, q as getNetworkLogs, r as patchConsole } from './consent-CfFPLwnF.js';
3
+ import { e as ReportIssueAdapter, f as ReportIssueToast, a as CaptureType, c as RecordingConsent, b as ConsoleEntry, N as NetworkEntry, R as RecorderStartResult } from './consent-BSWTFAIV.js';
4
+ export { C as CONSENT_VERSION, P as PageOption, d as ReportCategoryOption, g as ReportMetadata, h as ReportSubmitPayload, i as ReportSubmitResult, p as getConsoleLogs, q as getNetworkLogs, r as patchConsole } from './consent-BSWTFAIV.js';
5
5
 
6
6
  /** Adapter with every optional field filled in — what components actually read. */
7
7
  interface ResolvedConfig {
@@ -14,6 +14,7 @@ interface ResolvedConfig {
14
14
  maxFiles: number;
15
15
  maxFileSizeBytes: number;
16
16
  maxRecordingSeconds: number;
17
+ maskCapture: boolean;
17
18
  }
18
19
  interface ReportIssueContextValue {
19
20
  config: ResolvedConfig;
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { createVideoRecorder, CONSENT_VERSION } from './chunk-5S66KGBW.js';
2
2
  export { CONSENT_VERSION } from './chunk-5S66KGBW.js';
3
- import { cn, useReportIssueConfig, Button, useReportIssue } from './chunk-JMQUG5Q7.js';
4
- export { ReportIssueProvider, useReportIssue, useReportIssueConfig } from './chunk-JMQUG5Q7.js';
3
+ import { cn, useReportIssueConfig, Button, useReportIssue } from './chunk-6IHDBCB5.js';
4
+ export { ReportIssueProvider, useReportIssue, useReportIssueConfig } from './chunk-6IHDBCB5.js';
5
5
  import { getConsoleLogs, getNetworkLogs } from './chunk-EXDFVVYA.js';
6
6
  export { getConsoleLogs, getNetworkLogs, patchConsole } from './chunk-EXDFVVYA.js';
7
- import { toggleVideoMask } from './chunk-ZYF6UFBB.js';
7
+ import { toggleVideoMask } from './chunk-7ONPNSFH.js';
8
8
  import { createPortal } from 'react-dom';
9
9
  import { X, ShieldCheck, Camera, Video, Film, FileText, Circle, Square, MessageSquareWarning, Check, ChevronDown } from 'lucide-react';
10
10
  import { forwardRef, lazy, useCallback, useState, useRef, useEffect, useMemo, Suspense } from 'react';
@@ -257,7 +257,7 @@ var RecordingConsentDialog = ({ open, captureType = "video", onOpenChange, onAcc
257
257
  ] }) });
258
258
  };
259
259
  var RecordingConsentDialog_default = RecordingConsentDialog;
260
- var AnnotationEditor = lazy(() => import('./AnnotationEditor-ILMYBTOG.js'));
260
+ var AnnotationEditor = lazy(() => import('./AnnotationEditor-XYSZQLAH.js'));
261
261
  var getAppOrigin = () => typeof window !== "undefined" ? window.location.origin : "";
262
262
  var toAbsoluteUrl = (value) => {
263
263
  if (!value) return "";
@@ -284,7 +284,8 @@ var ReportIssueDialog = ({ open, onOpenChange }) => {
284
284
  getCurrentUrl,
285
285
  maxFiles,
286
286
  maxFileSizeBytes,
287
- maxRecordingSeconds
287
+ maxRecordingSeconds,
288
+ maskCapture
288
289
  } = config;
289
290
  const { collectConsoleLogs, collectNetworkLogs } = useReportIssueCapture();
290
291
  const [view, setView] = useState("form");
@@ -354,8 +355,8 @@ var ReportIssueDialog = ({ open, onOpenChange }) => {
354
355
  const captureScreenshot = useCallback(async () => {
355
356
  setIsCapturing(true);
356
357
  try {
357
- const { captureMaskedScreenshot } = await import('./screenshot-BQPXCSLD.js');
358
- const dataUrl = await captureMaskedScreenshot();
358
+ const { captureMaskedScreenshot } = await import('./screenshot-ZBPAR3UN.js');
359
+ const dataUrl = await captureMaskedScreenshot(maskCapture);
359
360
  setEditorImage(dataUrl);
360
361
  await new Promise((resolve) => {
361
362
  requestAnimationFrame(() => {
@@ -368,7 +369,7 @@ var ReportIssueDialog = ({ open, onOpenChange }) => {
368
369
  } finally {
369
370
  setIsCapturing(false);
370
371
  }
371
- }, [setIsCapturing, t, toast]);
372
+ }, [setIsCapturing, t, toast, maskCapture]);
372
373
  const handleScreenshotConsentAccept = useCallback((consent) => {
373
374
  screenshotConsentRef.current = consent;
374
375
  setScreenshotConsentOpen(false);
@@ -399,8 +400,10 @@ var ReportIssueDialog = ({ open, onOpenChange }) => {
399
400
  maxDurationMs: maxRecordingSeconds * 1e3
400
401
  });
401
402
  const handleStartRecording = useCallback(async () => {
402
- setIsVideoMaskEnabled(true);
403
- toggleVideoMask(true);
403
+ if (maskCapture) {
404
+ setIsVideoMaskEnabled(true);
405
+ toggleVideoMask(true);
406
+ }
404
407
  onOpenChange(false);
405
408
  const result = await start();
406
409
  if (result !== "started") {
@@ -409,7 +412,7 @@ var ReportIssueDialog = ({ open, onOpenChange }) => {
409
412
  const message = result === "unsupported" ? t("Screen recording is only supported on Chrome and Edge browsers.") : result === "wrong-surface" ? t("Only this application tab can be recorded. Please share this tab.") : t("Screen recording permission was denied.");
410
413
  toast.error(message);
411
414
  }
412
- }, [start, setIsVideoMaskEnabled, handleRecordingChange, onOpenChange, t, toast]);
415
+ }, [start, setIsVideoMaskEnabled, handleRecordingChange, onOpenChange, t, toast, maskCapture]);
413
416
  const handleConsentAccept = useCallback((consent) => {
414
417
  consentRef.current = consent;
415
418
  setConsentOpen(false);
@@ -0,0 +1,2 @@
1
+ export { captureMaskedScreenshot } from './chunk-DCNBXNWY.js';
2
+ import './chunk-7ONPNSFH.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tra-bilisim/report-issue",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Headless-core + React UI \"Report an Issue\" widget: masked screenshot/annotation, tab-only screen recording, console/network log capture and KVKK consent.",
5
5
  "license": "MIT",
6
6
  "author": "TRA Bilişim",
@@ -1,2 +0,0 @@
1
- export { captureMaskedScreenshot } from './chunk-KY2IRP36.js';
2
- import './chunk-ZYF6UFBB.js';