@ewjdev/anyclick-react 1.1.1 → 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} {
@@ -1030,19 +1034,27 @@ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1030
1034
  var VIEWPORT_PADDING = 10;
1031
1035
  var screenshotIndicatorStyle = {
1032
1036
  display: "flex",
1033
- alignItems: "center",
1034
- gap: "6px",
1035
- padding: "8px 12px",
1036
- fontSize: "11px",
1037
- color: "var(--anyclick-menu-text-muted, #9ca3af)",
1038
- borderTop: "1px solid var(--anyclick-menu-border, #f3f4f6)",
1039
- marginTop: "4px"
1037
+ marginLeft: "4px",
1038
+ opacity: 0.7,
1039
+ justifyContent: "flex-end",
1040
+ flex: 1
1040
1041
  };
1041
1042
  var defaultIcons = {
1042
1043
  issue: /* @__PURE__ */ jsx2(Flag, { className: "w-4 h-4" }),
1043
1044
  feature: /* @__PURE__ */ jsx2(Plus, { className: "w-4 h-4" }),
1044
1045
  like: /* @__PURE__ */ jsx2(ThumbsUp, { className: "w-4 h-4" })
1045
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
+ };
1046
1058
  function MenuItem({
1047
1059
  item,
1048
1060
  onClick,
@@ -1220,7 +1232,9 @@ function ContextMenu({
1220
1232
  className,
1221
1233
  highlightConfig,
1222
1234
  screenshotConfig,
1223
- positionMode = "inView"
1235
+ positionMode = "inView",
1236
+ header,
1237
+ footer
1224
1238
  }) {
1225
1239
  const [selectedType, setSelectedType] = useState2(null);
1226
1240
  const [currentView, setCurrentView] = useState2("menu");
@@ -1409,11 +1423,24 @@ function ContextMenu({
1409
1423
  if (!visible || !targetElement) {
1410
1424
  return null;
1411
1425
  }
1412
- const handleItemClick = (item) => {
1426
+ const handleItemClick = async (item) => {
1413
1427
  if (item.children && item.children.length > 0) {
1414
1428
  setSubmenuStack((prev) => [...prev, item.children]);
1415
1429
  return;
1416
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
+ }
1417
1444
  if (item.showComment) {
1418
1445
  setSelectedType(item.type);
1419
1446
  setCurrentView("comment");
@@ -1494,45 +1521,35 @@ function ContextMenu({
1494
1521
  role: "menu",
1495
1522
  "aria-label": "Feedback options",
1496
1523
  children: [
1497
- currentView !== "screenshot-preview" && /* @__PURE__ */ jsxs2(
1498
- "div",
1499
- {
1500
- style: {
1501
- ...menuStyles.header,
1502
- display: "flex",
1503
- alignItems: "center",
1504
- justifyContent: "space-between"
1505
- },
1506
- children: [
1507
- /* @__PURE__ */ jsx2("span", { children: "Send Feedback" }),
1508
- positionMode === "dynamic" && /* @__PURE__ */ jsx2(
1509
- "div",
1510
- {
1511
- "data-drag-handle": true,
1512
- onPointerDown: handleDragStart,
1513
- style: {
1514
- cursor: isDragging ? "grabbing" : "grab",
1515
- padding: "4px",
1516
- marginRight: "-4px",
1517
- borderRadius: "4px",
1518
- display: "flex",
1519
- alignItems: "center",
1520
- opacity: 0.5,
1521
- transition: "opacity 0.15s"
1522
- },
1523
- onMouseEnter: (e) => {
1524
- e.currentTarget.style.opacity = "1";
1525
- },
1526
- onMouseLeave: (e) => {
1527
- e.currentTarget.style.opacity = "0.5";
1528
- },
1529
- title: "Drag to move",
1530
- children: /* @__PURE__ */ jsx2(GripVertical, { className: "w-4 h-4" })
1531
- }
1532
- )
1533
- ]
1534
- }
1535
- ),
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,
1536
1553
  currentView === "menu" && /* @__PURE__ */ jsxs2("div", { style: menuStyles.itemList, children: [
1537
1554
  submenuStack.length > 0 && /* @__PURE__ */ jsx2(BackButton, { onClick: handleBack }),
1538
1555
  currentItems.map((item) => /* @__PURE__ */ jsx2(
@@ -1544,11 +1561,7 @@ function ContextMenu({
1544
1561
  hasChildren: item.children && item.children.length > 0
1545
1562
  },
1546
1563
  item.type
1547
- )),
1548
- showPreview && /* @__PURE__ */ jsxs2("div", { style: screenshotIndicatorStyle, children: [
1549
- /* @__PURE__ */ jsx2(Camera, { className: "w-3 h-3" }),
1550
- /* @__PURE__ */ jsx2("span", { children: "Screenshots will be captured" })
1551
- ] })
1564
+ ))
1552
1565
  ] }),
1553
1566
  currentView === "comment" && /* @__PURE__ */ jsx2(
1554
1567
  CommentForm,
@@ -1786,6 +1799,7 @@ function AnyclickProvider({
1786
1799
  menuClassName,
1787
1800
  disabled = false,
1788
1801
  highlightConfig,
1802
+ header,
1789
1803
  screenshotConfig,
1790
1804
  scoped = false,
1791
1805
  theme,
@@ -1996,7 +2010,7 @@ function AnyclickProvider({
1996
2010
  localThemeColors: localTheme.highlightConfig?.colors
1997
2011
  });
1998
2012
  }
1999
- const client = createFeedbackClient({
2013
+ const client = createAnyclickClient({
2000
2014
  adapter,
2001
2015
  targetFilter,
2002
2016
  maxInnerTextLength,
@@ -2038,13 +2052,13 @@ function AnyclickProvider({
2038
2052
  touchHoldDurationMs,
2039
2053
  touchMoveThreshold
2040
2054
  ]);
2041
- const submitFeedback = useCallback2(
2055
+ const submitAnyclick = useCallback2(
2042
2056
  async (element, type, comment, screenshots) => {
2043
2057
  const client = clientRef.current;
2044
2058
  if (!client) return;
2045
2059
  setIsSubmitting(true);
2046
2060
  try {
2047
- await client.submitFeedback(element, type, {
2061
+ await client.submitAnyclick(element, type, {
2048
2062
  comment,
2049
2063
  metadata,
2050
2064
  screenshots
@@ -2080,10 +2094,10 @@ function AnyclickProvider({
2080
2094
  const handleMenuSelect = useCallback2(
2081
2095
  (type, comment, screenshots) => {
2082
2096
  if (targetElement) {
2083
- submitFeedback(targetElement, type, comment, screenshots);
2097
+ submitAnyclick(targetElement, type, comment, screenshots);
2084
2098
  }
2085
2099
  },
2086
- [targetElement, submitFeedback]
2100
+ [targetElement, submitAnyclick]
2087
2101
  );
2088
2102
  const inheritedTheme = getMergedTheme(providerId);
2089
2103
  const mergedTheme = useMemo(
@@ -2126,7 +2140,7 @@ function AnyclickProvider({
2126
2140
  () => ({
2127
2141
  isEnabled: !effectiveDisabled && !isDisabledByAncestor(providerId),
2128
2142
  isSubmitting,
2129
- submitFeedback,
2143
+ submitAnyclick,
2130
2144
  openMenu,
2131
2145
  closeMenu,
2132
2146
  theme: mergedTheme,
@@ -2138,7 +2152,7 @@ function AnyclickProvider({
2138
2152
  providerId,
2139
2153
  isDisabledByAncestor,
2140
2154
  isSubmitting,
2141
- submitFeedback,
2155
+ submitAnyclick,
2142
2156
  openMenu,
2143
2157
  closeMenu,
2144
2158
  mergedTheme,
@@ -2162,13 +2176,109 @@ function AnyclickProvider({
2162
2176
  style: effectiveMenuStyle,
2163
2177
  className: effectiveMenuClassName,
2164
2178
  highlightConfig: effectiveHighlightConfig,
2165
- screenshotConfig: effectiveScreenshotConfig
2179
+ screenshotConfig: effectiveScreenshotConfig,
2180
+ header
2166
2181
  }
2167
2182
  )
2168
2183
  ] });
2169
2184
  }
2170
2185
  var FeedbackProvider = AnyclickProvider;
2171
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
+
2172
2282
  // src/types.ts
2173
2283
  function filterMenuItemsByRole(items, userContext) {
2174
2284
  if (!userContext) {
@@ -2203,6 +2313,7 @@ export {
2203
2313
  DEFAULT_SENSITIVE_SELECTORS,
2204
2314
  FeedbackContext,
2205
2315
  FeedbackProvider,
2316
+ FunModeBridge,
2206
2317
  ScreenshotPreview,
2207
2318
  applyHighlights,
2208
2319
  captureAllScreenshots2 as captureAllScreenshots,