@ewjdev/anyclick-react 0.1.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.mjs ADDED
@@ -0,0 +1,1462 @@
1
+ "use client";
2
+
3
+ // src/FeedbackProvider.tsx
4
+ import {
5
+ useCallback as useCallback2,
6
+ useEffect as useEffect2,
7
+ useMemo,
8
+ useRef as useRef2,
9
+ useState as useState3
10
+ } from "react";
11
+ import { createFeedbackClient } from "@ewjdev/anyclick-core";
12
+
13
+ // src/context.ts
14
+ import { createContext, useContext } from "react";
15
+ var FeedbackContext = createContext(null);
16
+ function useFeedback() {
17
+ const context = useContext(FeedbackContext);
18
+ if (!context) {
19
+ throw new Error("useFeedback must be used within a FeedbackProvider");
20
+ }
21
+ return context;
22
+ }
23
+
24
+ // src/ContextMenu.tsx
25
+ import { useState as useState2, useRef, useEffect, useCallback } from "react";
26
+ import {
27
+ captureAllScreenshots,
28
+ isScreenshotSupported,
29
+ DEFAULT_SCREENSHOT_CONFIG
30
+ } from "@ewjdev/anyclick-core";
31
+
32
+ // src/styles.ts
33
+ var menuStyles = {
34
+ overlay: {
35
+ position: "fixed",
36
+ inset: 0,
37
+ zIndex: 9998
38
+ },
39
+ container: {
40
+ position: "fixed",
41
+ zIndex: 9999,
42
+ minWidth: "200px",
43
+ backgroundColor: "#ffffff",
44
+ borderRadius: "8px",
45
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05)",
46
+ overflow: "hidden",
47
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
48
+ fontSize: "14px"
49
+ },
50
+ header: {
51
+ padding: "12px 16px",
52
+ borderBottom: "1px solid #e5e5e5",
53
+ color: "#666",
54
+ fontSize: "12px",
55
+ fontWeight: 500,
56
+ textTransform: "uppercase",
57
+ letterSpacing: "0.5px"
58
+ },
59
+ itemList: {
60
+ padding: "4px 0"
61
+ },
62
+ item: {
63
+ display: "flex",
64
+ alignItems: "center",
65
+ gap: "10px",
66
+ padding: "10px 16px",
67
+ cursor: "pointer",
68
+ transition: "background-color 0.15s",
69
+ color: "#333",
70
+ border: "none",
71
+ background: "none",
72
+ width: "100%",
73
+ textAlign: "left",
74
+ fontSize: "14px"
75
+ },
76
+ itemHover: {
77
+ backgroundColor: "#f5f5f5"
78
+ },
79
+ itemIcon: {
80
+ display: "flex",
81
+ alignItems: "center",
82
+ justifyContent: "center",
83
+ width: "20px",
84
+ height: "20px",
85
+ fontSize: "16px"
86
+ },
87
+ commentSection: {
88
+ padding: "12px 16px",
89
+ borderTop: "1px solid #e5e5e5"
90
+ },
91
+ commentInput: {
92
+ width: "100%",
93
+ minHeight: "60px",
94
+ padding: "8px 12px",
95
+ border: "1px solid #ddd",
96
+ borderRadius: "6px",
97
+ fontSize: "14px",
98
+ fontFamily: "inherit",
99
+ resize: "vertical",
100
+ outline: "none",
101
+ boxSizing: "border-box"
102
+ },
103
+ buttonRow: {
104
+ display: "flex",
105
+ justifyContent: "flex-end",
106
+ gap: "8px",
107
+ marginTop: "8px"
108
+ },
109
+ button: {
110
+ padding: "6px 12px",
111
+ borderRadius: "6px",
112
+ fontSize: "13px",
113
+ fontWeight: 500,
114
+ cursor: "pointer",
115
+ transition: "background-color 0.15s, opacity 0.15s",
116
+ border: "none"
117
+ },
118
+ cancelButton: {
119
+ backgroundColor: "#f0f0f0",
120
+ color: "#666",
121
+ display: "flex",
122
+ alignItems: "center",
123
+ gap: "2px"
124
+ },
125
+ submitButton: {
126
+ backgroundColor: "#0066cc",
127
+ color: "#ffffff",
128
+ display: "flex",
129
+ alignItems: "center",
130
+ gap: "2px"
131
+ },
132
+ submitButtonDisabled: {
133
+ opacity: 0.6,
134
+ cursor: "not-allowed"
135
+ }
136
+ };
137
+ var darkMenuStyles = {
138
+ container: {
139
+ ...menuStyles.container,
140
+ backgroundColor: "#1a1a1a",
141
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1)"
142
+ },
143
+ header: {
144
+ ...menuStyles.header,
145
+ borderBottom: "1px solid #333",
146
+ color: "#888"
147
+ },
148
+ item: {
149
+ ...menuStyles.item,
150
+ color: "#e0e0e0"
151
+ },
152
+ itemHover: {
153
+ backgroundColor: "#2a2a2a"
154
+ },
155
+ commentSection: {
156
+ ...menuStyles.commentSection,
157
+ borderTop: "1px solid #333"
158
+ },
159
+ commentInput: {
160
+ ...menuStyles.commentInput,
161
+ backgroundColor: "#2a2a2a",
162
+ border: "1px solid #444",
163
+ color: "#e0e0e0"
164
+ },
165
+ cancelButton: {
166
+ ...menuStyles.cancelButton,
167
+ backgroundColor: "#333",
168
+ color: "#ccc"
169
+ }
170
+ };
171
+
172
+ // src/highlight.ts
173
+ var HIGHLIGHT_TARGET_CLASS = "uifeedback-highlight-target";
174
+ var HIGHLIGHT_CONTAINER_CLASS = "uifeedback-highlight-container";
175
+ var STYLE_ID = "uifeedback-highlight-styles";
176
+ var defaultHighlightColors = {
177
+ targetColor: "#3b82f6",
178
+ containerColor: "#8b5cf6",
179
+ targetShadowOpacity: 0.25,
180
+ containerShadowOpacity: 0.1
181
+ };
182
+ var defaultContainerSelectors = [
183
+ ".container",
184
+ ".card",
185
+ ".panel",
186
+ ".section",
187
+ ".wrapper",
188
+ ".box",
189
+ ".modal",
190
+ ".dialog",
191
+ ".drawer",
192
+ '[role="dialog"]',
193
+ '[role="region"]',
194
+ '[role="article"]',
195
+ '[role="main"]',
196
+ "article",
197
+ "section",
198
+ "main",
199
+ "aside",
200
+ "nav",
201
+ "header",
202
+ "footer"
203
+ ];
204
+ function generateHighlightCSS(colors) {
205
+ const { targetColor, containerColor, targetShadowOpacity, containerShadowOpacity } = colors;
206
+ const hexToRgba = (hex, alpha) => {
207
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
208
+ if (!result) return `rgba(0, 0, 0, ${alpha})`;
209
+ return `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${alpha})`;
210
+ };
211
+ return `
212
+ .${HIGHLIGHT_TARGET_CLASS} {
213
+ outline: 2px solid ${targetColor} !important;
214
+ outline-offset: 2px !important;
215
+ box-shadow: 0 0 0 4px ${hexToRgba(targetColor, targetShadowOpacity)}, 0 4px 12px ${hexToRgba(targetColor, targetShadowOpacity * 0.6)} !important;
216
+ border-radius: 4px !important;
217
+ position: relative;
218
+ z-index: 9997;
219
+ }
220
+
221
+ .${HIGHLIGHT_CONTAINER_CLASS} {
222
+ outline: 2px dashed ${hexToRgba(containerColor, 0.6)} !important;
223
+ outline-offset: 4px !important;
224
+ box-shadow: 0 0 0 6px ${hexToRgba(containerColor, containerShadowOpacity)} !important;
225
+ border-radius: 6px !important;
226
+ position: relative;
227
+ z-index: 9996;
228
+ }
229
+ `;
230
+ }
231
+ function injectStyles(colors) {
232
+ if (typeof document === "undefined") return;
233
+ const existingStyle = document.getElementById(STYLE_ID);
234
+ if (existingStyle) {
235
+ existingStyle.remove();
236
+ }
237
+ const style = document.createElement("style");
238
+ style.id = STYLE_ID;
239
+ style.textContent = generateHighlightCSS(colors);
240
+ document.head.appendChild(style);
241
+ }
242
+ function findContainerParent(element, config) {
243
+ const containerSelectors = config?.containerSelectors ?? defaultContainerSelectors;
244
+ const minChildren = config?.minChildrenForContainer ?? 2;
245
+ let current = element.parentElement;
246
+ let fallbackContainer = null;
247
+ while (current && current !== document.body) {
248
+ for (const selector of containerSelectors) {
249
+ try {
250
+ if (current.matches(selector)) {
251
+ return current;
252
+ }
253
+ } catch {
254
+ }
255
+ }
256
+ const meaningfulChildren = Array.from(current.children).filter((child) => {
257
+ const tag = child.tagName.toLowerCase();
258
+ if (tag === "script" || tag === "style" || tag === "noscript") {
259
+ return false;
260
+ }
261
+ const style = window.getComputedStyle(child);
262
+ if (style.display === "none" || style.visibility === "hidden") {
263
+ return false;
264
+ }
265
+ return true;
266
+ });
267
+ if (meaningfulChildren.length >= minChildren && !fallbackContainer) {
268
+ fallbackContainer = current;
269
+ }
270
+ if (meaningfulChildren.length >= 3) {
271
+ return current;
272
+ }
273
+ current = current.parentElement;
274
+ }
275
+ return fallbackContainer;
276
+ }
277
+ function highlightTarget(element, colors) {
278
+ const mergedColors = { ...defaultHighlightColors, ...colors };
279
+ injectStyles(mergedColors);
280
+ element.classList.add(HIGHLIGHT_TARGET_CLASS);
281
+ }
282
+ function highlightContainer(element, colors) {
283
+ const mergedColors = { ...defaultHighlightColors, ...colors };
284
+ injectStyles(mergedColors);
285
+ element.classList.add(HIGHLIGHT_CONTAINER_CLASS);
286
+ }
287
+ function clearHighlights() {
288
+ if (typeof document === "undefined") return;
289
+ document.querySelectorAll(`.${HIGHLIGHT_TARGET_CLASS}`).forEach((el) => {
290
+ el.classList.remove(HIGHLIGHT_TARGET_CLASS);
291
+ });
292
+ document.querySelectorAll(`.${HIGHLIGHT_CONTAINER_CLASS}`).forEach((el) => {
293
+ el.classList.remove(HIGHLIGHT_CONTAINER_CLASS);
294
+ });
295
+ }
296
+ function applyHighlights(targetElement, config) {
297
+ if (config?.enabled === false) {
298
+ return { target: targetElement, container: null };
299
+ }
300
+ clearHighlights();
301
+ const colors = { ...defaultHighlightColors, ...config?.colors };
302
+ highlightTarget(targetElement, colors);
303
+ const container = findContainerParent(targetElement, config);
304
+ if (container && container !== targetElement) {
305
+ highlightContainer(container, colors);
306
+ }
307
+ return { target: targetElement, container };
308
+ }
309
+
310
+ // src/ScreenshotPreview.tsx
311
+ import { useState } from "react";
312
+ import { formatBytes, estimateTotalSize } from "@ewjdev/anyclick-core";
313
+
314
+ // ../../node_modules/lucide-react/dist/esm/createLucideIcon.js
315
+ import { forwardRef as forwardRef2, createElement as createElement2 } from "react";
316
+
317
+ // ../../node_modules/lucide-react/dist/esm/shared/src/utils.js
318
+ var toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
319
+ var toCamelCase = (string) => string.replace(
320
+ /^([A-Z])|[\s-_]+(\w)/g,
321
+ (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()
322
+ );
323
+ var toPascalCase = (string) => {
324
+ const camelCase = toCamelCase(string);
325
+ return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
326
+ };
327
+ var mergeClasses = (...classes) => classes.filter((className, index, array) => {
328
+ return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
329
+ }).join(" ").trim();
330
+ var hasA11yProp = (props) => {
331
+ for (const prop in props) {
332
+ if (prop.startsWith("aria-") || prop === "role" || prop === "title") {
333
+ return true;
334
+ }
335
+ }
336
+ };
337
+
338
+ // ../../node_modules/lucide-react/dist/esm/Icon.js
339
+ import { forwardRef, createElement } from "react";
340
+
341
+ // ../../node_modules/lucide-react/dist/esm/defaultAttributes.js
342
+ var defaultAttributes = {
343
+ xmlns: "http://www.w3.org/2000/svg",
344
+ width: 24,
345
+ height: 24,
346
+ viewBox: "0 0 24 24",
347
+ fill: "none",
348
+ stroke: "currentColor",
349
+ strokeWidth: 2,
350
+ strokeLinecap: "round",
351
+ strokeLinejoin: "round"
352
+ };
353
+
354
+ // ../../node_modules/lucide-react/dist/esm/Icon.js
355
+ var Icon = forwardRef(
356
+ ({
357
+ color = "currentColor",
358
+ size = 24,
359
+ strokeWidth = 2,
360
+ absoluteStrokeWidth,
361
+ className = "",
362
+ children,
363
+ iconNode,
364
+ ...rest
365
+ }, ref) => createElement(
366
+ "svg",
367
+ {
368
+ ref,
369
+ ...defaultAttributes,
370
+ width: size,
371
+ height: size,
372
+ stroke: color,
373
+ strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth,
374
+ className: mergeClasses("lucide", className),
375
+ ...!children && !hasA11yProp(rest) && { "aria-hidden": "true" },
376
+ ...rest
377
+ },
378
+ [
379
+ ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
380
+ ...Array.isArray(children) ? children : [children]
381
+ ]
382
+ )
383
+ );
384
+
385
+ // ../../node_modules/lucide-react/dist/esm/createLucideIcon.js
386
+ var createLucideIcon = (iconName, iconNode) => {
387
+ const Component = forwardRef2(
388
+ ({ className, ...props }, ref) => createElement2(Icon, {
389
+ ref,
390
+ iconNode,
391
+ className: mergeClasses(
392
+ `lucide-${toKebabCase(toPascalCase(iconName))}`,
393
+ `lucide-${iconName}`,
394
+ className
395
+ ),
396
+ ...props
397
+ })
398
+ );
399
+ Component.displayName = toPascalCase(iconName);
400
+ return Component;
401
+ };
402
+
403
+ // ../../node_modules/lucide-react/dist/esm/icons/camera.js
404
+ var __iconNode = [
405
+ [
406
+ "path",
407
+ {
408
+ d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z",
409
+ key: "18u6gg"
410
+ }
411
+ ],
412
+ ["circle", { cx: "12", cy: "13", r: "3", key: "1vg3eu" }]
413
+ ];
414
+ var Camera = createLucideIcon("camera", __iconNode);
415
+
416
+ // ../../node_modules/lucide-react/dist/esm/icons/check.js
417
+ var __iconNode2 = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
418
+ var Check = createLucideIcon("check", __iconNode2);
419
+
420
+ // ../../node_modules/lucide-react/dist/esm/icons/chevron-left.js
421
+ var __iconNode3 = [["path", { d: "m15 18-6-6 6-6", key: "1wnfg3" }]];
422
+ var ChevronLeft = createLucideIcon("chevron-left", __iconNode3);
423
+
424
+ // ../../node_modules/lucide-react/dist/esm/icons/chevron-right.js
425
+ var __iconNode4 = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]];
426
+ var ChevronRight = createLucideIcon("chevron-right", __iconNode4);
427
+
428
+ // ../../node_modules/lucide-react/dist/esm/icons/expand.js
429
+ var __iconNode5 = [
430
+ ["path", { d: "m15 15 6 6", key: "1s409w" }],
431
+ ["path", { d: "m15 9 6-6", key: "ko1vev" }],
432
+ ["path", { d: "M21 16v5h-5", key: "1ck2sf" }],
433
+ ["path", { d: "M21 8V3h-5", key: "1qoq8a" }],
434
+ ["path", { d: "M3 16v5h5", key: "1t08am" }],
435
+ ["path", { d: "m3 21 6-6", key: "wwnumi" }],
436
+ ["path", { d: "M3 8V3h5", key: "1ln10m" }],
437
+ ["path", { d: "M9 9 3 3", key: "v551iv" }]
438
+ ];
439
+ var Expand = createLucideIcon("expand", __iconNode5);
440
+
441
+ // ../../node_modules/lucide-react/dist/esm/icons/flag.js
442
+ var __iconNode6 = [
443
+ [
444
+ "path",
445
+ {
446
+ d: "M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528",
447
+ key: "1jaruq"
448
+ }
449
+ ]
450
+ ];
451
+ var Flag = createLucideIcon("flag", __iconNode6);
452
+
453
+ // ../../node_modules/lucide-react/dist/esm/icons/image.js
454
+ var __iconNode7 = [
455
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
456
+ ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
457
+ ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
458
+ ];
459
+ var Image = createLucideIcon("image", __iconNode7);
460
+
461
+ // ../../node_modules/lucide-react/dist/esm/icons/loader-circle.js
462
+ var __iconNode8 = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
463
+ var LoaderCircle = createLucideIcon("loader-circle", __iconNode8);
464
+
465
+ // ../../node_modules/lucide-react/dist/esm/icons/plus.js
466
+ var __iconNode9 = [
467
+ ["path", { d: "M5 12h14", key: "1ays0h" }],
468
+ ["path", { d: "M12 5v14", key: "s699le" }]
469
+ ];
470
+ var Plus = createLucideIcon("plus", __iconNode9);
471
+
472
+ // ../../node_modules/lucide-react/dist/esm/icons/refresh-cw.js
473
+ var __iconNode10 = [
474
+ ["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }],
475
+ ["path", { d: "M21 3v5h-5", key: "1q7to0" }],
476
+ ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }],
477
+ ["path", { d: "M8 16H3v5", key: "1cv678" }]
478
+ ];
479
+ var RefreshCw = createLucideIcon("refresh-cw", __iconNode10);
480
+
481
+ // ../../node_modules/lucide-react/dist/esm/icons/shrink.js
482
+ var __iconNode11 = [
483
+ ["path", { d: "m15 15 6 6m-6-6v4.8m0-4.8h4.8", key: "17vawe" }],
484
+ ["path", { d: "M9 19.8V15m0 0H4.2M9 15l-6 6", key: "chjx8e" }],
485
+ ["path", { d: "M15 4.2V9m0 0h4.8M15 9l6-6", key: "lav6yq" }],
486
+ ["path", { d: "M9 4.2V9m0 0H4.2M9 9 3 3", key: "1pxi2q" }]
487
+ ];
488
+ var Shrink = createLucideIcon("shrink", __iconNode11);
489
+
490
+ // ../../node_modules/lucide-react/dist/esm/icons/thumbs-up.js
491
+ var __iconNode12 = [
492
+ ["path", { d: "M7 10v12", key: "1qc93n" }],
493
+ [
494
+ "path",
495
+ {
496
+ d: "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z",
497
+ key: "emmmcr"
498
+ }
499
+ ]
500
+ ];
501
+ var ThumbsUp = createLucideIcon("thumbs-up", __iconNode12);
502
+
503
+ // ../../node_modules/lucide-react/dist/esm/icons/x.js
504
+ var __iconNode13 = [
505
+ ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
506
+ ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
507
+ ];
508
+ var X = createLucideIcon("x", __iconNode13);
509
+
510
+ // src/ScreenshotPreview.tsx
511
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
512
+ function ScreenshotPreview({
513
+ screenshots,
514
+ isLoading,
515
+ onConfirm,
516
+ onCancel,
517
+ onRetake,
518
+ isSubmitting
519
+ }) {
520
+ const [activeTab, setActiveTab] = useState("element");
521
+ const [isExpanded, setIsExpanded] = useState(false);
522
+ if (isLoading) {
523
+ return /* @__PURE__ */ jsx("div", { style: styles.container, children: /* @__PURE__ */ jsxs("div", { style: styles.loadingContainer, children: [
524
+ /* @__PURE__ */ jsx(
525
+ LoaderCircle,
526
+ {
527
+ className: "w-6 h-6 animate-spin",
528
+ style: { color: "#3b82f6" }
529
+ }
530
+ ),
531
+ /* @__PURE__ */ jsx("span", { style: styles.loadingText, children: "Capturing screenshots..." })
532
+ ] }) });
533
+ }
534
+ if (!screenshots) {
535
+ return /* @__PURE__ */ jsx("div", { style: styles.container, children: /* @__PURE__ */ jsxs("div", { style: styles.emptyContainer, children: [
536
+ /* @__PURE__ */ jsx(Image, { className: "w-8 h-8", style: { color: "#9ca3af" } }),
537
+ /* @__PURE__ */ jsx("span", { style: styles.emptyText, children: "No screenshots captured" }),
538
+ /* @__PURE__ */ jsxs(
539
+ "button",
540
+ {
541
+ type: "button",
542
+ onClick: onRetake,
543
+ style: styles.retakeButton,
544
+ disabled: isSubmitting,
545
+ children: [
546
+ /* @__PURE__ */ jsx(RefreshCw, { className: "w-4 h-4" }),
547
+ "Capture Screenshots"
548
+ ]
549
+ }
550
+ )
551
+ ] }) });
552
+ }
553
+ const allTabs = [
554
+ { key: "element", label: "Element", data: screenshots.element },
555
+ {
556
+ key: "container",
557
+ label: "Container",
558
+ data: screenshots.container
559
+ },
560
+ { key: "viewport", label: "Viewport", data: screenshots.viewport }
561
+ ];
562
+ const tabs = allTabs.filter((tab) => tab.data);
563
+ const activeScreenshot = activeTab === "element" ? screenshots.element : activeTab === "container" ? screenshots.container : screenshots.viewport;
564
+ const totalSize = estimateTotalSize(screenshots);
565
+ return /* @__PURE__ */ jsxs(
566
+ "div",
567
+ {
568
+ style: {
569
+ ...styles.container,
570
+ ...isExpanded ? styles.containerExpanded : {}
571
+ },
572
+ children: [
573
+ /* @__PURE__ */ jsxs("div", { style: styles.header, children: [
574
+ /* @__PURE__ */ jsx("span", { style: styles.headerTitle, children: "Screenshot Preview" }),
575
+ /* @__PURE__ */ jsxs("div", { style: styles.headerActions, children: [
576
+ /* @__PURE__ */ jsx("span", { style: styles.sizeLabel, children: formatBytes(totalSize) }),
577
+ /* @__PURE__ */ jsx(
578
+ "button",
579
+ {
580
+ type: "button",
581
+ onClick: () => setIsExpanded(!isExpanded),
582
+ style: styles.iconButton,
583
+ title: isExpanded ? "Collapse" : "Expand",
584
+ children: isExpanded ? /* @__PURE__ */ jsx(Shrink, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(Expand, { className: "w-4 h-4" })
585
+ }
586
+ )
587
+ ] })
588
+ ] }),
589
+ /* @__PURE__ */ jsx("div", { style: styles.tabContainer, children: tabs.map((tab) => /* @__PURE__ */ jsxs(
590
+ "button",
591
+ {
592
+ type: "button",
593
+ onClick: () => setActiveTab(tab.key),
594
+ style: {
595
+ ...styles.tab,
596
+ ...activeTab === tab.key ? styles.tabActive : {}
597
+ },
598
+ children: [
599
+ tab.label,
600
+ tab.data && /* @__PURE__ */ jsx("span", { style: styles.tabSize, children: formatBytes(tab.data.sizeBytes) })
601
+ ]
602
+ },
603
+ tab.key
604
+ )) }),
605
+ /* @__PURE__ */ jsx(
606
+ "div",
607
+ {
608
+ style: {
609
+ ...styles.previewContainer,
610
+ ...isExpanded ? styles.previewContainerExpanded : {}
611
+ },
612
+ children: activeScreenshot ? /* @__PURE__ */ jsx(
613
+ "img",
614
+ {
615
+ src: activeScreenshot.dataUrl,
616
+ alt: `${activeTab} screenshot`,
617
+ style: styles.previewImage
618
+ }
619
+ ) : /* @__PURE__ */ jsxs("div", { style: styles.noPreview, children: [
620
+ /* @__PURE__ */ jsx(Image, { className: "w-6 h-6", style: { color: "#9ca3af" } }),
621
+ /* @__PURE__ */ jsxs("span", { children: [
622
+ "No ",
623
+ activeTab,
624
+ " screenshot"
625
+ ] })
626
+ ] })
627
+ }
628
+ ),
629
+ activeScreenshot && /* @__PURE__ */ jsxs("div", { style: styles.dimensionsInfo, children: [
630
+ activeScreenshot.width,
631
+ " \xD7 ",
632
+ activeScreenshot.height,
633
+ "px"
634
+ ] }),
635
+ /* @__PURE__ */ jsxs("div", { style: styles.actions, children: [
636
+ /* @__PURE__ */ jsxs(
637
+ "button",
638
+ {
639
+ type: "button",
640
+ onClick: onRetake,
641
+ disabled: isSubmitting,
642
+ style: styles.retakeButtonSmall,
643
+ children: [
644
+ /* @__PURE__ */ jsx(RefreshCw, { className: "w-3 h-3" }),
645
+ "Retake"
646
+ ]
647
+ }
648
+ ),
649
+ /* @__PURE__ */ jsxs("div", { style: styles.actionsRight, children: [
650
+ /* @__PURE__ */ jsxs(
651
+ "button",
652
+ {
653
+ type: "button",
654
+ onClick: onCancel,
655
+ disabled: isSubmitting,
656
+ className: "flex items-center gap-1",
657
+ style: { ...menuStyles.button, ...menuStyles.cancelButton },
658
+ children: [
659
+ /* @__PURE__ */ jsx(X, { className: "w-3 h-3" }),
660
+ "Cancel"
661
+ ]
662
+ }
663
+ ),
664
+ /* @__PURE__ */ jsx(
665
+ "button",
666
+ {
667
+ type: "button",
668
+ onClick: () => onConfirm(screenshots),
669
+ disabled: isSubmitting,
670
+ style: {
671
+ ...menuStyles.button,
672
+ ...menuStyles.submitButton,
673
+ ...isSubmitting ? menuStyles.submitButtonDisabled : {}
674
+ },
675
+ children: isSubmitting ? /* @__PURE__ */ jsxs(Fragment, { children: [
676
+ /* @__PURE__ */ jsx(LoaderCircle, { className: "w-3 h-3 animate-spin" }),
677
+ "Sending..."
678
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
679
+ /* @__PURE__ */ jsx(Check, { className: "w-3 h-3" }),
680
+ "Send"
681
+ ] })
682
+ }
683
+ )
684
+ ] })
685
+ ] })
686
+ ]
687
+ }
688
+ );
689
+ }
690
+ var styles = {
691
+ container: {
692
+ width: "100%",
693
+ display: "flex",
694
+ flexDirection: "column",
695
+ gap: "8px"
696
+ },
697
+ containerExpanded: {
698
+ position: "fixed",
699
+ top: "50%",
700
+ left: "50%",
701
+ transform: "translate(-50%, -50%)",
702
+ width: "90vw",
703
+ maxWidth: "800px",
704
+ maxHeight: "90vh",
705
+ backgroundColor: "#fff",
706
+ borderRadius: "12px",
707
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
708
+ padding: "16px",
709
+ zIndex: 1e4
710
+ },
711
+ loadingContainer: {
712
+ display: "flex",
713
+ flexDirection: "column",
714
+ alignItems: "center",
715
+ justifyContent: "center",
716
+ gap: "12px",
717
+ padding: "24px"
718
+ },
719
+ loadingText: {
720
+ fontSize: "13px",
721
+ color: "#6b7280"
722
+ },
723
+ emptyContainer: {
724
+ display: "flex",
725
+ flexDirection: "column",
726
+ alignItems: "center",
727
+ justifyContent: "center",
728
+ gap: "8px",
729
+ padding: "24px"
730
+ },
731
+ emptyText: {
732
+ fontSize: "13px",
733
+ color: "#9ca3af"
734
+ },
735
+ header: {
736
+ display: "flex",
737
+ justifyContent: "space-between",
738
+ alignItems: "center",
739
+ padding: "0 4px"
740
+ },
741
+ headerTitle: {
742
+ fontSize: "12px",
743
+ fontWeight: "600",
744
+ color: "#374151",
745
+ textTransform: "uppercase",
746
+ letterSpacing: "0.05em"
747
+ },
748
+ headerActions: {
749
+ display: "flex",
750
+ alignItems: "center",
751
+ gap: "8px"
752
+ },
753
+ sizeLabel: {
754
+ fontSize: "11px",
755
+ color: "#9ca3af"
756
+ },
757
+ iconButton: {
758
+ display: "flex",
759
+ alignItems: "center",
760
+ justifyContent: "center",
761
+ width: "24px",
762
+ height: "24px",
763
+ border: "none",
764
+ background: "transparent",
765
+ borderRadius: "4px",
766
+ cursor: "pointer",
767
+ color: "#6b7280"
768
+ },
769
+ tabContainer: {
770
+ display: "flex",
771
+ gap: "4px",
772
+ borderBottom: "1px solid #e5e7eb",
773
+ paddingBottom: "8px"
774
+ },
775
+ tab: {
776
+ display: "flex",
777
+ alignItems: "center",
778
+ gap: "4px",
779
+ padding: "6px 10px",
780
+ fontSize: "12px",
781
+ color: "#6b7280",
782
+ background: "transparent",
783
+ border: "none",
784
+ borderRadius: "4px",
785
+ cursor: "pointer",
786
+ transition: "all 0.15s ease"
787
+ },
788
+ tabActive: {
789
+ backgroundColor: "#eff6ff",
790
+ color: "#3b82f6",
791
+ fontWeight: "500"
792
+ },
793
+ tabSize: {
794
+ fontSize: "10px",
795
+ color: "#9ca3af"
796
+ },
797
+ previewContainer: {
798
+ position: "relative",
799
+ width: "100%",
800
+ height: "150px",
801
+ backgroundColor: "#f9fafb",
802
+ borderRadius: "8px",
803
+ overflow: "hidden",
804
+ display: "flex",
805
+ alignItems: "center",
806
+ justifyContent: "center"
807
+ },
808
+ previewContainerExpanded: {
809
+ height: "60vh",
810
+ maxHeight: "500px"
811
+ },
812
+ previewImage: {
813
+ maxWidth: "100%",
814
+ maxHeight: "100%",
815
+ objectFit: "contain"
816
+ },
817
+ noPreview: {
818
+ display: "flex",
819
+ flexDirection: "column",
820
+ alignItems: "center",
821
+ gap: "8px",
822
+ fontSize: "12px",
823
+ color: "#9ca3af"
824
+ },
825
+ dimensionsInfo: {
826
+ fontSize: "11px",
827
+ color: "#9ca3af",
828
+ textAlign: "center"
829
+ },
830
+ actions: {
831
+ display: "flex",
832
+ justifyContent: "space-between",
833
+ alignItems: "center",
834
+ paddingTop: "8px",
835
+ borderTop: "1px solid #e5e7eb"
836
+ },
837
+ actionsRight: {
838
+ display: "flex",
839
+ gap: "8px"
840
+ },
841
+ retakeButton: {
842
+ display: "flex",
843
+ alignItems: "center",
844
+ gap: "6px",
845
+ padding: "8px 16px",
846
+ fontSize: "13px",
847
+ color: "#fff",
848
+ backgroundColor: "#3b82f6",
849
+ border: "none",
850
+ borderRadius: "6px",
851
+ cursor: "pointer",
852
+ fontWeight: "500"
853
+ },
854
+ retakeButtonSmall: {
855
+ display: "flex",
856
+ alignItems: "center",
857
+ gap: "4px",
858
+ padding: "4px 8px",
859
+ fontSize: "11px",
860
+ color: "#6b7280",
861
+ backgroundColor: "transparent",
862
+ border: "1px solid #e5e7eb",
863
+ borderRadius: "4px",
864
+ cursor: "pointer"
865
+ }
866
+ };
867
+
868
+ // src/ContextMenu.tsx
869
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
870
+ var defaultIcons = {
871
+ issue: /* @__PURE__ */ jsx2(Flag, { className: "w-4 h-4" }),
872
+ feature: /* @__PURE__ */ jsx2(Plus, { className: "w-4 h-4" }),
873
+ like: /* @__PURE__ */ jsx2(ThumbsUp, { className: "w-4 h-4" })
874
+ };
875
+ function MenuItem({
876
+ item,
877
+ onClick,
878
+ disabled,
879
+ hasChildren
880
+ }) {
881
+ const [isHovered, setIsHovered] = useState2(false);
882
+ return /* @__PURE__ */ jsxs2(
883
+ "button",
884
+ {
885
+ type: "button",
886
+ onClick,
887
+ disabled,
888
+ onMouseEnter: () => setIsHovered(true),
889
+ onMouseLeave: () => setIsHovered(false),
890
+ style: {
891
+ ...menuStyles.item,
892
+ ...isHovered ? menuStyles.itemHover : {},
893
+ ...disabled ? { opacity: 0.5, cursor: "not-allowed" } : {}
894
+ },
895
+ children: [
896
+ /* @__PURE__ */ jsx2("span", { style: menuStyles.itemIcon, children: item.icon ?? defaultIcons[item.type] }),
897
+ /* @__PURE__ */ jsx2("span", { style: { flex: 1 }, children: item.label }),
898
+ hasChildren && /* @__PURE__ */ jsx2(
899
+ ChevronRight,
900
+ {
901
+ className: "w-4 h-4",
902
+ style: { marginLeft: "auto", opacity: 0.5 }
903
+ }
904
+ )
905
+ ]
906
+ }
907
+ );
908
+ }
909
+ function BackButton({ onClick }) {
910
+ const [isHovered, setIsHovered] = useState2(false);
911
+ return /* @__PURE__ */ jsxs2(
912
+ "button",
913
+ {
914
+ type: "button",
915
+ onClick,
916
+ onMouseEnter: () => setIsHovered(true),
917
+ onMouseLeave: () => setIsHovered(false),
918
+ style: {
919
+ ...menuStyles.item,
920
+ ...isHovered ? menuStyles.itemHover : {},
921
+ borderBottom: "1px solid #e5e5e5",
922
+ marginBottom: "4px"
923
+ },
924
+ children: [
925
+ /* @__PURE__ */ jsx2(ChevronLeft, { className: "w-4 h-4", style: { opacity: 0.5 } }),
926
+ /* @__PURE__ */ jsx2("span", { style: { opacity: 0.7 }, children: "Back" })
927
+ ]
928
+ }
929
+ );
930
+ }
931
+ function CommentForm({
932
+ onSubmit,
933
+ onCancel,
934
+ isSubmitting
935
+ }) {
936
+ const [comment, setComment] = useState2("");
937
+ const inputRef = useRef(null);
938
+ useEffect(() => {
939
+ inputRef.current?.focus();
940
+ }, []);
941
+ const handleSubmit = () => {
942
+ onSubmit(comment);
943
+ };
944
+ const handleKeyDown = (e) => {
945
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
946
+ handleSubmit();
947
+ } else if (e.key === "Escape") {
948
+ onCancel();
949
+ }
950
+ };
951
+ return /* @__PURE__ */ jsxs2("div", { style: menuStyles.commentSection, children: [
952
+ /* @__PURE__ */ jsx2(
953
+ "textarea",
954
+ {
955
+ ref: inputRef,
956
+ value: comment,
957
+ onChange: (e) => setComment(e.target.value),
958
+ onKeyDown: handleKeyDown,
959
+ placeholder: "Add a comment (optional)...",
960
+ style: menuStyles.commentInput,
961
+ disabled: isSubmitting
962
+ }
963
+ ),
964
+ /* @__PURE__ */ jsxs2("div", { style: menuStyles.buttonRow, children: [
965
+ /* @__PURE__ */ jsx2(
966
+ "button",
967
+ {
968
+ type: "button",
969
+ onClick: onCancel,
970
+ disabled: isSubmitting,
971
+ style: { ...menuStyles.button, ...menuStyles.cancelButton },
972
+ children: "Cancel"
973
+ }
974
+ ),
975
+ /* @__PURE__ */ jsx2(
976
+ "button",
977
+ {
978
+ type: "button",
979
+ onClick: handleSubmit,
980
+ disabled: isSubmitting,
981
+ style: {
982
+ ...menuStyles.button,
983
+ ...menuStyles.submitButton,
984
+ ...isSubmitting ? menuStyles.submitButtonDisabled : {}
985
+ },
986
+ children: isSubmitting ? "Sending..." : "Send"
987
+ }
988
+ )
989
+ ] })
990
+ ] });
991
+ }
992
+ function ContextMenu({
993
+ visible,
994
+ position,
995
+ targetElement,
996
+ containerElement,
997
+ items,
998
+ onSelect,
999
+ onClose,
1000
+ isSubmitting,
1001
+ style,
1002
+ className,
1003
+ highlightConfig,
1004
+ screenshotConfig
1005
+ }) {
1006
+ const [selectedType, setSelectedType] = useState2(null);
1007
+ const [currentView, setCurrentView] = useState2("menu");
1008
+ const [pendingComment, setPendingComment] = useState2();
1009
+ const [submenuStack, setSubmenuStack] = useState2([]);
1010
+ const [screenshots, setScreenshots] = useState2(null);
1011
+ const [isCapturing, setIsCapturing] = useState2(false);
1012
+ const menuRef = useRef(null);
1013
+ const mergedScreenshotConfig = {
1014
+ ...DEFAULT_SCREENSHOT_CONFIG,
1015
+ ...screenshotConfig
1016
+ };
1017
+ const showPreview = mergedScreenshotConfig.showPreview && isScreenshotSupported();
1018
+ const currentItems = submenuStack.length > 0 ? submenuStack[submenuStack.length - 1] : items;
1019
+ const captureScreenshots = useCallback(async () => {
1020
+ if (!targetElement || !showPreview) return;
1021
+ setIsCapturing(true);
1022
+ try {
1023
+ const captured = await captureAllScreenshots(
1024
+ targetElement,
1025
+ containerElement,
1026
+ mergedScreenshotConfig
1027
+ );
1028
+ setScreenshots(captured);
1029
+ } catch (error) {
1030
+ console.error("Failed to capture screenshots:", error);
1031
+ setScreenshots(null);
1032
+ } finally {
1033
+ setIsCapturing(false);
1034
+ }
1035
+ }, [targetElement, containerElement, mergedScreenshotConfig, showPreview]);
1036
+ useEffect(() => {
1037
+ if (!visible) {
1038
+ setSelectedType(null);
1039
+ setCurrentView("menu");
1040
+ setPendingComment(void 0);
1041
+ setSubmenuStack([]);
1042
+ setScreenshots(null);
1043
+ setIsCapturing(false);
1044
+ }
1045
+ }, [visible]);
1046
+ useEffect(() => {
1047
+ if (visible && targetElement) {
1048
+ clearHighlights();
1049
+ applyHighlights(targetElement, highlightConfig);
1050
+ } else {
1051
+ clearHighlights();
1052
+ }
1053
+ return () => {
1054
+ clearHighlights();
1055
+ };
1056
+ }, [visible, targetElement, highlightConfig]);
1057
+ useEffect(() => {
1058
+ if (!visible) {
1059
+ return;
1060
+ }
1061
+ const handlePointerDown = (event) => {
1062
+ if (!menuRef.current) return;
1063
+ const target = event.target;
1064
+ if (!menuRef.current.contains(target)) {
1065
+ onClose();
1066
+ }
1067
+ };
1068
+ document.addEventListener("pointerdown", handlePointerDown);
1069
+ return () => {
1070
+ document.removeEventListener("pointerdown", handlePointerDown);
1071
+ };
1072
+ }, [visible, onClose]);
1073
+ useEffect(() => {
1074
+ if (visible && menuRef.current) {
1075
+ const rect = menuRef.current.getBoundingClientRect();
1076
+ const viewportWidth = window.innerWidth;
1077
+ const viewportHeight = window.innerHeight;
1078
+ let adjustedX = position.x;
1079
+ let adjustedY = position.y;
1080
+ if (position.x + rect.width > viewportWidth) {
1081
+ adjustedX = viewportWidth - rect.width - 10;
1082
+ }
1083
+ if (position.y + rect.height > viewportHeight) {
1084
+ adjustedY = viewportHeight - rect.height - 10;
1085
+ }
1086
+ if (adjustedX !== position.x || adjustedY !== position.y) {
1087
+ menuRef.current.style.left = `${adjustedX}px`;
1088
+ menuRef.current.style.top = `${adjustedY}px`;
1089
+ }
1090
+ }
1091
+ }, [visible, position]);
1092
+ useEffect(() => {
1093
+ const handleKeyDown = (e) => {
1094
+ if (e.key === "Escape") {
1095
+ if (currentView === "screenshot-preview") {
1096
+ setCurrentView("comment");
1097
+ } else if (currentView === "comment") {
1098
+ setCurrentView("menu");
1099
+ setSelectedType(null);
1100
+ setPendingComment(void 0);
1101
+ } else if (submenuStack.length > 0) {
1102
+ setSubmenuStack((prev) => prev.slice(0, -1));
1103
+ } else {
1104
+ onClose();
1105
+ }
1106
+ }
1107
+ };
1108
+ if (visible) {
1109
+ document.addEventListener("keydown", handleKeyDown);
1110
+ return () => document.removeEventListener("keydown", handleKeyDown);
1111
+ }
1112
+ }, [visible, currentView, submenuStack.length, onClose]);
1113
+ if (!visible || !targetElement) {
1114
+ return null;
1115
+ }
1116
+ const handleItemClick = (item) => {
1117
+ if (item.children && item.children.length > 0) {
1118
+ setSubmenuStack((prev) => [...prev, item.children]);
1119
+ return;
1120
+ }
1121
+ if (item.showComment) {
1122
+ setSelectedType(item.type);
1123
+ setCurrentView("comment");
1124
+ } else {
1125
+ if (showPreview) {
1126
+ setSelectedType(item.type);
1127
+ setCurrentView("screenshot-preview");
1128
+ captureScreenshots();
1129
+ } else {
1130
+ onSelect(item.type);
1131
+ }
1132
+ }
1133
+ };
1134
+ const handleBack = () => {
1135
+ setSubmenuStack((prev) => prev.slice(0, -1));
1136
+ };
1137
+ const handleCommentSubmit = (comment) => {
1138
+ if (!selectedType) return;
1139
+ if (showPreview) {
1140
+ setPendingComment(comment || void 0);
1141
+ setCurrentView("screenshot-preview");
1142
+ captureScreenshots();
1143
+ } else {
1144
+ onSelect(selectedType, comment || void 0);
1145
+ }
1146
+ };
1147
+ const handleCommentCancel = () => {
1148
+ setCurrentView("menu");
1149
+ setSelectedType(null);
1150
+ setPendingComment(void 0);
1151
+ };
1152
+ const handleScreenshotConfirm = (confirmedScreenshots) => {
1153
+ if (selectedType) {
1154
+ onSelect(selectedType, pendingComment, confirmedScreenshots);
1155
+ }
1156
+ };
1157
+ const handleScreenshotCancel = () => {
1158
+ if (pendingComment !== void 0) {
1159
+ setCurrentView("comment");
1160
+ } else {
1161
+ setCurrentView("menu");
1162
+ setSelectedType(null);
1163
+ }
1164
+ setScreenshots(null);
1165
+ };
1166
+ const handleRetakeScreenshots = () => {
1167
+ captureScreenshots();
1168
+ };
1169
+ const containerWidth = currentView === "screenshot-preview" ? 360 : void 0;
1170
+ return /* @__PURE__ */ jsxs2(
1171
+ "div",
1172
+ {
1173
+ ref: menuRef,
1174
+ className,
1175
+ style: {
1176
+ ...menuStyles.container,
1177
+ left: position.x,
1178
+ top: position.y,
1179
+ ...containerWidth ? { width: containerWidth, minWidth: containerWidth } : {},
1180
+ ...style
1181
+ },
1182
+ role: "menu",
1183
+ "aria-label": "Feedback options",
1184
+ children: [
1185
+ /* @__PURE__ */ jsx2("div", { style: menuStyles.header, children: currentView === "screenshot-preview" ? "Review Screenshots" : "Send Feedback" }),
1186
+ currentView === "menu" && /* @__PURE__ */ jsxs2("div", { style: menuStyles.itemList, children: [
1187
+ submenuStack.length > 0 && /* @__PURE__ */ jsx2(BackButton, { onClick: handleBack }),
1188
+ currentItems.map((item) => /* @__PURE__ */ jsx2(
1189
+ MenuItem,
1190
+ {
1191
+ item,
1192
+ onClick: () => handleItemClick(item),
1193
+ disabled: isSubmitting,
1194
+ hasChildren: item.children && item.children.length > 0
1195
+ },
1196
+ item.type
1197
+ )),
1198
+ showPreview && /* @__PURE__ */ jsxs2("div", { style: screenshotIndicatorStyle, children: [
1199
+ /* @__PURE__ */ jsx2(Camera, { className: "w-3 h-3" }),
1200
+ /* @__PURE__ */ jsx2("span", { children: "Screenshots will be captured" })
1201
+ ] })
1202
+ ] }),
1203
+ currentView === "comment" && /* @__PURE__ */ jsx2(
1204
+ CommentForm,
1205
+ {
1206
+ onSubmit: handleCommentSubmit,
1207
+ onCancel: handleCommentCancel,
1208
+ isSubmitting
1209
+ }
1210
+ ),
1211
+ currentView === "screenshot-preview" && /* @__PURE__ */ jsx2(
1212
+ ScreenshotPreview,
1213
+ {
1214
+ screenshots,
1215
+ isLoading: isCapturing,
1216
+ onConfirm: handleScreenshotConfirm,
1217
+ onCancel: handleScreenshotCancel,
1218
+ onRetake: handleRetakeScreenshots,
1219
+ isSubmitting
1220
+ }
1221
+ )
1222
+ ]
1223
+ }
1224
+ );
1225
+ }
1226
+ var screenshotIndicatorStyle = {
1227
+ display: "flex",
1228
+ alignItems: "center",
1229
+ gap: "6px",
1230
+ padding: "8px 12px",
1231
+ fontSize: "11px",
1232
+ color: "#9ca3af",
1233
+ borderTop: "1px solid #f3f4f6",
1234
+ marginTop: "4px"
1235
+ };
1236
+
1237
+ // src/FeedbackProvider.tsx
1238
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1239
+ var defaultMenuItems = [
1240
+ { type: "issue", label: "Report an issue", showComment: true },
1241
+ { type: "feature", label: "Request a feature", showComment: true },
1242
+ { type: "like", label: "I like this!", showComment: false }
1243
+ ];
1244
+ function FeedbackProvider({
1245
+ adapter,
1246
+ children,
1247
+ targetFilter,
1248
+ menuItems = defaultMenuItems,
1249
+ maxInnerTextLength,
1250
+ maxOuterHTMLLength,
1251
+ maxAncestors,
1252
+ cooldownMs,
1253
+ stripAttributes,
1254
+ metadata,
1255
+ onSubmitSuccess,
1256
+ onSubmitError,
1257
+ menuStyle,
1258
+ menuClassName,
1259
+ disabled = false,
1260
+ highlightConfig,
1261
+ screenshotConfig
1262
+ }) {
1263
+ const [isSubmitting, setIsSubmitting] = useState3(false);
1264
+ const [menuVisible, setMenuVisible] = useState3(false);
1265
+ const [menuPosition, setMenuPosition] = useState3({ x: 0, y: 0 });
1266
+ const [targetElement, setTargetElement] = useState3(null);
1267
+ const [containerElement, setContainerElement] = useState3(
1268
+ null
1269
+ );
1270
+ const clientRef = useRef2(null);
1271
+ useEffect2(() => {
1272
+ const client = createFeedbackClient({
1273
+ adapter,
1274
+ targetFilter,
1275
+ maxInnerTextLength,
1276
+ maxOuterHTMLLength,
1277
+ maxAncestors,
1278
+ cooldownMs,
1279
+ stripAttributes
1280
+ });
1281
+ client.onSubmitSuccess = onSubmitSuccess;
1282
+ client.onSubmitError = onSubmitError;
1283
+ client.onContextMenu = (event, element) => {
1284
+ setTargetElement(element);
1285
+ const container = findContainerParent(element, highlightConfig);
1286
+ setContainerElement(container);
1287
+ setMenuPosition({ x: event.clientX, y: event.clientY });
1288
+ setMenuVisible(true);
1289
+ };
1290
+ clientRef.current = client;
1291
+ if (!disabled) {
1292
+ client.attach();
1293
+ }
1294
+ return () => {
1295
+ client.detach();
1296
+ };
1297
+ }, [
1298
+ adapter,
1299
+ targetFilter,
1300
+ maxInnerTextLength,
1301
+ maxOuterHTMLLength,
1302
+ maxAncestors,
1303
+ cooldownMs,
1304
+ stripAttributes,
1305
+ onSubmitSuccess,
1306
+ onSubmitError,
1307
+ disabled,
1308
+ highlightConfig
1309
+ ]);
1310
+ const submitFeedback = useCallback2(
1311
+ async (element, type, comment, screenshots) => {
1312
+ const client = clientRef.current;
1313
+ if (!client) return;
1314
+ setIsSubmitting(true);
1315
+ try {
1316
+ await client.submitFeedback(element, type, {
1317
+ comment,
1318
+ metadata,
1319
+ screenshots
1320
+ });
1321
+ } finally {
1322
+ setIsSubmitting(false);
1323
+ setMenuVisible(false);
1324
+ setTargetElement(null);
1325
+ setContainerElement(null);
1326
+ }
1327
+ },
1328
+ [metadata]
1329
+ );
1330
+ const openMenu = useCallback2(
1331
+ (element, position) => {
1332
+ setTargetElement(element);
1333
+ const container = findContainerParent(element, highlightConfig);
1334
+ setContainerElement(container);
1335
+ setMenuPosition(position);
1336
+ setMenuVisible(true);
1337
+ },
1338
+ [highlightConfig]
1339
+ );
1340
+ const closeMenu = useCallback2(() => {
1341
+ setMenuVisible(false);
1342
+ setTargetElement(null);
1343
+ setContainerElement(null);
1344
+ }, []);
1345
+ const handleMenuSelect = useCallback2(
1346
+ (type, comment, screenshots) => {
1347
+ if (targetElement) {
1348
+ submitFeedback(targetElement, type, comment, screenshots);
1349
+ }
1350
+ },
1351
+ [targetElement, submitFeedback]
1352
+ );
1353
+ const contextValue = useMemo(
1354
+ () => ({
1355
+ isEnabled: !disabled,
1356
+ isSubmitting,
1357
+ submitFeedback,
1358
+ openMenu,
1359
+ closeMenu
1360
+ }),
1361
+ [disabled, isSubmitting, submitFeedback, openMenu, closeMenu]
1362
+ );
1363
+ return /* @__PURE__ */ jsxs3(FeedbackContext.Provider, { value: contextValue, children: [
1364
+ children,
1365
+ /* @__PURE__ */ jsx3(
1366
+ ContextMenu,
1367
+ {
1368
+ visible: menuVisible && !disabled,
1369
+ position: menuPosition,
1370
+ targetElement,
1371
+ containerElement,
1372
+ items: menuItems,
1373
+ onSelect: handleMenuSelect,
1374
+ onClose: closeMenu,
1375
+ isSubmitting,
1376
+ style: menuStyle,
1377
+ className: menuClassName,
1378
+ highlightConfig,
1379
+ screenshotConfig
1380
+ }
1381
+ )
1382
+ ] });
1383
+ }
1384
+
1385
+ // src/types.ts
1386
+ function filterMenuItemsByRole(items, userContext) {
1387
+ if (!userContext) {
1388
+ return items.filter(
1389
+ (item) => !item.requiredRoles || item.requiredRoles.length === 0
1390
+ );
1391
+ }
1392
+ const userRoles = userContext.roles ?? [];
1393
+ return items.filter((item) => {
1394
+ if (!item.requiredRoles || item.requiredRoles.length === 0) {
1395
+ return true;
1396
+ }
1397
+ return item.requiredRoles.some((role) => userRoles.includes(role));
1398
+ });
1399
+ }
1400
+
1401
+ // src/index.ts
1402
+ import {
1403
+ captureAllScreenshots as captureAllScreenshots2,
1404
+ captureScreenshot,
1405
+ isScreenshotSupported as isScreenshotSupported2,
1406
+ formatBytes as formatBytes2,
1407
+ estimateTotalSize as estimateTotalSize2,
1408
+ DEFAULT_SCREENSHOT_CONFIG as DEFAULT_SCREENSHOT_CONFIG2,
1409
+ DEFAULT_SENSITIVE_SELECTORS
1410
+ } from "@ewjdev/anyclick-core";
1411
+ export {
1412
+ ContextMenu,
1413
+ DEFAULT_SCREENSHOT_CONFIG2 as DEFAULT_SCREENSHOT_CONFIG,
1414
+ DEFAULT_SENSITIVE_SELECTORS,
1415
+ FeedbackContext,
1416
+ FeedbackProvider,
1417
+ ScreenshotPreview,
1418
+ applyHighlights,
1419
+ captureAllScreenshots2 as captureAllScreenshots,
1420
+ captureScreenshot,
1421
+ clearHighlights,
1422
+ darkMenuStyles,
1423
+ defaultContainerSelectors,
1424
+ defaultHighlightColors,
1425
+ estimateTotalSize2 as estimateTotalSize,
1426
+ filterMenuItemsByRole,
1427
+ findContainerParent,
1428
+ formatBytes2 as formatBytes,
1429
+ highlightContainer,
1430
+ highlightTarget,
1431
+ isScreenshotSupported2 as isScreenshotSupported,
1432
+ menuStyles,
1433
+ useFeedback
1434
+ };
1435
+ /*! Bundled license information:
1436
+
1437
+ lucide-react/dist/esm/shared/src/utils.js:
1438
+ lucide-react/dist/esm/defaultAttributes.js:
1439
+ lucide-react/dist/esm/Icon.js:
1440
+ lucide-react/dist/esm/createLucideIcon.js:
1441
+ lucide-react/dist/esm/icons/camera.js:
1442
+ lucide-react/dist/esm/icons/check.js:
1443
+ lucide-react/dist/esm/icons/chevron-left.js:
1444
+ lucide-react/dist/esm/icons/chevron-right.js:
1445
+ lucide-react/dist/esm/icons/expand.js:
1446
+ lucide-react/dist/esm/icons/flag.js:
1447
+ lucide-react/dist/esm/icons/image.js:
1448
+ lucide-react/dist/esm/icons/loader-circle.js:
1449
+ lucide-react/dist/esm/icons/plus.js:
1450
+ lucide-react/dist/esm/icons/refresh-cw.js:
1451
+ lucide-react/dist/esm/icons/shrink.js:
1452
+ lucide-react/dist/esm/icons/thumbs-up.js:
1453
+ lucide-react/dist/esm/icons/x.js:
1454
+ lucide-react/dist/esm/lucide-react.js:
1455
+ (**
1456
+ * @license lucide-react v0.544.0 - ISC
1457
+ *
1458
+ * This source code is licensed under the ISC license.
1459
+ * See the LICENSE file in the root directory of this source tree.
1460
+ *)
1461
+ */
1462
+ //# sourceMappingURL=index.mjs.map