ui-sniper 3.2.1 → 3.2.2
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/README.md +11 -2
- package/dist/index.js +50 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +50 -30
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,12 +39,21 @@ The toolbar appears in the bottom-right corner. Click to activate, then click an
|
|
|
39
39
|
- **Text selection** – Select text to annotate specific content
|
|
40
40
|
- **Multi-select** – Drag to select multiple elements at once
|
|
41
41
|
- **Area selection** – Drag to annotate any region, even empty space
|
|
42
|
-
- **Animation pause** – Freeze all animations (CSS, JS, videos) to capture specific states
|
|
42
|
+
- **Animation pause (P)** – Freeze all animations (CSS, JS, videos) to capture specific states
|
|
43
|
+
- **X-Ray mode (X)** – Reveal invisible structure, bounding boxes, and alignments
|
|
44
|
+
- **Layout mode (L)** – Drag, drop, edit text, or draw directly on the screen
|
|
45
|
+
- **Detailed Help Panel** – In-app guide detailing how, why, and the value of each mode
|
|
43
46
|
- **Structured output** – Copy markdown with selectors, positions, and context
|
|
44
47
|
- **Programmatic access** – Callback prop for direct integration with tools
|
|
45
48
|
- **Dark/light mode** – Toggle in settings, persists to localStorage
|
|
46
49
|
- **Zero dependencies** – Pure CSS animations, no runtime libraries
|
|
47
50
|
|
|
51
|
+
## What's New in v3.2
|
|
52
|
+
- **Rich Help Panel**: A new built-in help panel explaining feature usage and value propositions.
|
|
53
|
+
- **Improved Review Panel**: Hover states, distinct color actions (Delete, Copy, Mark Done), and layout fixes preventing overlap.
|
|
54
|
+
- **Enhanced Visual Feedback**: The toolbar now has a distinct active border, and capturing screenshots accurately respects visibility states.
|
|
55
|
+
- **Advanced X-Ray & Layout Tools**: Reposition elements on the fly or inspect padding/margins before writing feedback.
|
|
56
|
+
|
|
48
57
|
## Props
|
|
49
58
|
|
|
50
59
|
| Prop | Type | Default | Description |
|
|
@@ -135,6 +144,6 @@ Full documentation at [hara-xy.com](https://hara-xy.com)
|
|
|
135
144
|
|
|
136
145
|
## License
|
|
137
146
|
|
|
138
|
-
© 2026
|
|
147
|
+
© 2026 Shiva Patil
|
|
139
148
|
|
|
140
149
|
Licensed under PolyForm Shield 1.0.0
|
package/dist/index.js
CHANGED
|
@@ -3706,7 +3706,7 @@ var styles_module_default4 = classNames5;
|
|
|
3706
3706
|
|
|
3707
3707
|
// src/components/xray-overlay/index.tsx
|
|
3708
3708
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
3709
|
-
function XRayOverlay({ isActive, isDarkMode }) {
|
|
3709
|
+
function XRayOverlay({ isActive, isDarkMode, isFrozen }) {
|
|
3710
3710
|
const [hoveredNode, setHoveredNode] = (0, import_react4.useState)(null);
|
|
3711
3711
|
const [stylesData, setStylesData] = (0, import_react4.useState)({});
|
|
3712
3712
|
const [cursorPos, setCursorPos] = (0, import_react4.useState)({ x: 0, y: 0 });
|
|
@@ -3716,6 +3716,7 @@ function XRayOverlay({ isActive, isDarkMode }) {
|
|
|
3716
3716
|
return;
|
|
3717
3717
|
}
|
|
3718
3718
|
const onMouseMove = (e) => {
|
|
3719
|
+
if (isFrozen) return;
|
|
3719
3720
|
setCursorPos({ x: e.clientX, y: e.clientY });
|
|
3720
3721
|
const target = e.target;
|
|
3721
3722
|
if (target.closest("[data-ui-sniper-toolbar]") || target.closest("[data-ui-sniper-root]") || target.closest("[data-review-panel]")) {
|
|
@@ -3739,7 +3740,7 @@ function XRayOverlay({ isActive, isDarkMode }) {
|
|
|
3739
3740
|
};
|
|
3740
3741
|
window.addEventListener("mousemove", onMouseMove);
|
|
3741
3742
|
return () => window.removeEventListener("mousemove", onMouseMove);
|
|
3742
|
-
}, [isActive]);
|
|
3743
|
+
}, [isActive, isFrozen]);
|
|
3743
3744
|
if (!isActive || !hoveredNode) return null;
|
|
3744
3745
|
const rect = hoveredNode.getBoundingClientRect();
|
|
3745
3746
|
return (0, import_react_dom2.createPortal)(
|
|
@@ -7879,20 +7880,6 @@ async function getDomCapture() {
|
|
|
7879
7880
|
}
|
|
7880
7881
|
}
|
|
7881
7882
|
function findCaptureTarget(captureX, captureY, captureW, captureH) {
|
|
7882
|
-
const cx = captureX + captureW / 2;
|
|
7883
|
-
const cy = captureY + captureH / 2;
|
|
7884
|
-
const elements = document.elementsFromPoint(cx, cy);
|
|
7885
|
-
for (const el of elements) {
|
|
7886
|
-
if (!(el instanceof HTMLElement)) continue;
|
|
7887
|
-
if (el.hasAttribute("data-ui-sniper-root")) continue;
|
|
7888
|
-
if (el.closest?.("[data-ui-sniper-root]")) continue;
|
|
7889
|
-
if (el.tagName === "CANVAS") continue;
|
|
7890
|
-
if (el === document.documentElement || el === document.body) continue;
|
|
7891
|
-
const rect = el.getBoundingClientRect();
|
|
7892
|
-
if (rect.left <= captureX + captureW * 0.1 && rect.top <= captureY + captureH * 0.1 && rect.right >= captureX + captureW * 0.9 && rect.bottom >= captureY + captureH * 0.9) {
|
|
7893
|
-
return el;
|
|
7894
|
-
}
|
|
7895
|
-
}
|
|
7896
7883
|
return document.body;
|
|
7897
7884
|
}
|
|
7898
7885
|
async function captureDomRegion(regionX, regionY, regionW, regionH, strokes, padding = 32, quality = 0.85, piiConfig) {
|
|
@@ -8948,7 +8935,7 @@ function SettingsPanel({
|
|
|
8948
8935
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("a", { className: styles_module_default11.settingsBrand, href: "https://hara-xy.com", target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("svg", { width: "72", height: "16", viewBox: "0 0 500 151", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("text", { x: "10", y: "110", fontFamily: "'Arial Black', 'Impact', sans-serif", fontSize: "105", fontWeight: "900", fontStyle: "italic", letterSpacing: "-2", fill: "currentColor", children: "UI SNIPER" }) }) }),
|
|
8949
8936
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("p", { className: styles_module_default11.settingsVersion, children: [
|
|
8950
8937
|
"v",
|
|
8951
|
-
"3.2.
|
|
8938
|
+
"3.2.2"
|
|
8952
8939
|
] }),
|
|
8953
8940
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
8954
8941
|
"button",
|
|
@@ -10981,7 +10968,7 @@ function PageFeedbackToolbarCSS({
|
|
|
10981
10968
|
});
|
|
10982
10969
|
}, [effectiveReactMode]);
|
|
10983
10970
|
(0, import_react11.useEffect)(() => {
|
|
10984
|
-
if (!isActive || isDrawMode || isDesignMode ||
|
|
10971
|
+
if (!isActive || isDrawMode || isDesignMode || showReviewPanel) return;
|
|
10985
10972
|
const handleClick = (e) => {
|
|
10986
10973
|
if (justFinishedDragRef.current) {
|
|
10987
10974
|
justFinishedDragRef.current = false;
|
|
@@ -11134,7 +11121,7 @@ function PageFeedbackToolbarCSS({
|
|
|
11134
11121
|
};
|
|
11135
11122
|
}, [isActive, pendingMultiSelectElements, createMultiSelectPendingAnnotation]);
|
|
11136
11123
|
(0, import_react11.useEffect)(() => {
|
|
11137
|
-
if (!isActive || pendingAnnotation || isDrawMode || isDesignMode
|
|
11124
|
+
if (!isActive || pendingAnnotation || isDrawMode || isDesignMode) return;
|
|
11138
11125
|
const handleMouseDown = (e) => {
|
|
11139
11126
|
const target = e.composedPath()[0] || e.target;
|
|
11140
11127
|
if (closestCrossingShadow(target, "[data-feedback-toolbar]")) return;
|
|
@@ -11485,16 +11472,30 @@ function PageFeedbackToolbarCSS({
|
|
|
11485
11472
|
}
|
|
11486
11473
|
if (elToCapture) {
|
|
11487
11474
|
try {
|
|
11488
|
-
|
|
11475
|
+
let targetRect = elToCapture.getBoundingClientRect();
|
|
11476
|
+
let currentEl = elToCapture;
|
|
11477
|
+
const MAX_W = Math.min(800, window.innerWidth - 40);
|
|
11478
|
+
const MAX_H = Math.min(600, window.innerHeight - 40);
|
|
11479
|
+
for (let i = 0; i < 4; i++) {
|
|
11480
|
+
if (!currentEl || !currentEl.parentElement) break;
|
|
11481
|
+
if (currentEl.parentElement === document.body) break;
|
|
11482
|
+
const parentRect = currentEl.parentElement.getBoundingClientRect();
|
|
11483
|
+
if (parentRect.width <= MAX_W && parentRect.height <= MAX_H) {
|
|
11484
|
+
targetRect = parentRect;
|
|
11485
|
+
currentEl = currentEl.parentElement;
|
|
11486
|
+
} else {
|
|
11487
|
+
break;
|
|
11488
|
+
}
|
|
11489
|
+
}
|
|
11489
11490
|
const result = await captureDomRegion(
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
|
|
11491
|
+
targetRect.left,
|
|
11492
|
+
targetRect.top,
|
|
11493
|
+
targetRect.width,
|
|
11494
|
+
targetRect.height,
|
|
11494
11495
|
[],
|
|
11495
11496
|
// No strokes for standard annotation
|
|
11496
|
-
|
|
11497
|
-
//
|
|
11497
|
+
48,
|
|
11498
|
+
// 48px extra padding around the logical parent
|
|
11498
11499
|
0.8,
|
|
11499
11500
|
piiConfig.enabled ? piiConfig : void 0
|
|
11500
11501
|
);
|
|
@@ -12202,7 +12203,7 @@ function PageFeedbackToolbarCSS({
|
|
|
12202
12203
|
copyOutput();
|
|
12203
12204
|
}
|
|
12204
12205
|
}
|
|
12205
|
-
if (e.key === "x" || e.key === "X") {
|
|
12206
|
+
if ((e.key === "x" || e.key === "X") && e.shiftKey) {
|
|
12206
12207
|
if (annotations.length > 0 || designPlacements.length > 0 || rearrangeState) {
|
|
12207
12208
|
e.preventDefault();
|
|
12208
12209
|
hideTooltipsUntilMouseLeave();
|
|
@@ -12210,6 +12211,24 @@ function PageFeedbackToolbarCSS({
|
|
|
12210
12211
|
if (designPlacements.length > 0) setDesignPlacements([]);
|
|
12211
12212
|
if (rearrangeState) setRearrangeState(null);
|
|
12212
12213
|
}
|
|
12214
|
+
} else if ((e.key === "x" || e.key === "X") && !e.shiftKey) {
|
|
12215
|
+
e.preventDefault();
|
|
12216
|
+
hideTooltipsUntilMouseLeave();
|
|
12217
|
+
setIsXRayMode(!isXRayMode);
|
|
12218
|
+
}
|
|
12219
|
+
if (e.key === "l" || e.key === "L") {
|
|
12220
|
+
e.preventDefault();
|
|
12221
|
+
hideTooltipsUntilMouseLeave();
|
|
12222
|
+
if (isDesignMode) {
|
|
12223
|
+
closeDesignMode();
|
|
12224
|
+
} else {
|
|
12225
|
+
setIsDesignMode(true);
|
|
12226
|
+
}
|
|
12227
|
+
}
|
|
12228
|
+
if (e.key === "p" || e.key === "P") {
|
|
12229
|
+
e.preventDefault();
|
|
12230
|
+
hideTooltipsUntilMouseLeave();
|
|
12231
|
+
toggleFreeze();
|
|
12213
12232
|
}
|
|
12214
12233
|
if (e.key === "s" || e.key === "S") {
|
|
12215
12234
|
const hasValidWebhook = isValidUrl(settings.webhookUrl) || isValidUrl(webhookUrl || "");
|
|
@@ -12238,7 +12257,8 @@ function PageFeedbackToolbarCSS({
|
|
|
12238
12257
|
toggleFreeze,
|
|
12239
12258
|
copyOutput,
|
|
12240
12259
|
clearAll,
|
|
12241
|
-
pendingMultiSelectElements
|
|
12260
|
+
pendingMultiSelectElements,
|
|
12261
|
+
isXRayMode
|
|
12242
12262
|
]);
|
|
12243
12263
|
if (!mounted) return null;
|
|
12244
12264
|
if (isToolbarHidden) return null;
|
|
@@ -12561,7 +12581,7 @@ function PageFeedbackToolbarCSS({
|
|
|
12561
12581
|
),
|
|
12562
12582
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: styles_module_default6.buttonTooltip, children: [
|
|
12563
12583
|
"Clear your notes",
|
|
12564
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: styles_module_default6.shortcut, children: "X" })
|
|
12584
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: styles_module_default6.shortcut, children: "Shift+X" })
|
|
12565
12585
|
] })
|
|
12566
12586
|
] }),
|
|
12567
12587
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: styles_module_default6.buttonWrapper, children: [
|
|
@@ -12810,7 +12830,7 @@ function PageFeedbackToolbarCSS({
|
|
|
12810
12830
|
}
|
|
12811
12831
|
}
|
|
12812
12832
|
),
|
|
12813
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(XRayOverlay, { isActive: isXRayMode, isDarkMode }),
|
|
12833
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(XRayOverlay, { isActive: isXRayMode, isDarkMode, isFrozen: !!(pendingAnnotation || editingAnnotation) }),
|
|
12814
12834
|
(isDesignMode || designOverlayExiting) && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
12815
12835
|
"div",
|
|
12816
12836
|
{
|