@sylergydigital/issue-pin-sdk 0.6.4 → 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/CHANGELOG.md +17 -0
- package/dist/index.cjs +159 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +162 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@sylergydigital/issue-pin-sdk` are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.6.5] - 2026-04-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Draggable launcher button** — the feedback launcher can now be freely dragged across the viewport using mouse, touch, or pen input (iOS AssistiveTouch pattern). On release, the button smoothly snaps to the nearest left or right viewport edge.
|
|
9
|
+
- Position persistence — launcher position (edge + Y offset) is saved to `localStorage` and restored across page reloads.
|
|
10
|
+
- Menu and hint toast auto-flip to the opposite side of the docked edge so they never render off-screen.
|
|
11
|
+
- Viewport resize clamping — button repositions to stay within bounds when the window is resized.
|
|
12
|
+
- Click vs. drag disambiguation — short interactions (< 5px movement) still toggle the menu normally.
|
|
13
|
+
|
|
14
|
+
## [0.6.4] - 2026-04-14
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Included `CHANGELOG.md` in the SDK npm publish pipeline (`files` array in package.json).
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- SDK CHANGELOG.md with full release history.
|
|
21
|
+
|
|
5
22
|
## [0.6.3] - 2026-04-14
|
|
6
23
|
|
|
7
24
|
### Added
|
package/dist/index.cjs
CHANGED
|
@@ -2168,10 +2168,52 @@ function getLauncherCapabilities({
|
|
|
2168
2168
|
|
|
2169
2169
|
// src/FeedbackButton.tsx
|
|
2170
2170
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2171
|
+
var LAUNCHER_POS_KEY = "issue-pin:launcher-pos";
|
|
2172
|
+
var DRAG_THRESHOLD = 5;
|
|
2173
|
+
var EDGE_MARGIN = 20;
|
|
2174
|
+
var BTN_SIZE = 48;
|
|
2175
|
+
var SNAP_EASING = "cubic-bezier(0.2, 0, 0, 1)";
|
|
2176
|
+
var SNAP_TRANSITION = `left 0.3s ${SNAP_EASING}, top 0.3s ${SNAP_EASING}`;
|
|
2177
|
+
function loadPos(initialEdge) {
|
|
2178
|
+
try {
|
|
2179
|
+
const raw = localStorage.getItem(LAUNCHER_POS_KEY);
|
|
2180
|
+
if (raw) {
|
|
2181
|
+
const parsed = JSON.parse(raw);
|
|
2182
|
+
if ((parsed.edge === "left" || parsed.edge === "right") && typeof parsed.y === "number") {
|
|
2183
|
+
return { edge: parsed.edge, y: parsed.y };
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
} catch {
|
|
2187
|
+
}
|
|
2188
|
+
return {
|
|
2189
|
+
edge: initialEdge === "bottom-left" ? "left" : "right",
|
|
2190
|
+
y: window.innerHeight - BTN_SIZE - EDGE_MARGIN
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
function savePos(pos) {
|
|
2194
|
+
try {
|
|
2195
|
+
localStorage.setItem(LAUNCHER_POS_KEY, JSON.stringify(pos));
|
|
2196
|
+
} catch {
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
function clampY(y) {
|
|
2200
|
+
return Math.min(
|
|
2201
|
+
Math.max(y, EDGE_MARGIN),
|
|
2202
|
+
window.innerHeight - BTN_SIZE - EDGE_MARGIN
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
function snapXForEdge(edge) {
|
|
2206
|
+
return edge === "left" ? EDGE_MARGIN : window.innerWidth - BTN_SIZE - EDGE_MARGIN;
|
|
2207
|
+
}
|
|
2171
2208
|
function FeedbackButton({ position = "bottom-right" }) {
|
|
2172
2209
|
const ctx = useFeedbackSafe();
|
|
2173
2210
|
const menuRef = (0, import_react9.useRef)(null);
|
|
2174
2211
|
const [hintDismissed, setHintDismissed] = (0, import_react9.useState)(false);
|
|
2212
|
+
const [pos, setPos] = (0, import_react9.useState)(() => loadPos(position));
|
|
2213
|
+
const [dragXY, setDragXY] = (0, import_react9.useState)(null);
|
|
2214
|
+
const [snapping, setSnapping] = (0, import_react9.useState)(false);
|
|
2215
|
+
const dragStateRef = (0, import_react9.useRef)(null);
|
|
2216
|
+
const snapTimerRef = (0, import_react9.useRef)(null);
|
|
2175
2217
|
const menuOpenState = ctx?.menuOpen ?? false;
|
|
2176
2218
|
const debug = ctx?.debug ?? false;
|
|
2177
2219
|
(0, import_react9.useEffect)(() => {
|
|
@@ -2189,6 +2231,79 @@ function FeedbackButton({ position = "bottom-right" }) {
|
|
|
2189
2231
|
console.log("[EW SDK] FeedbackButton mounted");
|
|
2190
2232
|
return () => console.log("[EW SDK] FeedbackButton unmounted");
|
|
2191
2233
|
}, [debug]);
|
|
2234
|
+
(0, import_react9.useEffect)(() => {
|
|
2235
|
+
const handleResize = () => {
|
|
2236
|
+
setPos((prev) => {
|
|
2237
|
+
const newPos = {
|
|
2238
|
+
edge: prev.edge,
|
|
2239
|
+
y: clampY(prev.y)
|
|
2240
|
+
};
|
|
2241
|
+
savePos(newPos);
|
|
2242
|
+
return newPos;
|
|
2243
|
+
});
|
|
2244
|
+
};
|
|
2245
|
+
window.addEventListener("resize", handleResize);
|
|
2246
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
2247
|
+
}, []);
|
|
2248
|
+
(0, import_react9.useEffect)(() => {
|
|
2249
|
+
return () => {
|
|
2250
|
+
if (snapTimerRef.current) clearTimeout(snapTimerRef.current);
|
|
2251
|
+
};
|
|
2252
|
+
}, []);
|
|
2253
|
+
const handlePointerDown = (0, import_react9.useCallback)((e) => {
|
|
2254
|
+
if (e.button != null && e.button !== 0) return;
|
|
2255
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
2256
|
+
dragStateRef.current = {
|
|
2257
|
+
active: false,
|
|
2258
|
+
startX: e.clientX,
|
|
2259
|
+
startY: e.clientY,
|
|
2260
|
+
currentX: e.clientX,
|
|
2261
|
+
currentY: e.clientY,
|
|
2262
|
+
didDrag: false
|
|
2263
|
+
};
|
|
2264
|
+
}, []);
|
|
2265
|
+
const handlePointerMove = (0, import_react9.useCallback)((e) => {
|
|
2266
|
+
const ds = dragStateRef.current;
|
|
2267
|
+
if (!ds) return;
|
|
2268
|
+
const dx = e.clientX - ds.startX;
|
|
2269
|
+
const dy = e.clientY - ds.startY;
|
|
2270
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
2271
|
+
if (!ds.active && distance >= DRAG_THRESHOLD) {
|
|
2272
|
+
ds.active = true;
|
|
2273
|
+
ds.didDrag = true;
|
|
2274
|
+
}
|
|
2275
|
+
if (ds.active) {
|
|
2276
|
+
ds.currentX = e.clientX;
|
|
2277
|
+
ds.currentY = e.clientY;
|
|
2278
|
+
const x = e.clientX - BTN_SIZE / 2;
|
|
2279
|
+
const y = clampY(e.clientY - BTN_SIZE / 2);
|
|
2280
|
+
setDragXY({ x, y });
|
|
2281
|
+
}
|
|
2282
|
+
}, []);
|
|
2283
|
+
const handlePointerUp = (0, import_react9.useCallback)((e) => {
|
|
2284
|
+
try {
|
|
2285
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
2286
|
+
} catch {
|
|
2287
|
+
}
|
|
2288
|
+
const ds = dragStateRef.current;
|
|
2289
|
+
if (!ds || !ds.didDrag) {
|
|
2290
|
+
dragStateRef.current = null;
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
const centerX = ds.currentX;
|
|
2294
|
+
const edge = centerX < window.innerWidth / 2 ? "left" : "right";
|
|
2295
|
+
const y = clampY(ds.currentY - BTN_SIZE / 2);
|
|
2296
|
+
const newPos = { edge, y };
|
|
2297
|
+
setSnapping(true);
|
|
2298
|
+
setPos(newPos);
|
|
2299
|
+
savePos(newPos);
|
|
2300
|
+
setDragXY(null);
|
|
2301
|
+
if (snapTimerRef.current) clearTimeout(snapTimerRef.current);
|
|
2302
|
+
snapTimerRef.current = setTimeout(() => {
|
|
2303
|
+
setSnapping(false);
|
|
2304
|
+
snapTimerRef.current = null;
|
|
2305
|
+
}, 300);
|
|
2306
|
+
}, []);
|
|
2192
2307
|
if (!ctx) return null;
|
|
2193
2308
|
const {
|
|
2194
2309
|
mode,
|
|
@@ -2204,9 +2319,13 @@ function FeedbackButton({ position = "bottom-right" }) {
|
|
|
2204
2319
|
} = ctx;
|
|
2205
2320
|
const annotate = mode === "annotate";
|
|
2206
2321
|
const capabilities = getLauncherCapabilities({ allowPinOnPage: canPinOnPage, allowScreenshot: canScreenshot });
|
|
2207
|
-
const posStyle = position === "bottom-left" ? { left: 20, bottom: 20 } : { right: 20, bottom: 20 };
|
|
2208
|
-
const hintStyle = position === "bottom-left" ? { left: 56, bottom: 8 } : { right: 56, bottom: 8 };
|
|
2209
2322
|
const handleToggle = () => {
|
|
2323
|
+
if (dragStateRef.current?.didDrag) {
|
|
2324
|
+
dragStateRef.current.didDrag = false;
|
|
2325
|
+
dragStateRef.current = null;
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
dragStateRef.current = null;
|
|
2210
2329
|
if (debug) console.log("[EW SDK] handleToggle", { mode, menuOpen });
|
|
2211
2330
|
setHintDismissed(true);
|
|
2212
2331
|
if (annotate) {
|
|
@@ -2226,12 +2345,44 @@ function FeedbackButton({ position = "bottom-right" }) {
|
|
|
2226
2345
|
const capturing = screenshotCapturing;
|
|
2227
2346
|
const showLauncherHint = showHints && !hintDismissed && !annotate && !menuOpen;
|
|
2228
2347
|
if (capabilities.actionCount === 0) return null;
|
|
2348
|
+
const restX = snapXForEdge(pos.edge);
|
|
2349
|
+
let wrapperStyle;
|
|
2350
|
+
if (dragXY !== null) {
|
|
2351
|
+
wrapperStyle = {
|
|
2352
|
+
position: "fixed",
|
|
2353
|
+
left: dragXY.x,
|
|
2354
|
+
top: dragXY.y,
|
|
2355
|
+
transition: "none",
|
|
2356
|
+
zIndex: Z.launcher
|
|
2357
|
+
};
|
|
2358
|
+
} else if (snapping) {
|
|
2359
|
+
wrapperStyle = {
|
|
2360
|
+
position: "fixed",
|
|
2361
|
+
left: restX,
|
|
2362
|
+
top: pos.y,
|
|
2363
|
+
transition: SNAP_TRANSITION,
|
|
2364
|
+
zIndex: Z.launcher
|
|
2365
|
+
};
|
|
2366
|
+
} else {
|
|
2367
|
+
wrapperStyle = {
|
|
2368
|
+
position: "fixed",
|
|
2369
|
+
left: restX,
|
|
2370
|
+
top: pos.y,
|
|
2371
|
+
transition: "none",
|
|
2372
|
+
zIndex: Z.launcher
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
const hintStyle = pos.edge === "right" ? { right: BTN_SIZE + 8, top: "50%", transform: "translateY(-50%)" } : { left: BTN_SIZE + 8, top: "50%", transform: "translateY(-50%)" };
|
|
2376
|
+
const menuPositionStyle = pos.edge === "right" ? { right: 0, bottom: BTN_SIZE + 4 } : { left: 0, bottom: BTN_SIZE + 4 };
|
|
2229
2377
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2230
2378
|
"div",
|
|
2231
2379
|
{
|
|
2232
2380
|
ref: menuRef,
|
|
2233
2381
|
"data-ew-feedback-interactive": "true",
|
|
2234
|
-
style: {
|
|
2382
|
+
style: { ...wrapperStyle, touchAction: "none" },
|
|
2383
|
+
onPointerDown: handlePointerDown,
|
|
2384
|
+
onPointerMove: handlePointerMove,
|
|
2385
|
+
onPointerUp: handlePointerUp,
|
|
2235
2386
|
children: [
|
|
2236
2387
|
showLauncherHint && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2237
2388
|
"div",
|
|
@@ -2258,8 +2409,7 @@ function FeedbackButton({ position = "bottom-right" }) {
|
|
|
2258
2409
|
{
|
|
2259
2410
|
style: {
|
|
2260
2411
|
position: "absolute",
|
|
2261
|
-
|
|
2262
|
-
right: 0,
|
|
2412
|
+
...menuPositionStyle,
|
|
2263
2413
|
marginBottom: 4,
|
|
2264
2414
|
width: 176,
|
|
2265
2415
|
borderRadius: 8,
|
|
@@ -2324,7 +2474,7 @@ function FeedbackButton({ position = "bottom-right" }) {
|
|
|
2324
2474
|
children: [
|
|
2325
2475
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Camera, { style: { width: 16, height: 16, color: T.primary, flexShrink: 0 } }),
|
|
2326
2476
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
2327
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { fontWeight: 500 }, children: capturing ? "Capturing
|
|
2477
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { fontWeight: 500 }, children: capturing ? "Capturing..." : "Screenshot" }),
|
|
2328
2478
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { fontSize: 10, color: T.muted }, children: "Capture current view" })
|
|
2329
2479
|
] })
|
|
2330
2480
|
]
|
|
@@ -2339,8 +2489,8 @@ function FeedbackButton({ position = "bottom-right" }) {
|
|
|
2339
2489
|
onClick: handleToggle,
|
|
2340
2490
|
style: {
|
|
2341
2491
|
display: "flex",
|
|
2342
|
-
height:
|
|
2343
|
-
width:
|
|
2492
|
+
height: BTN_SIZE,
|
|
2493
|
+
width: BTN_SIZE,
|
|
2344
2494
|
alignItems: "center",
|
|
2345
2495
|
justifyContent: "center",
|
|
2346
2496
|
borderRadius: "50%",
|
|
@@ -2349,7 +2499,7 @@ function FeedbackButton({ position = "bottom-right" }) {
|
|
|
2349
2499
|
border: annotate ? "none" : `1px solid ${T.border}`,
|
|
2350
2500
|
background: annotate ? T.primary : T.card,
|
|
2351
2501
|
color: annotate ? T.primaryFg : T.fg,
|
|
2352
|
-
cursor: "pointer",
|
|
2502
|
+
cursor: dragXY !== null ? "grabbing" : "pointer",
|
|
2353
2503
|
transform: annotate ? "scale(1.1)" : "scale(1)"
|
|
2354
2504
|
},
|
|
2355
2505
|
title: annotate ? "Exit feedback mode" : "Open feedback menu",
|