@sylergydigital/issue-pin-sdk 0.6.3 → 0.6.5

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
@@ -2096,7 +2096,7 @@ function ScreenshotFeedback() {
2096
2096
  }
2097
2097
 
2098
2098
  // src/FeedbackButton.tsx
2099
- import { useRef as useRef5, useEffect as useEffect6, useState as useState7 } from "react";
2099
+ import { useRef as useRef5, useEffect as useEffect6, useState as useState7, useCallback as useCallback8 } from "react";
2100
2100
  import { MessageSquarePlus, MapPin, Camera as Camera2 } from "lucide-react";
2101
2101
 
2102
2102
  // src/launcher.ts
@@ -2118,10 +2118,52 @@ function getLauncherCapabilities({
2118
2118
 
2119
2119
  // src/FeedbackButton.tsx
2120
2120
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2121
+ var LAUNCHER_POS_KEY = "issue-pin:launcher-pos";
2122
+ var DRAG_THRESHOLD = 5;
2123
+ var EDGE_MARGIN = 20;
2124
+ var BTN_SIZE = 48;
2125
+ var SNAP_EASING = "cubic-bezier(0.2, 0, 0, 1)";
2126
+ var SNAP_TRANSITION = `left 0.3s ${SNAP_EASING}, top 0.3s ${SNAP_EASING}`;
2127
+ function loadPos(initialEdge) {
2128
+ try {
2129
+ const raw = localStorage.getItem(LAUNCHER_POS_KEY);
2130
+ if (raw) {
2131
+ const parsed = JSON.parse(raw);
2132
+ if ((parsed.edge === "left" || parsed.edge === "right") && typeof parsed.y === "number") {
2133
+ return { edge: parsed.edge, y: parsed.y };
2134
+ }
2135
+ }
2136
+ } catch {
2137
+ }
2138
+ return {
2139
+ edge: initialEdge === "bottom-left" ? "left" : "right",
2140
+ y: window.innerHeight - BTN_SIZE - EDGE_MARGIN
2141
+ };
2142
+ }
2143
+ function savePos(pos) {
2144
+ try {
2145
+ localStorage.setItem(LAUNCHER_POS_KEY, JSON.stringify(pos));
2146
+ } catch {
2147
+ }
2148
+ }
2149
+ function clampY(y) {
2150
+ return Math.min(
2151
+ Math.max(y, EDGE_MARGIN),
2152
+ window.innerHeight - BTN_SIZE - EDGE_MARGIN
2153
+ );
2154
+ }
2155
+ function snapXForEdge(edge) {
2156
+ return edge === "left" ? EDGE_MARGIN : window.innerWidth - BTN_SIZE - EDGE_MARGIN;
2157
+ }
2121
2158
  function FeedbackButton({ position = "bottom-right" }) {
2122
2159
  const ctx = useFeedbackSafe();
2123
2160
  const menuRef = useRef5(null);
2124
2161
  const [hintDismissed, setHintDismissed] = useState7(false);
2162
+ const [pos, setPos] = useState7(() => loadPos(position));
2163
+ const [dragXY, setDragXY] = useState7(null);
2164
+ const [snapping, setSnapping] = useState7(false);
2165
+ const dragStateRef = useRef5(null);
2166
+ const snapTimerRef = useRef5(null);
2125
2167
  const menuOpenState = ctx?.menuOpen ?? false;
2126
2168
  const debug = ctx?.debug ?? false;
2127
2169
  useEffect6(() => {
@@ -2139,6 +2181,79 @@ function FeedbackButton({ position = "bottom-right" }) {
2139
2181
  console.log("[EW SDK] FeedbackButton mounted");
2140
2182
  return () => console.log("[EW SDK] FeedbackButton unmounted");
2141
2183
  }, [debug]);
2184
+ useEffect6(() => {
2185
+ const handleResize = () => {
2186
+ setPos((prev) => {
2187
+ const newPos = {
2188
+ edge: prev.edge,
2189
+ y: clampY(prev.y)
2190
+ };
2191
+ savePos(newPos);
2192
+ return newPos;
2193
+ });
2194
+ };
2195
+ window.addEventListener("resize", handleResize);
2196
+ return () => window.removeEventListener("resize", handleResize);
2197
+ }, []);
2198
+ useEffect6(() => {
2199
+ return () => {
2200
+ if (snapTimerRef.current) clearTimeout(snapTimerRef.current);
2201
+ };
2202
+ }, []);
2203
+ const handlePointerDown = useCallback8((e) => {
2204
+ if (e.button != null && e.button !== 0) return;
2205
+ e.currentTarget.setPointerCapture(e.pointerId);
2206
+ dragStateRef.current = {
2207
+ active: false,
2208
+ startX: e.clientX,
2209
+ startY: e.clientY,
2210
+ currentX: e.clientX,
2211
+ currentY: e.clientY,
2212
+ didDrag: false
2213
+ };
2214
+ }, []);
2215
+ const handlePointerMove = useCallback8((e) => {
2216
+ const ds = dragStateRef.current;
2217
+ if (!ds) return;
2218
+ const dx = e.clientX - ds.startX;
2219
+ const dy = e.clientY - ds.startY;
2220
+ const distance = Math.sqrt(dx * dx + dy * dy);
2221
+ if (!ds.active && distance >= DRAG_THRESHOLD) {
2222
+ ds.active = true;
2223
+ ds.didDrag = true;
2224
+ }
2225
+ if (ds.active) {
2226
+ ds.currentX = e.clientX;
2227
+ ds.currentY = e.clientY;
2228
+ const x = e.clientX - BTN_SIZE / 2;
2229
+ const y = clampY(e.clientY - BTN_SIZE / 2);
2230
+ setDragXY({ x, y });
2231
+ }
2232
+ }, []);
2233
+ const handlePointerUp = useCallback8((e) => {
2234
+ try {
2235
+ e.currentTarget.releasePointerCapture(e.pointerId);
2236
+ } catch {
2237
+ }
2238
+ const ds = dragStateRef.current;
2239
+ if (!ds || !ds.didDrag) {
2240
+ dragStateRef.current = null;
2241
+ return;
2242
+ }
2243
+ const centerX = ds.currentX;
2244
+ const edge = centerX < window.innerWidth / 2 ? "left" : "right";
2245
+ const y = clampY(ds.currentY - BTN_SIZE / 2);
2246
+ const newPos = { edge, y };
2247
+ setSnapping(true);
2248
+ setPos(newPos);
2249
+ savePos(newPos);
2250
+ setDragXY(null);
2251
+ if (snapTimerRef.current) clearTimeout(snapTimerRef.current);
2252
+ snapTimerRef.current = setTimeout(() => {
2253
+ setSnapping(false);
2254
+ snapTimerRef.current = null;
2255
+ }, 300);
2256
+ }, []);
2142
2257
  if (!ctx) return null;
2143
2258
  const {
2144
2259
  mode,
@@ -2154,9 +2269,13 @@ function FeedbackButton({ position = "bottom-right" }) {
2154
2269
  } = ctx;
2155
2270
  const annotate = mode === "annotate";
2156
2271
  const capabilities = getLauncherCapabilities({ allowPinOnPage: canPinOnPage, allowScreenshot: canScreenshot });
2157
- const posStyle = position === "bottom-left" ? { left: 20, bottom: 20 } : { right: 20, bottom: 20 };
2158
- const hintStyle = position === "bottom-left" ? { left: 56, bottom: 8 } : { right: 56, bottom: 8 };
2159
2272
  const handleToggle = () => {
2273
+ if (dragStateRef.current?.didDrag) {
2274
+ dragStateRef.current.didDrag = false;
2275
+ dragStateRef.current = null;
2276
+ return;
2277
+ }
2278
+ dragStateRef.current = null;
2160
2279
  if (debug) console.log("[EW SDK] handleToggle", { mode, menuOpen });
2161
2280
  setHintDismissed(true);
2162
2281
  if (annotate) {
@@ -2176,12 +2295,44 @@ function FeedbackButton({ position = "bottom-right" }) {
2176
2295
  const capturing = screenshotCapturing;
2177
2296
  const showLauncherHint = showHints && !hintDismissed && !annotate && !menuOpen;
2178
2297
  if (capabilities.actionCount === 0) return null;
2298
+ const restX = snapXForEdge(pos.edge);
2299
+ let wrapperStyle;
2300
+ if (dragXY !== null) {
2301
+ wrapperStyle = {
2302
+ position: "fixed",
2303
+ left: dragXY.x,
2304
+ top: dragXY.y,
2305
+ transition: "none",
2306
+ zIndex: Z.launcher
2307
+ };
2308
+ } else if (snapping) {
2309
+ wrapperStyle = {
2310
+ position: "fixed",
2311
+ left: restX,
2312
+ top: pos.y,
2313
+ transition: SNAP_TRANSITION,
2314
+ zIndex: Z.launcher
2315
+ };
2316
+ } else {
2317
+ wrapperStyle = {
2318
+ position: "fixed",
2319
+ left: restX,
2320
+ top: pos.y,
2321
+ transition: "none",
2322
+ zIndex: Z.launcher
2323
+ };
2324
+ }
2325
+ const hintStyle = pos.edge === "right" ? { right: BTN_SIZE + 8, top: "50%", transform: "translateY(-50%)" } : { left: BTN_SIZE + 8, top: "50%", transform: "translateY(-50%)" };
2326
+ const menuPositionStyle = pos.edge === "right" ? { right: 0, bottom: BTN_SIZE + 4 } : { left: 0, bottom: BTN_SIZE + 4 };
2179
2327
  return /* @__PURE__ */ jsxs6(
2180
2328
  "div",
2181
2329
  {
2182
2330
  ref: menuRef,
2183
2331
  "data-ew-feedback-interactive": "true",
2184
- style: { position: "fixed", ...posStyle, zIndex: Z.launcher },
2332
+ style: { ...wrapperStyle, touchAction: "none" },
2333
+ onPointerDown: handlePointerDown,
2334
+ onPointerMove: handlePointerMove,
2335
+ onPointerUp: handlePointerUp,
2185
2336
  children: [
2186
2337
  showLauncherHint && /* @__PURE__ */ jsxs6(
2187
2338
  "div",
@@ -2208,8 +2359,7 @@ function FeedbackButton({ position = "bottom-right" }) {
2208
2359
  {
2209
2360
  style: {
2210
2361
  position: "absolute",
2211
- bottom: 56,
2212
- right: 0,
2362
+ ...menuPositionStyle,
2213
2363
  marginBottom: 4,
2214
2364
  width: 176,
2215
2365
  borderRadius: 8,
@@ -2274,7 +2424,7 @@ function FeedbackButton({ position = "bottom-right" }) {
2274
2424
  children: [
2275
2425
  /* @__PURE__ */ jsx7(Camera2, { style: { width: 16, height: 16, color: T.primary, flexShrink: 0 } }),
2276
2426
  /* @__PURE__ */ jsxs6("div", { children: [
2277
- /* @__PURE__ */ jsx7("div", { style: { fontWeight: 500 }, children: capturing ? "Capturing\u2026" : "Screenshot" }),
2427
+ /* @__PURE__ */ jsx7("div", { style: { fontWeight: 500 }, children: capturing ? "Capturing..." : "Screenshot" }),
2278
2428
  /* @__PURE__ */ jsx7("div", { style: { fontSize: 10, color: T.muted }, children: "Capture current view" })
2279
2429
  ] })
2280
2430
  ]
@@ -2289,8 +2439,8 @@ function FeedbackButton({ position = "bottom-right" }) {
2289
2439
  onClick: handleToggle,
2290
2440
  style: {
2291
2441
  display: "flex",
2292
- height: 48,
2293
- width: 48,
2442
+ height: BTN_SIZE,
2443
+ width: BTN_SIZE,
2294
2444
  alignItems: "center",
2295
2445
  justifyContent: "center",
2296
2446
  borderRadius: "50%",
@@ -2299,7 +2449,7 @@ function FeedbackButton({ position = "bottom-right" }) {
2299
2449
  border: annotate ? "none" : `1px solid ${T.border}`,
2300
2450
  background: annotate ? T.primary : T.card,
2301
2451
  color: annotate ? T.primaryFg : T.fg,
2302
- cursor: "pointer",
2452
+ cursor: dragXY !== null ? "grabbing" : "pointer",
2303
2453
  transform: annotate ? "scale(1.1)" : "scale(1)"
2304
2454
  },
2305
2455
  title: annotate ? "Exit feedback mode" : "Open feedback menu",
@@ -2313,7 +2463,7 @@ function FeedbackButton({ position = "bottom-right" }) {
2313
2463
  }
2314
2464
 
2315
2465
  // src/ModalFeedbackInjector.tsx
2316
- import { useState as useState8, useEffect as useEffect7, useCallback as useCallback8 } from "react";
2466
+ import { useState as useState8, useEffect as useEffect7, useCallback as useCallback9 } from "react";
2317
2467
  import { createPortal as createPortal3 } from "react-dom";
2318
2468
  import { Camera as Camera3 } from "lucide-react";
2319
2469
  import { Fragment as Fragment4, jsx as jsx8 } from "react/jsx-runtime";
@@ -2332,7 +2482,7 @@ function ModalFeedbackInjector({
2332
2482
  observer.observe(document.body, { childList: true, subtree: true });
2333
2483
  return () => observer.disconnect();
2334
2484
  }, []);
2335
- const handleCapture = useCallback8(async () => {
2485
+ const handleCapture = useCallback9(async () => {
2336
2486
  if (!ctx) return;
2337
2487
  await ctx.startScreenshotCapture();
2338
2488
  }, [ctx]);