@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.mjs CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  useRef as useRef2,
10
10
  useState as useState3
11
11
  } from "react";
12
- import { createFeedbackClient } from "@ewjdev/anyclick-core";
12
+ import { createAnyclickClient } from "@ewjdev/anyclick-core";
13
13
 
14
14
  // src/context.ts
15
15
  import { createContext, useContext } from "react";
@@ -78,16 +78,19 @@ var menuStyles = {
78
78
  ...menuCSSVariables
79
79
  },
80
80
  header: {
81
- padding: "12px 16px",
81
+ padding: "8px 12px",
82
82
  borderBottom: "1px solid var(--anyclick-menu-border, #e5e5e5)",
83
83
  color: "var(--anyclick-menu-text-muted, #666)",
84
84
  fontSize: "12px",
85
85
  fontWeight: 500,
86
86
  textTransform: "uppercase",
87
- letterSpacing: "0.5px"
87
+ letterSpacing: "0.5px",
88
+ display: "flex",
89
+ justifyContent: "space-between",
90
+ alignItems: "center"
88
91
  },
89
92
  itemList: {
90
- padding: "4px 0"
93
+ padding: "0px 0px"
91
94
  },
92
95
  item: {
93
96
  display: "flex",
@@ -489,6 +492,7 @@ function generateHighlightCSS(colors) {
489
492
  border-radius: 4px !important;
490
493
  position: relative;
491
494
  z-index: 9997;
495
+ top: 0;
492
496
  }
493
497
 
494
498
  .${HIGHLIGHT_CONTAINER_CLASS} {
@@ -731,45 +735,56 @@ var __iconNode7 = [
731
735
  ];
732
736
  var Flag = createLucideIcon("flag", __iconNode7);
733
737
 
734
- // ../../node_modules/lucide-react/dist/esm/icons/image.js
738
+ // ../../node_modules/lucide-react/dist/esm/icons/grip-vertical.js
735
739
  var __iconNode8 = [
740
+ ["circle", { cx: "9", cy: "12", r: "1", key: "1vctgf" }],
741
+ ["circle", { cx: "9", cy: "5", r: "1", key: "hp0tcf" }],
742
+ ["circle", { cx: "9", cy: "19", r: "1", key: "fkjjf6" }],
743
+ ["circle", { cx: "15", cy: "12", r: "1", key: "1tmaij" }],
744
+ ["circle", { cx: "15", cy: "5", r: "1", key: "19l28e" }],
745
+ ["circle", { cx: "15", cy: "19", r: "1", key: "f4zoj3" }]
746
+ ];
747
+ var GripVertical = createLucideIcon("grip-vertical", __iconNode8);
748
+
749
+ // ../../node_modules/lucide-react/dist/esm/icons/image.js
750
+ var __iconNode9 = [
736
751
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
737
752
  ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
738
753
  ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
739
754
  ];
740
- var Image = createLucideIcon("image", __iconNode8);
755
+ var Image = createLucideIcon("image", __iconNode9);
741
756
 
742
757
  // ../../node_modules/lucide-react/dist/esm/icons/loader-circle.js
743
- var __iconNode9 = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
744
- var LoaderCircle = createLucideIcon("loader-circle", __iconNode9);
758
+ var __iconNode10 = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
759
+ var LoaderCircle = createLucideIcon("loader-circle", __iconNode10);
745
760
 
746
761
  // ../../node_modules/lucide-react/dist/esm/icons/plus.js
747
- var __iconNode10 = [
762
+ var __iconNode11 = [
748
763
  ["path", { d: "M5 12h14", key: "1ays0h" }],
749
764
  ["path", { d: "M12 5v14", key: "s699le" }]
750
765
  ];
751
- var Plus = createLucideIcon("plus", __iconNode10);
766
+ var Plus = createLucideIcon("plus", __iconNode11);
752
767
 
753
768
  // ../../node_modules/lucide-react/dist/esm/icons/refresh-cw.js
754
- var __iconNode11 = [
769
+ var __iconNode12 = [
755
770
  ["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }],
756
771
  ["path", { d: "M21 3v5h-5", key: "1q7to0" }],
757
772
  ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }],
758
773
  ["path", { d: "M8 16H3v5", key: "1cv678" }]
759
774
  ];
760
- var RefreshCw = createLucideIcon("refresh-cw", __iconNode11);
775
+ var RefreshCw = createLucideIcon("refresh-cw", __iconNode12);
761
776
 
762
777
  // ../../node_modules/lucide-react/dist/esm/icons/shrink.js
763
- var __iconNode12 = [
778
+ var __iconNode13 = [
764
779
  ["path", { d: "m15 15 6 6m-6-6v4.8m0-4.8h4.8", key: "17vawe" }],
765
780
  ["path", { d: "M9 19.8V15m0 0H4.2M9 15l-6 6", key: "chjx8e" }],
766
781
  ["path", { d: "M15 4.2V9m0 0h4.8M15 9l6-6", key: "lav6yq" }],
767
782
  ["path", { d: "M9 4.2V9m0 0H4.2M9 9 3 3", key: "1pxi2q" }]
768
783
  ];
769
- var Shrink = createLucideIcon("shrink", __iconNode12);
784
+ var Shrink = createLucideIcon("shrink", __iconNode13);
770
785
 
771
786
  // ../../node_modules/lucide-react/dist/esm/icons/thumbs-up.js
772
- var __iconNode13 = [
787
+ var __iconNode14 = [
773
788
  ["path", { d: "M7 10v12", key: "1qc93n" }],
774
789
  [
775
790
  "path",
@@ -779,14 +794,14 @@ var __iconNode13 = [
779
794
  }
780
795
  ]
781
796
  ];
782
- var ThumbsUp = createLucideIcon("thumbs-up", __iconNode13);
797
+ var ThumbsUp = createLucideIcon("thumbs-up", __iconNode14);
783
798
 
784
799
  // ../../node_modules/lucide-react/dist/esm/icons/x.js
785
- var __iconNode14 = [
800
+ var __iconNode15 = [
786
801
  ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
787
802
  ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
788
803
  ];
789
- var X = createLucideIcon("x", __iconNode14);
804
+ var X = createLucideIcon("x", __iconNode15);
790
805
 
791
806
  // src/ScreenshotPreview.tsx
792
807
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1016,11 +1031,30 @@ function ScreenshotPreview({
1016
1031
 
1017
1032
  // src/ContextMenu.tsx
1018
1033
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1034
+ var VIEWPORT_PADDING = 10;
1035
+ var screenshotIndicatorStyle = {
1036
+ display: "flex",
1037
+ marginLeft: "4px",
1038
+ opacity: 0.7,
1039
+ justifyContent: "flex-end",
1040
+ flex: 1
1041
+ };
1019
1042
  var defaultIcons = {
1020
1043
  issue: /* @__PURE__ */ jsx2(Flag, { className: "w-4 h-4" }),
1021
1044
  feature: /* @__PURE__ */ jsx2(Plus, { className: "w-4 h-4" }),
1022
1045
  like: /* @__PURE__ */ jsx2(ThumbsUp, { className: "w-4 h-4" })
1023
1046
  };
1047
+ var DefaultHeader = ({
1048
+ styles,
1049
+ className,
1050
+ title = "Send Feedback",
1051
+ children
1052
+ }) => {
1053
+ return /* @__PURE__ */ jsxs2("div", { style: styles, className, children: [
1054
+ /* @__PURE__ */ jsx2("span", { children: title }),
1055
+ children
1056
+ ] });
1057
+ };
1024
1058
  function MenuItem({
1025
1059
  item,
1026
1060
  onClick,
@@ -1028,6 +1062,7 @@ function MenuItem({
1028
1062
  hasChildren
1029
1063
  }) {
1030
1064
  const [isHovered, setIsHovered] = useState2(false);
1065
+ const [isPressed, setIsPressed] = useState2(false);
1031
1066
  return /* @__PURE__ */ jsxs2(
1032
1067
  "button",
1033
1068
  {
@@ -1035,11 +1070,25 @@ function MenuItem({
1035
1070
  onClick,
1036
1071
  disabled,
1037
1072
  onMouseEnter: () => setIsHovered(true),
1038
- onMouseLeave: () => setIsHovered(false),
1073
+ onMouseLeave: () => {
1074
+ setIsHovered(false);
1075
+ setIsPressed(false);
1076
+ },
1077
+ onTouchStart: () => setIsPressed(true),
1078
+ onTouchEnd: () => setIsPressed(false),
1079
+ onTouchCancel: () => setIsPressed(false),
1039
1080
  style: {
1040
1081
  ...menuStyles.item,
1041
- ...isHovered ? menuStyles.itemHover : {},
1042
- ...disabled ? { opacity: 0.5, cursor: "not-allowed" } : {}
1082
+ // Apply hover/pressed state for both mouse and touch
1083
+ ...isHovered || isPressed ? menuStyles.itemHover : {},
1084
+ ...disabled ? { opacity: 0.5, cursor: "not-allowed" } : {},
1085
+ // Ensure minimum touch target size (44px is recommended)
1086
+ minHeight: "44px",
1087
+ // Prevent text selection on touch
1088
+ WebkitUserSelect: "none",
1089
+ userSelect: "none",
1090
+ // Prevent touch callout on iOS
1091
+ WebkitTouchCallout: "none"
1043
1092
  },
1044
1093
  children: [
1045
1094
  /* @__PURE__ */ jsx2("span", { style: menuStyles.itemIcon, children: item.icon ?? defaultIcons[item.type] }),
@@ -1057,18 +1106,31 @@ function MenuItem({
1057
1106
  }
1058
1107
  function BackButton({ onClick }) {
1059
1108
  const [isHovered, setIsHovered] = useState2(false);
1109
+ const [isPressed, setIsPressed] = useState2(false);
1060
1110
  return /* @__PURE__ */ jsxs2(
1061
1111
  "button",
1062
1112
  {
1063
1113
  type: "button",
1064
1114
  onClick,
1065
1115
  onMouseEnter: () => setIsHovered(true),
1066
- onMouseLeave: () => setIsHovered(false),
1116
+ onMouseLeave: () => {
1117
+ setIsHovered(false);
1118
+ setIsPressed(false);
1119
+ },
1120
+ onTouchStart: () => setIsPressed(true),
1121
+ onTouchEnd: () => setIsPressed(false),
1122
+ onTouchCancel: () => setIsPressed(false),
1067
1123
  style: {
1068
1124
  ...menuStyles.item,
1069
- ...isHovered ? menuStyles.itemHover : {},
1125
+ ...isHovered || isPressed ? menuStyles.itemHover : {},
1070
1126
  borderBottom: "1px solid #e5e5e5",
1071
- marginBottom: "4px"
1127
+ marginBottom: "4px",
1128
+ // Ensure minimum touch target size
1129
+ minHeight: "44px",
1130
+ // Prevent text selection on touch
1131
+ WebkitUserSelect: "none",
1132
+ userSelect: "none",
1133
+ WebkitTouchCallout: "none"
1072
1134
  },
1073
1135
  children: [
1074
1136
  /* @__PURE__ */ jsx2(ChevronLeft, { className: "w-4 h-4", style: { opacity: 0.5 } }),
@@ -1138,6 +1200,25 @@ function CommentForm({
1138
1200
  ] })
1139
1201
  ] });
1140
1202
  }
1203
+ function calculateInViewPosition(requestedX, requestedY, menuWidth, menuHeight) {
1204
+ const viewportWidth = window.innerWidth;
1205
+ const viewportHeight = window.innerHeight;
1206
+ let x = requestedX;
1207
+ let y = requestedY;
1208
+ if (x + menuWidth > viewportWidth - VIEWPORT_PADDING) {
1209
+ x = viewportWidth - menuWidth - VIEWPORT_PADDING;
1210
+ }
1211
+ if (x < VIEWPORT_PADDING) {
1212
+ x = VIEWPORT_PADDING;
1213
+ }
1214
+ if (y + menuHeight > viewportHeight - VIEWPORT_PADDING) {
1215
+ y = viewportHeight - menuHeight - VIEWPORT_PADDING;
1216
+ }
1217
+ if (y < VIEWPORT_PADDING) {
1218
+ y = VIEWPORT_PADDING;
1219
+ }
1220
+ return { x, y };
1221
+ }
1141
1222
  function ContextMenu({
1142
1223
  visible,
1143
1224
  position,
@@ -1150,7 +1231,10 @@ function ContextMenu({
1150
1231
  style,
1151
1232
  className,
1152
1233
  highlightConfig,
1153
- screenshotConfig
1234
+ screenshotConfig,
1235
+ positionMode = "inView",
1236
+ header,
1237
+ footer
1154
1238
  }) {
1155
1239
  const [selectedType, setSelectedType] = useState2(null);
1156
1240
  const [currentView, setCurrentView] = useState2("menu");
@@ -1159,6 +1243,13 @@ function ContextMenu({
1159
1243
  const [screenshots, setScreenshots] = useState2(null);
1160
1244
  const [isCapturing, setIsCapturing] = useState2(false);
1161
1245
  const menuRef = useRef(null);
1246
+ const [adjustedPosition, setAdjustedPosition] = useState2(position);
1247
+ const [isDragging, setIsDragging] = useState2(false);
1248
+ const [dragOffset, setDragOffset] = useState2({
1249
+ x: 0,
1250
+ y: 0
1251
+ });
1252
+ const dragStartRef = useRef(null);
1162
1253
  const mergedScreenshotConfig = {
1163
1254
  ...DEFAULT_SCREENSHOT_CONFIG,
1164
1255
  ...screenshotConfig
@@ -1190,6 +1281,9 @@ function ContextMenu({
1190
1281
  setSubmenuStack([]);
1191
1282
  setScreenshots(null);
1192
1283
  setIsCapturing(false);
1284
+ setIsDragging(false);
1285
+ setDragOffset({ x: 0, y: 0 });
1286
+ dragStartRef.current = null;
1193
1287
  }
1194
1288
  }, [visible]);
1195
1289
  useEffect(() => {
@@ -1210,6 +1304,9 @@ function ContextMenu({
1210
1304
  const handlePointerDown = (event) => {
1211
1305
  if (!menuRef.current) return;
1212
1306
  const target = event.target;
1307
+ if (target.closest?.("[data-drag-handle]")) {
1308
+ return;
1309
+ }
1213
1310
  if (!menuRef.current.contains(target)) {
1214
1311
  onClose();
1215
1312
  }
@@ -1220,24 +1317,71 @@ function ContextMenu({
1220
1317
  };
1221
1318
  }, [visible, onClose]);
1222
1319
  useEffect(() => {
1223
- if (visible && menuRef.current) {
1224
- const rect = menuRef.current.getBoundingClientRect();
1225
- const viewportWidth = window.innerWidth;
1226
- const viewportHeight = window.innerHeight;
1227
- let adjustedX = position.x;
1228
- let adjustedY = position.y;
1229
- if (position.x + rect.width > viewportWidth) {
1230
- adjustedX = viewportWidth - rect.width - 10;
1231
- }
1232
- if (position.y + rect.height > viewportHeight) {
1233
- adjustedY = viewportHeight - rect.height - 10;
1234
- }
1235
- if (adjustedX !== position.x || adjustedY !== position.y) {
1236
- menuRef.current.style.left = `${adjustedX}px`;
1237
- menuRef.current.style.top = `${adjustedY}px`;
1320
+ if (visible) {
1321
+ setAdjustedPosition(position);
1322
+ setDragOffset({ x: 0, y: 0 });
1323
+ }
1324
+ }, [visible, position.x, position.y]);
1325
+ useEffect(() => {
1326
+ if (!visible || !menuRef.current) return;
1327
+ const updatePosition = () => {
1328
+ const menuElement = menuRef.current;
1329
+ if (!menuElement) return;
1330
+ const rect = menuElement.getBoundingClientRect();
1331
+ const baseX = position.x + dragOffset.x;
1332
+ const baseY = position.y + dragOffset.y;
1333
+ if (positionMode === "static") {
1334
+ setAdjustedPosition({ x: baseX, y: baseY });
1335
+ } else if (positionMode === "inView" || positionMode === "dynamic") {
1336
+ const adjusted = calculateInViewPosition(
1337
+ baseX,
1338
+ baseY,
1339
+ rect.width,
1340
+ rect.height
1341
+ );
1342
+ setAdjustedPosition(adjusted);
1238
1343
  }
1344
+ };
1345
+ requestAnimationFrame(updatePosition);
1346
+ window.addEventListener("resize", updatePosition);
1347
+ return () => window.removeEventListener("resize", updatePosition);
1348
+ }, [visible, position, positionMode, dragOffset, currentView]);
1349
+ useEffect(() => {
1350
+ if (!visible || positionMode !== "dynamic") return;
1351
+ const handlePointerMove = (event) => {
1352
+ if (!isDragging || !dragStartRef.current) return;
1353
+ const deltaX = event.clientX - dragStartRef.current.x;
1354
+ const deltaY = event.clientY - dragStartRef.current.y;
1355
+ setDragOffset((prev) => ({
1356
+ x: prev.x + deltaX,
1357
+ y: prev.y + deltaY
1358
+ }));
1359
+ dragStartRef.current = { x: event.clientX, y: event.clientY };
1360
+ };
1361
+ const handlePointerUp = () => {
1362
+ setIsDragging(false);
1363
+ dragStartRef.current = null;
1364
+ };
1365
+ if (isDragging) {
1366
+ document.addEventListener("pointermove", handlePointerMove);
1367
+ document.addEventListener("pointerup", handlePointerUp);
1368
+ document.addEventListener("pointercancel", handlePointerUp);
1369
+ return () => {
1370
+ document.removeEventListener("pointermove", handlePointerMove);
1371
+ document.removeEventListener("pointerup", handlePointerUp);
1372
+ document.removeEventListener("pointercancel", handlePointerUp);
1373
+ };
1239
1374
  }
1240
- }, [visible, position]);
1375
+ }, [visible, positionMode, isDragging]);
1376
+ const handleDragStart = useCallback(
1377
+ (event) => {
1378
+ if (positionMode !== "dynamic") return;
1379
+ event.preventDefault();
1380
+ setIsDragging(true);
1381
+ dragStartRef.current = { x: event.clientX, y: event.clientY };
1382
+ },
1383
+ [positionMode]
1384
+ );
1241
1385
  useEffect(() => {
1242
1386
  const handleKeyDown = (e) => {
1243
1387
  if (e.key === "Escape") {
@@ -1259,14 +1403,44 @@ function ContextMenu({
1259
1403
  return () => document.removeEventListener("keydown", handleKeyDown);
1260
1404
  }
1261
1405
  }, [visible, currentView, submenuStack.length, onClose]);
1406
+ useEffect(() => {
1407
+ const menuElement = menuRef.current;
1408
+ if (!visible || !menuElement) return;
1409
+ const preventTouchDefault = (e) => {
1410
+ const target = e.target;
1411
+ if (target.tagName === "TEXTAREA" || target.tagName === "INPUT" || target.isContentEditable) {
1412
+ return;
1413
+ }
1414
+ e.preventDefault();
1415
+ };
1416
+ menuElement.addEventListener("touchmove", preventTouchDefault, {
1417
+ passive: false
1418
+ });
1419
+ return () => {
1420
+ menuElement.removeEventListener("touchmove", preventTouchDefault);
1421
+ };
1422
+ }, [visible]);
1262
1423
  if (!visible || !targetElement) {
1263
1424
  return null;
1264
1425
  }
1265
- const handleItemClick = (item) => {
1426
+ const handleItemClick = async (item) => {
1266
1427
  if (item.children && item.children.length > 0) {
1267
1428
  setSubmenuStack((prev) => [...prev, item.children]);
1268
1429
  return;
1269
1430
  }
1431
+ if (item.onClick) {
1432
+ try {
1433
+ const result = await item.onClick({
1434
+ targetElement,
1435
+ containerElement,
1436
+ closeMenu: onClose
1437
+ });
1438
+ return result;
1439
+ } catch (error) {
1440
+ console.error("Anyclick menu onClick error:", error);
1441
+ return;
1442
+ }
1443
+ }
1270
1444
  if (item.showComment) {
1271
1445
  setSelectedType(item.type);
1272
1446
  setCurrentView("comment");
@@ -1331,15 +1505,51 @@ function ContextMenu({
1331
1505
  className,
1332
1506
  style: {
1333
1507
  ...menuStyles.container,
1334
- left: position.x,
1335
- top: position.y,
1508
+ left: adjustedPosition.x,
1509
+ top: adjustedPosition.y,
1336
1510
  ...containerWidth ? { width: containerWidth, minWidth: containerWidth } : {},
1511
+ // Touch-specific styles
1512
+ WebkitUserSelect: "none",
1513
+ userSelect: "none",
1514
+ WebkitTouchCallout: "none",
1515
+ touchAction: "none",
1516
+ // Prevent default touch behaviors
1517
+ // Cursor style for dragging
1518
+ ...isDragging ? { cursor: "grabbing" } : {},
1337
1519
  ...style
1338
1520
  },
1339
1521
  role: "menu",
1340
1522
  "aria-label": "Feedback options",
1341
1523
  children: [
1342
- currentView !== "screenshot-preview" && /* @__PURE__ */ jsx2("div", { style: menuStyles.header, children: "Send Feedback" }),
1524
+ !header && currentView !== "screenshot-preview" && /* @__PURE__ */ jsxs2(DefaultHeader, { styles: menuStyles.header, title: "Send Feedback", children: [
1525
+ showPreview && /* @__PURE__ */ jsx2("div", { style: screenshotIndicatorStyle, children: /* @__PURE__ */ jsx2(Camera, { className: "w-3 h-3" }) }),
1526
+ positionMode === "dynamic" && /* @__PURE__ */ jsx2(
1527
+ "div",
1528
+ {
1529
+ "data-drag-handle": true,
1530
+ onPointerDown: handleDragStart,
1531
+ style: {
1532
+ cursor: isDragging ? "grabbing" : "grab",
1533
+ padding: "4px",
1534
+ marginRight: "-4px",
1535
+ borderRadius: "4px",
1536
+ display: "flex",
1537
+ alignItems: "center",
1538
+ opacity: 0.5,
1539
+ transition: "opacity 0.15s"
1540
+ },
1541
+ onMouseEnter: (e) => {
1542
+ e.currentTarget.style.opacity = "1";
1543
+ },
1544
+ onMouseLeave: (e) => {
1545
+ e.currentTarget.style.opacity = "0.5";
1546
+ },
1547
+ title: "Drag to move",
1548
+ children: /* @__PURE__ */ jsx2(GripVertical, { className: "w-4 h-4" })
1549
+ }
1550
+ )
1551
+ ] }),
1552
+ !!header && header,
1343
1553
  currentView === "menu" && /* @__PURE__ */ jsxs2("div", { style: menuStyles.itemList, children: [
1344
1554
  submenuStack.length > 0 && /* @__PURE__ */ jsx2(BackButton, { onClick: handleBack }),
1345
1555
  currentItems.map((item) => /* @__PURE__ */ jsx2(
@@ -1351,11 +1561,7 @@ function ContextMenu({
1351
1561
  hasChildren: item.children && item.children.length > 0
1352
1562
  },
1353
1563
  item.type
1354
- )),
1355
- showPreview && /* @__PURE__ */ jsxs2("div", { style: screenshotIndicatorStyle, children: [
1356
- /* @__PURE__ */ jsx2(Camera, { className: "w-3 h-3" }),
1357
- /* @__PURE__ */ jsx2("span", { children: "Screenshots will be captured" })
1358
- ] })
1564
+ ))
1359
1565
  ] }),
1360
1566
  currentView === "comment" && /* @__PURE__ */ jsx2(
1361
1567
  CommentForm,
@@ -1380,16 +1586,6 @@ function ContextMenu({
1380
1586
  }
1381
1587
  );
1382
1588
  }
1383
- var screenshotIndicatorStyle = {
1384
- display: "flex",
1385
- alignItems: "center",
1386
- gap: "6px",
1387
- padding: "8px 12px",
1388
- fontSize: "11px",
1389
- color: "var(--anyclick-menu-text-muted, #9ca3af)",
1390
- borderTop: "1px solid var(--anyclick-menu-border, #f3f4f6)",
1391
- marginTop: "4px"
1392
- };
1393
1589
 
1394
1590
  // src/store.ts
1395
1591
  import { create } from "zustand";
@@ -1535,17 +1731,45 @@ var useProviderStore = create((set, get) => ({
1535
1731
  }
1536
1732
  }
1537
1733
  return false;
1734
+ },
1735
+ isElementInAnyScopedProvider: (element) => {
1736
+ const { providers } = get();
1737
+ for (const provider of providers.values()) {
1738
+ if (!provider.scoped) continue;
1739
+ const container = provider.containerRef.current;
1740
+ if (!container) continue;
1741
+ if (container.contains(element)) {
1742
+ if (process.env.NODE_ENV === "development") {
1743
+ console.log(
1744
+ `[Store:isElementInAnyScopedProvider] Element is in scoped provider: ${provider.id}`,
1745
+ {
1746
+ elementTag: element.tagName,
1747
+ providerId: provider.id,
1748
+ providerDisabled: provider.disabled
1749
+ }
1750
+ );
1751
+ }
1752
+ return true;
1753
+ }
1754
+ }
1755
+ return false;
1538
1756
  }
1539
1757
  }));
1540
1758
  function dispatchContextMenuEvent(event, element) {
1541
1759
  const store = useProviderStore.getState();
1542
1760
  const providers = store.findProvidersForElement(element);
1761
+ const menuEvent = {
1762
+ clientX: event.clientX,
1763
+ clientY: event.clientY,
1764
+ originalEvent: event,
1765
+ isTouch: false
1766
+ };
1543
1767
  for (const provider of providers) {
1544
1768
  if (store.isDisabledByAncestor(provider.id)) {
1545
1769
  continue;
1546
1770
  }
1547
1771
  if (provider.onContextMenu) {
1548
- provider.onContextMenu(event, element);
1772
+ provider.onContextMenu(menuEvent, element);
1549
1773
  break;
1550
1774
  }
1551
1775
  }
@@ -1575,9 +1799,12 @@ function AnyclickProvider({
1575
1799
  menuClassName,
1576
1800
  disabled = false,
1577
1801
  highlightConfig,
1802
+ header,
1578
1803
  screenshotConfig,
1579
1804
  scoped = false,
1580
- theme
1805
+ theme,
1806
+ touchHoldDurationMs,
1807
+ touchMoveThreshold
1581
1808
  }) {
1582
1809
  const [isSubmitting, setIsSubmitting] = useState3(false);
1583
1810
  const [menuVisible, setMenuVisible] = useState3(false);
@@ -1624,7 +1851,8 @@ function AnyclickProvider({
1624
1851
  getMergedTheme,
1625
1852
  isDisabledByAncestor,
1626
1853
  findParentProvider,
1627
- isElementInDisabledScope
1854
+ isElementInDisabledScope,
1855
+ isElementInAnyScopedProvider
1628
1856
  } = useProviderStore();
1629
1857
  const parentId = parentContext?.providerId ?? null;
1630
1858
  const depth = parentContext ? parentContext.scoped ? 1 : 0 : 0;
@@ -1670,6 +1898,17 @@ function AnyclickProvider({
1670
1898
  }
1671
1899
  return false;
1672
1900
  }
1901
+ if (!scoped && event.isTouch && isElementInAnyScopedProvider(element)) {
1902
+ if (process.env.NODE_ENV === "development") {
1903
+ console.log(
1904
+ `[AnyclickProvider:${providerId}] Deferring to scoped provider for touch event`,
1905
+ {
1906
+ targetTag: element.tagName
1907
+ }
1908
+ );
1909
+ }
1910
+ return false;
1911
+ }
1673
1912
  const mergedTheme2 = getMergedTheme(providerId);
1674
1913
  if (process.env.NODE_ENV === "development") {
1675
1914
  console.log(
@@ -1678,7 +1917,8 @@ function AnyclickProvider({
1678
1917
  scoped,
1679
1918
  targetTag: element.tagName,
1680
1919
  mergedThemeColors: mergedTheme2.highlightConfig?.colors,
1681
- position: { x: event.clientX, y: event.clientY }
1920
+ position: { x: event.clientX, y: event.clientY },
1921
+ isTouch: event.isTouch
1682
1922
  }
1683
1923
  );
1684
1924
  }
@@ -1697,7 +1937,8 @@ function AnyclickProvider({
1697
1937
  getMergedTheme,
1698
1938
  highlightConfig,
1699
1939
  scoped,
1700
- isElementInDisabledScope
1940
+ isElementInDisabledScope,
1941
+ isElementInAnyScopedProvider
1701
1942
  ]
1702
1943
  );
1703
1944
  useLayoutEffect(() => {
@@ -1769,7 +2010,7 @@ function AnyclickProvider({
1769
2010
  localThemeColors: localTheme.highlightConfig?.colors
1770
2011
  });
1771
2012
  }
1772
- const client = createFeedbackClient({
2013
+ const client = createAnyclickClient({
1773
2014
  adapter,
1774
2015
  targetFilter,
1775
2016
  maxInnerTextLength,
@@ -1778,7 +2019,9 @@ function AnyclickProvider({
1778
2019
  cooldownMs,
1779
2020
  stripAttributes,
1780
2021
  // For scoped providers, pass the container
1781
- container: scoped ? containerRef.current : null
2022
+ container: scoped ? containerRef.current : null,
2023
+ touchHoldDurationMs,
2024
+ touchMoveThreshold
1782
2025
  });
1783
2026
  client.onSubmitSuccess = onSubmitSuccess;
1784
2027
  client.onSubmitError = onSubmitError;
@@ -1805,15 +2048,17 @@ function AnyclickProvider({
1805
2048
  containerReady,
1806
2049
  providerId,
1807
2050
  isDisabledByAncestor,
1808
- handleContextMenu
2051
+ handleContextMenu,
2052
+ touchHoldDurationMs,
2053
+ touchMoveThreshold
1809
2054
  ]);
1810
- const submitFeedback = useCallback2(
2055
+ const submitAnyclick = useCallback2(
1811
2056
  async (element, type, comment, screenshots) => {
1812
2057
  const client = clientRef.current;
1813
2058
  if (!client) return;
1814
2059
  setIsSubmitting(true);
1815
2060
  try {
1816
- await client.submitFeedback(element, type, {
2061
+ await client.submitAnyclick(element, type, {
1817
2062
  comment,
1818
2063
  metadata,
1819
2064
  screenshots
@@ -1849,10 +2094,10 @@ function AnyclickProvider({
1849
2094
  const handleMenuSelect = useCallback2(
1850
2095
  (type, comment, screenshots) => {
1851
2096
  if (targetElement) {
1852
- submitFeedback(targetElement, type, comment, screenshots);
2097
+ submitAnyclick(targetElement, type, comment, screenshots);
1853
2098
  }
1854
2099
  },
1855
- [targetElement, submitFeedback]
2100
+ [targetElement, submitAnyclick]
1856
2101
  );
1857
2102
  const inheritedTheme = getMergedTheme(providerId);
1858
2103
  const mergedTheme = useMemo(
@@ -1895,7 +2140,7 @@ function AnyclickProvider({
1895
2140
  () => ({
1896
2141
  isEnabled: !effectiveDisabled && !isDisabledByAncestor(providerId),
1897
2142
  isSubmitting,
1898
- submitFeedback,
2143
+ submitAnyclick,
1899
2144
  openMenu,
1900
2145
  closeMenu,
1901
2146
  theme: mergedTheme,
@@ -1907,7 +2152,7 @@ function AnyclickProvider({
1907
2152
  providerId,
1908
2153
  isDisabledByAncestor,
1909
2154
  isSubmitting,
1910
- submitFeedback,
2155
+ submitAnyclick,
1911
2156
  openMenu,
1912
2157
  closeMenu,
1913
2158
  mergedTheme,
@@ -1931,13 +2176,109 @@ function AnyclickProvider({
1931
2176
  style: effectiveMenuStyle,
1932
2177
  className: effectiveMenuClassName,
1933
2178
  highlightConfig: effectiveHighlightConfig,
1934
- screenshotConfig: effectiveScreenshotConfig
2179
+ screenshotConfig: effectiveScreenshotConfig,
2180
+ header
1935
2181
  }
1936
2182
  )
1937
2183
  ] });
1938
2184
  }
1939
2185
  var FeedbackProvider = AnyclickProvider;
1940
2186
 
2187
+ // src/FunModeBridge.tsx
2188
+ import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3 } from "react";
2189
+ import {
2190
+ usePointer
2191
+ } from "@ewjdev/anyclick-pointer";
2192
+ function isFunModeEnabled(theme) {
2193
+ if (!theme) return false;
2194
+ if (theme.funMode === void 0) return false;
2195
+ if (typeof theme.funMode === "boolean") return theme.funMode;
2196
+ return theme.funMode.enabled ?? true;
2197
+ }
2198
+ function buildFunConfig(theme, container) {
2199
+ const funTheme = typeof theme?.funMode === "object" ? theme.funMode : {};
2200
+ const resolveTrackElement = () => {
2201
+ let el = container;
2202
+ while (el) {
2203
+ const rect = el.getBoundingClientRect();
2204
+ if (rect.width > 0 && rect.height > 0) {
2205
+ return el;
2206
+ }
2207
+ el = el.parentElement;
2208
+ }
2209
+ return document.body;
2210
+ };
2211
+ const getTrackElement = () => resolveTrackElement();
2212
+ return {
2213
+ maxSpeed: funTheme.maxSpeed,
2214
+ acceleration: funTheme.acceleration,
2215
+ getTrackElement,
2216
+ getObstacles: () => {
2217
+ const trackElement = getTrackElement();
2218
+ const obstacles = [];
2219
+ const parent = trackElement.parentElement;
2220
+ if (parent) {
2221
+ Array.from(parent.children).forEach((sibling) => {
2222
+ if (sibling !== trackElement) {
2223
+ obstacles.push(sibling.getBoundingClientRect());
2224
+ }
2225
+ });
2226
+ }
2227
+ Array.from(trackElement.children).forEach((child) => {
2228
+ obstacles.push(child.getBoundingClientRect());
2229
+ });
2230
+ return obstacles;
2231
+ }
2232
+ };
2233
+ }
2234
+ function FunModeBridge() {
2235
+ const { setConfig } = usePointer();
2236
+ const providerStore = useProviderStore();
2237
+ const activeProviderRef = useRef3(null);
2238
+ const cachedConfigs = useRef3({});
2239
+ const findActiveFunProvider = useMemo2(() => {
2240
+ return (el) => {
2241
+ if (!el) return null;
2242
+ const providers = providerStore.findProvidersForElement(el);
2243
+ for (const provider of providers) {
2244
+ if (provider.scoped && !provider.disabled && isFunModeEnabled(provider.theme)) {
2245
+ return provider;
2246
+ }
2247
+ }
2248
+ return null;
2249
+ };
2250
+ }, [providerStore]);
2251
+ useEffect3(() => {
2252
+ const handleMove = (event) => {
2253
+ const el = document.elementFromPoint(event.clientX, event.clientY);
2254
+ const provider = findActiveFunProvider(el);
2255
+ if (!provider || !provider.containerRef.current) {
2256
+ if (activeProviderRef.current !== null) {
2257
+ activeProviderRef.current = null;
2258
+ setConfig({ mode: "normal" });
2259
+ }
2260
+ return;
2261
+ }
2262
+ if (activeProviderRef.current === provider.id) {
2263
+ return;
2264
+ }
2265
+ activeProviderRef.current = provider.id;
2266
+ if (!cachedConfigs.current[provider.id]) {
2267
+ cachedConfigs.current[provider.id] = {
2268
+ mode: "fun",
2269
+ funConfig: buildFunConfig(provider.theme, provider.containerRef.current)
2270
+ };
2271
+ }
2272
+ setConfig(cachedConfigs.current[provider.id]);
2273
+ };
2274
+ window.addEventListener("pointermove", handleMove, { passive: true });
2275
+ return () => {
2276
+ window.removeEventListener("pointermove", handleMove);
2277
+ };
2278
+ }, [findActiveFunProvider, setConfig]);
2279
+ return null;
2280
+ }
2281
+
1941
2282
  // src/types.ts
1942
2283
  function filterMenuItemsByRole(items, userContext) {
1943
2284
  if (!userContext) {
@@ -1972,6 +2313,7 @@ export {
1972
2313
  DEFAULT_SENSITIVE_SELECTORS,
1973
2314
  FeedbackContext,
1974
2315
  FeedbackProvider,
2316
+ FunModeBridge,
1975
2317
  ScreenshotPreview,
1976
2318
  applyHighlights,
1977
2319
  captureAllScreenshots2 as captureAllScreenshots,
@@ -2008,6 +2350,7 @@ lucide-react/dist/esm/icons/chevron-right.js:
2008
2350
  lucide-react/dist/esm/icons/circle-alert.js:
2009
2351
  lucide-react/dist/esm/icons/expand.js:
2010
2352
  lucide-react/dist/esm/icons/flag.js:
2353
+ lucide-react/dist/esm/icons/grip-vertical.js:
2011
2354
  lucide-react/dist/esm/icons/image.js:
2012
2355
  lucide-react/dist/esm/icons/loader-circle.js:
2013
2356
  lucide-react/dist/esm/icons/plus.js: