@ewjdev/anyclick-react 1.1.0 → 1.2.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
@@ -28,6 +28,7 @@ __export(index_exports, {
28
28
  DEFAULT_SENSITIVE_SELECTORS: () => import_anyclick_core4.DEFAULT_SENSITIVE_SELECTORS,
29
29
  FeedbackContext: () => FeedbackContext,
30
30
  FeedbackProvider: () => FeedbackProvider,
31
+ FunModeBridge: () => FunModeBridge,
31
32
  ScreenshotPreview: () => ScreenshotPreview,
32
33
  applyHighlights: () => applyHighlights,
33
34
  captureAllScreenshots: () => import_anyclick_core4.captureAllScreenshots,
@@ -120,16 +121,19 @@ var menuStyles = {
120
121
  ...menuCSSVariables
121
122
  },
122
123
  header: {
123
- padding: "12px 16px",
124
+ padding: "8px 12px",
124
125
  borderBottom: "1px solid var(--anyclick-menu-border, #e5e5e5)",
125
126
  color: "var(--anyclick-menu-text-muted, #666)",
126
127
  fontSize: "12px",
127
128
  fontWeight: 500,
128
129
  textTransform: "uppercase",
129
- letterSpacing: "0.5px"
130
+ letterSpacing: "0.5px",
131
+ display: "flex",
132
+ justifyContent: "space-between",
133
+ alignItems: "center"
130
134
  },
131
135
  itemList: {
132
- padding: "4px 0"
136
+ padding: "0px 0px"
133
137
  },
134
138
  item: {
135
139
  display: "flex",
@@ -531,6 +535,7 @@ function generateHighlightCSS(colors) {
531
535
  border-radius: 4px !important;
532
536
  position: relative;
533
537
  z-index: 9997;
538
+ top: 0;
534
539
  }
535
540
 
536
541
  .${HIGHLIGHT_CONTAINER_CLASS} {
@@ -773,45 +778,56 @@ var __iconNode7 = [
773
778
  ];
774
779
  var Flag = createLucideIcon("flag", __iconNode7);
775
780
 
776
- // ../../node_modules/lucide-react/dist/esm/icons/image.js
781
+ // ../../node_modules/lucide-react/dist/esm/icons/grip-vertical.js
777
782
  var __iconNode8 = [
783
+ ["circle", { cx: "9", cy: "12", r: "1", key: "1vctgf" }],
784
+ ["circle", { cx: "9", cy: "5", r: "1", key: "hp0tcf" }],
785
+ ["circle", { cx: "9", cy: "19", r: "1", key: "fkjjf6" }],
786
+ ["circle", { cx: "15", cy: "12", r: "1", key: "1tmaij" }],
787
+ ["circle", { cx: "15", cy: "5", r: "1", key: "19l28e" }],
788
+ ["circle", { cx: "15", cy: "19", r: "1", key: "f4zoj3" }]
789
+ ];
790
+ var GripVertical = createLucideIcon("grip-vertical", __iconNode8);
791
+
792
+ // ../../node_modules/lucide-react/dist/esm/icons/image.js
793
+ var __iconNode9 = [
778
794
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
779
795
  ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
780
796
  ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
781
797
  ];
782
- var Image = createLucideIcon("image", __iconNode8);
798
+ var Image = createLucideIcon("image", __iconNode9);
783
799
 
784
800
  // ../../node_modules/lucide-react/dist/esm/icons/loader-circle.js
785
- var __iconNode9 = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
786
- var LoaderCircle = createLucideIcon("loader-circle", __iconNode9);
801
+ var __iconNode10 = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
802
+ var LoaderCircle = createLucideIcon("loader-circle", __iconNode10);
787
803
 
788
804
  // ../../node_modules/lucide-react/dist/esm/icons/plus.js
789
- var __iconNode10 = [
805
+ var __iconNode11 = [
790
806
  ["path", { d: "M5 12h14", key: "1ays0h" }],
791
807
  ["path", { d: "M12 5v14", key: "s699le" }]
792
808
  ];
793
- var Plus = createLucideIcon("plus", __iconNode10);
809
+ var Plus = createLucideIcon("plus", __iconNode11);
794
810
 
795
811
  // ../../node_modules/lucide-react/dist/esm/icons/refresh-cw.js
796
- var __iconNode11 = [
812
+ var __iconNode12 = [
797
813
  ["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }],
798
814
  ["path", { d: "M21 3v5h-5", key: "1q7to0" }],
799
815
  ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }],
800
816
  ["path", { d: "M8 16H3v5", key: "1cv678" }]
801
817
  ];
802
- var RefreshCw = createLucideIcon("refresh-cw", __iconNode11);
818
+ var RefreshCw = createLucideIcon("refresh-cw", __iconNode12);
803
819
 
804
820
  // ../../node_modules/lucide-react/dist/esm/icons/shrink.js
805
- var __iconNode12 = [
821
+ var __iconNode13 = [
806
822
  ["path", { d: "m15 15 6 6m-6-6v4.8m0-4.8h4.8", key: "17vawe" }],
807
823
  ["path", { d: "M9 19.8V15m0 0H4.2M9 15l-6 6", key: "chjx8e" }],
808
824
  ["path", { d: "M15 4.2V9m0 0h4.8M15 9l6-6", key: "lav6yq" }],
809
825
  ["path", { d: "M9 4.2V9m0 0H4.2M9 9 3 3", key: "1pxi2q" }]
810
826
  ];
811
- var Shrink = createLucideIcon("shrink", __iconNode12);
827
+ var Shrink = createLucideIcon("shrink", __iconNode13);
812
828
 
813
829
  // ../../node_modules/lucide-react/dist/esm/icons/thumbs-up.js
814
- var __iconNode13 = [
830
+ var __iconNode14 = [
815
831
  ["path", { d: "M7 10v12", key: "1qc93n" }],
816
832
  [
817
833
  "path",
@@ -821,14 +837,14 @@ var __iconNode13 = [
821
837
  }
822
838
  ]
823
839
  ];
824
- var ThumbsUp = createLucideIcon("thumbs-up", __iconNode13);
840
+ var ThumbsUp = createLucideIcon("thumbs-up", __iconNode14);
825
841
 
826
842
  // ../../node_modules/lucide-react/dist/esm/icons/x.js
827
- var __iconNode14 = [
843
+ var __iconNode15 = [
828
844
  ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
829
845
  ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
830
846
  ];
831
- var X = createLucideIcon("x", __iconNode14);
847
+ var X = createLucideIcon("x", __iconNode15);
832
848
 
833
849
  // src/ScreenshotPreview.tsx
834
850
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -1058,11 +1074,30 @@ function ScreenshotPreview({
1058
1074
 
1059
1075
  // src/ContextMenu.tsx
1060
1076
  var import_jsx_runtime2 = require("react/jsx-runtime");
1077
+ var VIEWPORT_PADDING = 10;
1078
+ var screenshotIndicatorStyle = {
1079
+ display: "flex",
1080
+ marginLeft: "4px",
1081
+ opacity: 0.7,
1082
+ justifyContent: "flex-end",
1083
+ flex: 1
1084
+ };
1061
1085
  var defaultIcons = {
1062
1086
  issue: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Flag, { className: "w-4 h-4" }),
1063
1087
  feature: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Plus, { className: "w-4 h-4" }),
1064
1088
  like: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ThumbsUp, { className: "w-4 h-4" })
1065
1089
  };
1090
+ var DefaultHeader = ({
1091
+ styles,
1092
+ className,
1093
+ title = "Send Feedback",
1094
+ children
1095
+ }) => {
1096
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles, className, children: [
1097
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: title }),
1098
+ children
1099
+ ] });
1100
+ };
1066
1101
  function MenuItem({
1067
1102
  item,
1068
1103
  onClick,
@@ -1070,6 +1105,7 @@ function MenuItem({
1070
1105
  hasChildren
1071
1106
  }) {
1072
1107
  const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
1108
+ const [isPressed, setIsPressed] = (0, import_react5.useState)(false);
1073
1109
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1074
1110
  "button",
1075
1111
  {
@@ -1077,11 +1113,25 @@ function MenuItem({
1077
1113
  onClick,
1078
1114
  disabled,
1079
1115
  onMouseEnter: () => setIsHovered(true),
1080
- onMouseLeave: () => setIsHovered(false),
1116
+ onMouseLeave: () => {
1117
+ setIsHovered(false);
1118
+ setIsPressed(false);
1119
+ },
1120
+ onTouchStart: () => setIsPressed(true),
1121
+ onTouchEnd: () => setIsPressed(false),
1122
+ onTouchCancel: () => setIsPressed(false),
1081
1123
  style: {
1082
1124
  ...menuStyles.item,
1083
- ...isHovered ? menuStyles.itemHover : {},
1084
- ...disabled ? { opacity: 0.5, cursor: "not-allowed" } : {}
1125
+ // Apply hover/pressed state for both mouse and touch
1126
+ ...isHovered || isPressed ? menuStyles.itemHover : {},
1127
+ ...disabled ? { opacity: 0.5, cursor: "not-allowed" } : {},
1128
+ // Ensure minimum touch target size (44px is recommended)
1129
+ minHeight: "44px",
1130
+ // Prevent text selection on touch
1131
+ WebkitUserSelect: "none",
1132
+ userSelect: "none",
1133
+ // Prevent touch callout on iOS
1134
+ WebkitTouchCallout: "none"
1085
1135
  },
1086
1136
  children: [
1087
1137
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: menuStyles.itemIcon, children: item.icon ?? defaultIcons[item.type] }),
@@ -1099,18 +1149,31 @@ function MenuItem({
1099
1149
  }
1100
1150
  function BackButton({ onClick }) {
1101
1151
  const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
1152
+ const [isPressed, setIsPressed] = (0, import_react5.useState)(false);
1102
1153
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1103
1154
  "button",
1104
1155
  {
1105
1156
  type: "button",
1106
1157
  onClick,
1107
1158
  onMouseEnter: () => setIsHovered(true),
1108
- onMouseLeave: () => setIsHovered(false),
1159
+ onMouseLeave: () => {
1160
+ setIsHovered(false);
1161
+ setIsPressed(false);
1162
+ },
1163
+ onTouchStart: () => setIsPressed(true),
1164
+ onTouchEnd: () => setIsPressed(false),
1165
+ onTouchCancel: () => setIsPressed(false),
1109
1166
  style: {
1110
1167
  ...menuStyles.item,
1111
- ...isHovered ? menuStyles.itemHover : {},
1168
+ ...isHovered || isPressed ? menuStyles.itemHover : {},
1112
1169
  borderBottom: "1px solid #e5e5e5",
1113
- marginBottom: "4px"
1170
+ marginBottom: "4px",
1171
+ // Ensure minimum touch target size
1172
+ minHeight: "44px",
1173
+ // Prevent text selection on touch
1174
+ WebkitUserSelect: "none",
1175
+ userSelect: "none",
1176
+ WebkitTouchCallout: "none"
1114
1177
  },
1115
1178
  children: [
1116
1179
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChevronLeft, { className: "w-4 h-4", style: { opacity: 0.5 } }),
@@ -1180,6 +1243,25 @@ function CommentForm({
1180
1243
  ] })
1181
1244
  ] });
1182
1245
  }
1246
+ function calculateInViewPosition(requestedX, requestedY, menuWidth, menuHeight) {
1247
+ const viewportWidth = window.innerWidth;
1248
+ const viewportHeight = window.innerHeight;
1249
+ let x = requestedX;
1250
+ let y = requestedY;
1251
+ if (x + menuWidth > viewportWidth - VIEWPORT_PADDING) {
1252
+ x = viewportWidth - menuWidth - VIEWPORT_PADDING;
1253
+ }
1254
+ if (x < VIEWPORT_PADDING) {
1255
+ x = VIEWPORT_PADDING;
1256
+ }
1257
+ if (y + menuHeight > viewportHeight - VIEWPORT_PADDING) {
1258
+ y = viewportHeight - menuHeight - VIEWPORT_PADDING;
1259
+ }
1260
+ if (y < VIEWPORT_PADDING) {
1261
+ y = VIEWPORT_PADDING;
1262
+ }
1263
+ return { x, y };
1264
+ }
1183
1265
  function ContextMenu({
1184
1266
  visible,
1185
1267
  position,
@@ -1192,7 +1274,10 @@ function ContextMenu({
1192
1274
  style,
1193
1275
  className,
1194
1276
  highlightConfig,
1195
- screenshotConfig
1277
+ screenshotConfig,
1278
+ positionMode = "inView",
1279
+ header,
1280
+ footer
1196
1281
  }) {
1197
1282
  const [selectedType, setSelectedType] = (0, import_react5.useState)(null);
1198
1283
  const [currentView, setCurrentView] = (0, import_react5.useState)("menu");
@@ -1201,6 +1286,13 @@ function ContextMenu({
1201
1286
  const [screenshots, setScreenshots] = (0, import_react5.useState)(null);
1202
1287
  const [isCapturing, setIsCapturing] = (0, import_react5.useState)(false);
1203
1288
  const menuRef = (0, import_react5.useRef)(null);
1289
+ const [adjustedPosition, setAdjustedPosition] = (0, import_react5.useState)(position);
1290
+ const [isDragging, setIsDragging] = (0, import_react5.useState)(false);
1291
+ const [dragOffset, setDragOffset] = (0, import_react5.useState)({
1292
+ x: 0,
1293
+ y: 0
1294
+ });
1295
+ const dragStartRef = (0, import_react5.useRef)(null);
1204
1296
  const mergedScreenshotConfig = {
1205
1297
  ...import_anyclick_core2.DEFAULT_SCREENSHOT_CONFIG,
1206
1298
  ...screenshotConfig
@@ -1232,6 +1324,9 @@ function ContextMenu({
1232
1324
  setSubmenuStack([]);
1233
1325
  setScreenshots(null);
1234
1326
  setIsCapturing(false);
1327
+ setIsDragging(false);
1328
+ setDragOffset({ x: 0, y: 0 });
1329
+ dragStartRef.current = null;
1235
1330
  }
1236
1331
  }, [visible]);
1237
1332
  (0, import_react5.useEffect)(() => {
@@ -1252,6 +1347,9 @@ function ContextMenu({
1252
1347
  const handlePointerDown = (event) => {
1253
1348
  if (!menuRef.current) return;
1254
1349
  const target = event.target;
1350
+ if (target.closest?.("[data-drag-handle]")) {
1351
+ return;
1352
+ }
1255
1353
  if (!menuRef.current.contains(target)) {
1256
1354
  onClose();
1257
1355
  }
@@ -1262,24 +1360,71 @@ function ContextMenu({
1262
1360
  };
1263
1361
  }, [visible, onClose]);
1264
1362
  (0, import_react5.useEffect)(() => {
1265
- if (visible && menuRef.current) {
1266
- const rect = menuRef.current.getBoundingClientRect();
1267
- const viewportWidth = window.innerWidth;
1268
- const viewportHeight = window.innerHeight;
1269
- let adjustedX = position.x;
1270
- let adjustedY = position.y;
1271
- if (position.x + rect.width > viewportWidth) {
1272
- adjustedX = viewportWidth - rect.width - 10;
1273
- }
1274
- if (position.y + rect.height > viewportHeight) {
1275
- adjustedY = viewportHeight - rect.height - 10;
1276
- }
1277
- if (adjustedX !== position.x || adjustedY !== position.y) {
1278
- menuRef.current.style.left = `${adjustedX}px`;
1279
- menuRef.current.style.top = `${adjustedY}px`;
1363
+ if (visible) {
1364
+ setAdjustedPosition(position);
1365
+ setDragOffset({ x: 0, y: 0 });
1366
+ }
1367
+ }, [visible, position.x, position.y]);
1368
+ (0, import_react5.useEffect)(() => {
1369
+ if (!visible || !menuRef.current) return;
1370
+ const updatePosition = () => {
1371
+ const menuElement = menuRef.current;
1372
+ if (!menuElement) return;
1373
+ const rect = menuElement.getBoundingClientRect();
1374
+ const baseX = position.x + dragOffset.x;
1375
+ const baseY = position.y + dragOffset.y;
1376
+ if (positionMode === "static") {
1377
+ setAdjustedPosition({ x: baseX, y: baseY });
1378
+ } else if (positionMode === "inView" || positionMode === "dynamic") {
1379
+ const adjusted = calculateInViewPosition(
1380
+ baseX,
1381
+ baseY,
1382
+ rect.width,
1383
+ rect.height
1384
+ );
1385
+ setAdjustedPosition(adjusted);
1280
1386
  }
1387
+ };
1388
+ requestAnimationFrame(updatePosition);
1389
+ window.addEventListener("resize", updatePosition);
1390
+ return () => window.removeEventListener("resize", updatePosition);
1391
+ }, [visible, position, positionMode, dragOffset, currentView]);
1392
+ (0, import_react5.useEffect)(() => {
1393
+ if (!visible || positionMode !== "dynamic") return;
1394
+ const handlePointerMove = (event) => {
1395
+ if (!isDragging || !dragStartRef.current) return;
1396
+ const deltaX = event.clientX - dragStartRef.current.x;
1397
+ const deltaY = event.clientY - dragStartRef.current.y;
1398
+ setDragOffset((prev) => ({
1399
+ x: prev.x + deltaX,
1400
+ y: prev.y + deltaY
1401
+ }));
1402
+ dragStartRef.current = { x: event.clientX, y: event.clientY };
1403
+ };
1404
+ const handlePointerUp = () => {
1405
+ setIsDragging(false);
1406
+ dragStartRef.current = null;
1407
+ };
1408
+ if (isDragging) {
1409
+ document.addEventListener("pointermove", handlePointerMove);
1410
+ document.addEventListener("pointerup", handlePointerUp);
1411
+ document.addEventListener("pointercancel", handlePointerUp);
1412
+ return () => {
1413
+ document.removeEventListener("pointermove", handlePointerMove);
1414
+ document.removeEventListener("pointerup", handlePointerUp);
1415
+ document.removeEventListener("pointercancel", handlePointerUp);
1416
+ };
1281
1417
  }
1282
- }, [visible, position]);
1418
+ }, [visible, positionMode, isDragging]);
1419
+ const handleDragStart = (0, import_react5.useCallback)(
1420
+ (event) => {
1421
+ if (positionMode !== "dynamic") return;
1422
+ event.preventDefault();
1423
+ setIsDragging(true);
1424
+ dragStartRef.current = { x: event.clientX, y: event.clientY };
1425
+ },
1426
+ [positionMode]
1427
+ );
1283
1428
  (0, import_react5.useEffect)(() => {
1284
1429
  const handleKeyDown = (e) => {
1285
1430
  if (e.key === "Escape") {
@@ -1301,14 +1446,44 @@ function ContextMenu({
1301
1446
  return () => document.removeEventListener("keydown", handleKeyDown);
1302
1447
  }
1303
1448
  }, [visible, currentView, submenuStack.length, onClose]);
1449
+ (0, import_react5.useEffect)(() => {
1450
+ const menuElement = menuRef.current;
1451
+ if (!visible || !menuElement) return;
1452
+ const preventTouchDefault = (e) => {
1453
+ const target = e.target;
1454
+ if (target.tagName === "TEXTAREA" || target.tagName === "INPUT" || target.isContentEditable) {
1455
+ return;
1456
+ }
1457
+ e.preventDefault();
1458
+ };
1459
+ menuElement.addEventListener("touchmove", preventTouchDefault, {
1460
+ passive: false
1461
+ });
1462
+ return () => {
1463
+ menuElement.removeEventListener("touchmove", preventTouchDefault);
1464
+ };
1465
+ }, [visible]);
1304
1466
  if (!visible || !targetElement) {
1305
1467
  return null;
1306
1468
  }
1307
- const handleItemClick = (item) => {
1469
+ const handleItemClick = async (item) => {
1308
1470
  if (item.children && item.children.length > 0) {
1309
1471
  setSubmenuStack((prev) => [...prev, item.children]);
1310
1472
  return;
1311
1473
  }
1474
+ if (item.onClick) {
1475
+ try {
1476
+ const result = await item.onClick({
1477
+ targetElement,
1478
+ containerElement,
1479
+ closeMenu: onClose
1480
+ });
1481
+ return result;
1482
+ } catch (error) {
1483
+ console.error("Anyclick menu onClick error:", error);
1484
+ return;
1485
+ }
1486
+ }
1312
1487
  if (item.showComment) {
1313
1488
  setSelectedType(item.type);
1314
1489
  setCurrentView("comment");
@@ -1373,15 +1548,51 @@ function ContextMenu({
1373
1548
  className,
1374
1549
  style: {
1375
1550
  ...menuStyles.container,
1376
- left: position.x,
1377
- top: position.y,
1551
+ left: adjustedPosition.x,
1552
+ top: adjustedPosition.y,
1378
1553
  ...containerWidth ? { width: containerWidth, minWidth: containerWidth } : {},
1554
+ // Touch-specific styles
1555
+ WebkitUserSelect: "none",
1556
+ userSelect: "none",
1557
+ WebkitTouchCallout: "none",
1558
+ touchAction: "none",
1559
+ // Prevent default touch behaviors
1560
+ // Cursor style for dragging
1561
+ ...isDragging ? { cursor: "grabbing" } : {},
1379
1562
  ...style
1380
1563
  },
1381
1564
  role: "menu",
1382
1565
  "aria-label": "Feedback options",
1383
1566
  children: [
1384
- currentView !== "screenshot-preview" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: menuStyles.header, children: "Send Feedback" }),
1567
+ !header && currentView !== "screenshot-preview" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(DefaultHeader, { styles: menuStyles.header, title: "Send Feedback", children: [
1568
+ showPreview && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: screenshotIndicatorStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Camera, { className: "w-3 h-3" }) }),
1569
+ positionMode === "dynamic" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1570
+ "div",
1571
+ {
1572
+ "data-drag-handle": true,
1573
+ onPointerDown: handleDragStart,
1574
+ style: {
1575
+ cursor: isDragging ? "grabbing" : "grab",
1576
+ padding: "4px",
1577
+ marginRight: "-4px",
1578
+ borderRadius: "4px",
1579
+ display: "flex",
1580
+ alignItems: "center",
1581
+ opacity: 0.5,
1582
+ transition: "opacity 0.15s"
1583
+ },
1584
+ onMouseEnter: (e) => {
1585
+ e.currentTarget.style.opacity = "1";
1586
+ },
1587
+ onMouseLeave: (e) => {
1588
+ e.currentTarget.style.opacity = "0.5";
1589
+ },
1590
+ title: "Drag to move",
1591
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GripVertical, { className: "w-4 h-4" })
1592
+ }
1593
+ )
1594
+ ] }),
1595
+ !!header && header,
1385
1596
  currentView === "menu" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: menuStyles.itemList, children: [
1386
1597
  submenuStack.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BackButton, { onClick: handleBack }),
1387
1598
  currentItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -1393,11 +1604,7 @@ function ContextMenu({
1393
1604
  hasChildren: item.children && item.children.length > 0
1394
1605
  },
1395
1606
  item.type
1396
- )),
1397
- showPreview && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: screenshotIndicatorStyle, children: [
1398
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Camera, { className: "w-3 h-3" }),
1399
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Screenshots will be captured" })
1400
- ] })
1607
+ ))
1401
1608
  ] }),
1402
1609
  currentView === "comment" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1403
1610
  CommentForm,
@@ -1422,16 +1629,6 @@ function ContextMenu({
1422
1629
  }
1423
1630
  );
1424
1631
  }
1425
- var screenshotIndicatorStyle = {
1426
- display: "flex",
1427
- alignItems: "center",
1428
- gap: "6px",
1429
- padding: "8px 12px",
1430
- fontSize: "11px",
1431
- color: "var(--anyclick-menu-text-muted, #9ca3af)",
1432
- borderTop: "1px solid var(--anyclick-menu-border, #f3f4f6)",
1433
- marginTop: "4px"
1434
- };
1435
1632
 
1436
1633
  // src/store.ts
1437
1634
  var import_zustand = require("zustand");
@@ -1577,17 +1774,45 @@ var useProviderStore = (0, import_zustand.create)((set, get) => ({
1577
1774
  }
1578
1775
  }
1579
1776
  return false;
1777
+ },
1778
+ isElementInAnyScopedProvider: (element) => {
1779
+ const { providers } = get();
1780
+ for (const provider of providers.values()) {
1781
+ if (!provider.scoped) continue;
1782
+ const container = provider.containerRef.current;
1783
+ if (!container) continue;
1784
+ if (container.contains(element)) {
1785
+ if (process.env.NODE_ENV === "development") {
1786
+ console.log(
1787
+ `[Store:isElementInAnyScopedProvider] Element is in scoped provider: ${provider.id}`,
1788
+ {
1789
+ elementTag: element.tagName,
1790
+ providerId: provider.id,
1791
+ providerDisabled: provider.disabled
1792
+ }
1793
+ );
1794
+ }
1795
+ return true;
1796
+ }
1797
+ }
1798
+ return false;
1580
1799
  }
1581
1800
  }));
1582
1801
  function dispatchContextMenuEvent(event, element) {
1583
1802
  const store = useProviderStore.getState();
1584
1803
  const providers = store.findProvidersForElement(element);
1804
+ const menuEvent = {
1805
+ clientX: event.clientX,
1806
+ clientY: event.clientY,
1807
+ originalEvent: event,
1808
+ isTouch: false
1809
+ };
1585
1810
  for (const provider of providers) {
1586
1811
  if (store.isDisabledByAncestor(provider.id)) {
1587
1812
  continue;
1588
1813
  }
1589
1814
  if (provider.onContextMenu) {
1590
- provider.onContextMenu(event, element);
1815
+ provider.onContextMenu(menuEvent, element);
1591
1816
  break;
1592
1817
  }
1593
1818
  }
@@ -1617,9 +1842,12 @@ function AnyclickProvider({
1617
1842
  menuClassName,
1618
1843
  disabled = false,
1619
1844
  highlightConfig,
1845
+ header,
1620
1846
  screenshotConfig,
1621
1847
  scoped = false,
1622
- theme
1848
+ theme,
1849
+ touchHoldDurationMs,
1850
+ touchMoveThreshold
1623
1851
  }) {
1624
1852
  const [isSubmitting, setIsSubmitting] = (0, import_react6.useState)(false);
1625
1853
  const [menuVisible, setMenuVisible] = (0, import_react6.useState)(false);
@@ -1666,7 +1894,8 @@ function AnyclickProvider({
1666
1894
  getMergedTheme,
1667
1895
  isDisabledByAncestor,
1668
1896
  findParentProvider,
1669
- isElementInDisabledScope
1897
+ isElementInDisabledScope,
1898
+ isElementInAnyScopedProvider
1670
1899
  } = useProviderStore();
1671
1900
  const parentId = parentContext?.providerId ?? null;
1672
1901
  const depth = parentContext ? parentContext.scoped ? 1 : 0 : 0;
@@ -1712,6 +1941,17 @@ function AnyclickProvider({
1712
1941
  }
1713
1942
  return false;
1714
1943
  }
1944
+ if (!scoped && event.isTouch && isElementInAnyScopedProvider(element)) {
1945
+ if (process.env.NODE_ENV === "development") {
1946
+ console.log(
1947
+ `[AnyclickProvider:${providerId}] Deferring to scoped provider for touch event`,
1948
+ {
1949
+ targetTag: element.tagName
1950
+ }
1951
+ );
1952
+ }
1953
+ return false;
1954
+ }
1715
1955
  const mergedTheme2 = getMergedTheme(providerId);
1716
1956
  if (process.env.NODE_ENV === "development") {
1717
1957
  console.log(
@@ -1720,7 +1960,8 @@ function AnyclickProvider({
1720
1960
  scoped,
1721
1961
  targetTag: element.tagName,
1722
1962
  mergedThemeColors: mergedTheme2.highlightConfig?.colors,
1723
- position: { x: event.clientX, y: event.clientY }
1963
+ position: { x: event.clientX, y: event.clientY },
1964
+ isTouch: event.isTouch
1724
1965
  }
1725
1966
  );
1726
1967
  }
@@ -1739,7 +1980,8 @@ function AnyclickProvider({
1739
1980
  getMergedTheme,
1740
1981
  highlightConfig,
1741
1982
  scoped,
1742
- isElementInDisabledScope
1983
+ isElementInDisabledScope,
1984
+ isElementInAnyScopedProvider
1743
1985
  ]
1744
1986
  );
1745
1987
  (0, import_react6.useLayoutEffect)(() => {
@@ -1811,7 +2053,7 @@ function AnyclickProvider({
1811
2053
  localThemeColors: localTheme.highlightConfig?.colors
1812
2054
  });
1813
2055
  }
1814
- const client = (0, import_anyclick_core3.createFeedbackClient)({
2056
+ const client = (0, import_anyclick_core3.createAnyclickClient)({
1815
2057
  adapter,
1816
2058
  targetFilter,
1817
2059
  maxInnerTextLength,
@@ -1820,7 +2062,9 @@ function AnyclickProvider({
1820
2062
  cooldownMs,
1821
2063
  stripAttributes,
1822
2064
  // For scoped providers, pass the container
1823
- container: scoped ? containerRef.current : null
2065
+ container: scoped ? containerRef.current : null,
2066
+ touchHoldDurationMs,
2067
+ touchMoveThreshold
1824
2068
  });
1825
2069
  client.onSubmitSuccess = onSubmitSuccess;
1826
2070
  client.onSubmitError = onSubmitError;
@@ -1847,15 +2091,17 @@ function AnyclickProvider({
1847
2091
  containerReady,
1848
2092
  providerId,
1849
2093
  isDisabledByAncestor,
1850
- handleContextMenu
2094
+ handleContextMenu,
2095
+ touchHoldDurationMs,
2096
+ touchMoveThreshold
1851
2097
  ]);
1852
- const submitFeedback = (0, import_react6.useCallback)(
2098
+ const submitAnyclick = (0, import_react6.useCallback)(
1853
2099
  async (element, type, comment, screenshots) => {
1854
2100
  const client = clientRef.current;
1855
2101
  if (!client) return;
1856
2102
  setIsSubmitting(true);
1857
2103
  try {
1858
- await client.submitFeedback(element, type, {
2104
+ await client.submitAnyclick(element, type, {
1859
2105
  comment,
1860
2106
  metadata,
1861
2107
  screenshots
@@ -1891,10 +2137,10 @@ function AnyclickProvider({
1891
2137
  const handleMenuSelect = (0, import_react6.useCallback)(
1892
2138
  (type, comment, screenshots) => {
1893
2139
  if (targetElement) {
1894
- submitFeedback(targetElement, type, comment, screenshots);
2140
+ submitAnyclick(targetElement, type, comment, screenshots);
1895
2141
  }
1896
2142
  },
1897
- [targetElement, submitFeedback]
2143
+ [targetElement, submitAnyclick]
1898
2144
  );
1899
2145
  const inheritedTheme = getMergedTheme(providerId);
1900
2146
  const mergedTheme = (0, import_react6.useMemo)(
@@ -1937,7 +2183,7 @@ function AnyclickProvider({
1937
2183
  () => ({
1938
2184
  isEnabled: !effectiveDisabled && !isDisabledByAncestor(providerId),
1939
2185
  isSubmitting,
1940
- submitFeedback,
2186
+ submitAnyclick,
1941
2187
  openMenu,
1942
2188
  closeMenu,
1943
2189
  theme: mergedTheme,
@@ -1949,7 +2195,7 @@ function AnyclickProvider({
1949
2195
  providerId,
1950
2196
  isDisabledByAncestor,
1951
2197
  isSubmitting,
1952
- submitFeedback,
2198
+ submitAnyclick,
1953
2199
  openMenu,
1954
2200
  closeMenu,
1955
2201
  mergedTheme,
@@ -1973,13 +2219,107 @@ function AnyclickProvider({
1973
2219
  style: effectiveMenuStyle,
1974
2220
  className: effectiveMenuClassName,
1975
2221
  highlightConfig: effectiveHighlightConfig,
1976
- screenshotConfig: effectiveScreenshotConfig
2222
+ screenshotConfig: effectiveScreenshotConfig,
2223
+ header
1977
2224
  }
1978
2225
  )
1979
2226
  ] });
1980
2227
  }
1981
2228
  var FeedbackProvider = AnyclickProvider;
1982
2229
 
2230
+ // src/FunModeBridge.tsx
2231
+ var import_react7 = require("react");
2232
+ var import_anyclick_pointer = require("@ewjdev/anyclick-pointer");
2233
+ function isFunModeEnabled(theme) {
2234
+ if (!theme) return false;
2235
+ if (theme.funMode === void 0) return false;
2236
+ if (typeof theme.funMode === "boolean") return theme.funMode;
2237
+ return theme.funMode.enabled ?? true;
2238
+ }
2239
+ function buildFunConfig(theme, container) {
2240
+ const funTheme = typeof theme?.funMode === "object" ? theme.funMode : {};
2241
+ const resolveTrackElement = () => {
2242
+ let el = container;
2243
+ while (el) {
2244
+ const rect = el.getBoundingClientRect();
2245
+ if (rect.width > 0 && rect.height > 0) {
2246
+ return el;
2247
+ }
2248
+ el = el.parentElement;
2249
+ }
2250
+ return document.body;
2251
+ };
2252
+ const getTrackElement = () => resolveTrackElement();
2253
+ return {
2254
+ maxSpeed: funTheme.maxSpeed,
2255
+ acceleration: funTheme.acceleration,
2256
+ getTrackElement,
2257
+ getObstacles: () => {
2258
+ const trackElement = getTrackElement();
2259
+ const obstacles = [];
2260
+ const parent = trackElement.parentElement;
2261
+ if (parent) {
2262
+ Array.from(parent.children).forEach((sibling) => {
2263
+ if (sibling !== trackElement) {
2264
+ obstacles.push(sibling.getBoundingClientRect());
2265
+ }
2266
+ });
2267
+ }
2268
+ Array.from(trackElement.children).forEach((child) => {
2269
+ obstacles.push(child.getBoundingClientRect());
2270
+ });
2271
+ return obstacles;
2272
+ }
2273
+ };
2274
+ }
2275
+ function FunModeBridge() {
2276
+ const { setConfig } = (0, import_anyclick_pointer.usePointer)();
2277
+ const providerStore = useProviderStore();
2278
+ const activeProviderRef = (0, import_react7.useRef)(null);
2279
+ const cachedConfigs = (0, import_react7.useRef)({});
2280
+ const findActiveFunProvider = (0, import_react7.useMemo)(() => {
2281
+ return (el) => {
2282
+ if (!el) return null;
2283
+ const providers = providerStore.findProvidersForElement(el);
2284
+ for (const provider of providers) {
2285
+ if (provider.scoped && !provider.disabled && isFunModeEnabled(provider.theme)) {
2286
+ return provider;
2287
+ }
2288
+ }
2289
+ return null;
2290
+ };
2291
+ }, [providerStore]);
2292
+ (0, import_react7.useEffect)(() => {
2293
+ const handleMove = (event) => {
2294
+ const el = document.elementFromPoint(event.clientX, event.clientY);
2295
+ const provider = findActiveFunProvider(el);
2296
+ if (!provider || !provider.containerRef.current) {
2297
+ if (activeProviderRef.current !== null) {
2298
+ activeProviderRef.current = null;
2299
+ setConfig({ mode: "normal" });
2300
+ }
2301
+ return;
2302
+ }
2303
+ if (activeProviderRef.current === provider.id) {
2304
+ return;
2305
+ }
2306
+ activeProviderRef.current = provider.id;
2307
+ if (!cachedConfigs.current[provider.id]) {
2308
+ cachedConfigs.current[provider.id] = {
2309
+ mode: "fun",
2310
+ funConfig: buildFunConfig(provider.theme, provider.containerRef.current)
2311
+ };
2312
+ }
2313
+ setConfig(cachedConfigs.current[provider.id]);
2314
+ };
2315
+ window.addEventListener("pointermove", handleMove, { passive: true });
2316
+ return () => {
2317
+ window.removeEventListener("pointermove", handleMove);
2318
+ };
2319
+ }, [findActiveFunProvider, setConfig]);
2320
+ return null;
2321
+ }
2322
+
1983
2323
  // src/types.ts
1984
2324
  function filterMenuItemsByRole(items, userContext) {
1985
2325
  if (!userContext) {
@@ -2007,6 +2347,7 @@ var import_anyclick_core4 = require("@ewjdev/anyclick-core");
2007
2347
  DEFAULT_SENSITIVE_SELECTORS,
2008
2348
  FeedbackContext,
2009
2349
  FeedbackProvider,
2350
+ FunModeBridge,
2010
2351
  ScreenshotPreview,
2011
2352
  applyHighlights,
2012
2353
  captureAllScreenshots,
@@ -2043,6 +2384,7 @@ lucide-react/dist/esm/icons/chevron-right.js:
2043
2384
  lucide-react/dist/esm/icons/circle-alert.js:
2044
2385
  lucide-react/dist/esm/icons/expand.js:
2045
2386
  lucide-react/dist/esm/icons/flag.js:
2387
+ lucide-react/dist/esm/icons/grip-vertical.js:
2046
2388
  lucide-react/dist/esm/icons/image.js:
2047
2389
  lucide-react/dist/esm/icons/loader-circle.js:
2048
2390
  lucide-react/dist/esm/icons/plus.js: