qapture2 0.7.1 → 0.7.3
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 +40 -0
- package/dist/{chunk-JSEFC2VP.js → chunk-BM3ESR2O.js} +191 -69
- package/dist/{chunk-WAOYPLZA.cjs → chunk-C5WJFSMN.cjs} +191 -69
- package/dist/index.cjs +5 -5
- package/dist/index.js +1 -1
- package/dist/next.cjs +4 -4
- package/dist/next.js +1 -1
- package/dist/standalone.cjs +2 -2
- package/dist/standalone.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -315,6 +315,46 @@ rewritten in place, and a render that still throws is retried once with
|
|
|
315
315
|
decoration stripped. `npm run modern-css-test` proves it, and proves itself:
|
|
316
316
|
it first asserts that raw html2canvas *still* dies on the same fixture.
|
|
317
317
|
|
|
318
|
+
**v0.7.2 fixed the last of the mis-framing: clicking an element only captured
|
|
319
|
+
the part of it that was on screen.** Dragged regions are clamped into the
|
|
320
|
+
viewport before capture, so they were always safe — but an element *pick* is a
|
|
321
|
+
raw `getBoundingClientRect()`, and a table column, a sidebar, a long form or a
|
|
322
|
+
wide toolbar routinely extends past the fold. You got a fragment. And when the
|
|
323
|
+
element started *above* the viewport, the crop slid down the page to fill its
|
|
324
|
+
height, so the screenshot was the right size showing the wrong content — a
|
|
325
|
+
failure with nothing on screen to reveal it.
|
|
326
|
+
|
|
327
|
+
Captures now render the union of the viewport and the selection, so an element
|
|
328
|
+
that leaves the screen in any direction is rendered whole, up to 4000px per
|
|
329
|
+
side (past a ~64 MB device-pixel render budget the scale drops, not the
|
|
330
|
+
framing). The exact engine can't photograph off-screen pixels, so there the
|
|
331
|
+
selection is trimmed to the visible part instead — cropped, never displaced.
|
|
332
|
+
`npm run element-capture-test` measures all four cases.
|
|
333
|
+
|
|
334
|
+
**v0.7.3 corrected the half of that which was wrong.** Chasing an element's
|
|
335
|
+
whole box is only right when the whole box is *drawn*. Inside a scroll
|
|
336
|
+
container it isn't: a dashboard scrolls an inner `overflow:auto` box, so a
|
|
337
|
+
3000px column inside a 700px box is 700px of pixels and 2300px of nothing.
|
|
338
|
+
0.7.2 rendered the nothing — measured at 23.3% element, 76.7% empty page, with
|
|
339
|
+
the real content squeezed from 1040px wide to 312px by the long-edge cap.
|
|
340
|
+
|
|
341
|
+
Captures are now clipped to what the browser actually paints: the element's box
|
|
342
|
+
intersected with every ancestor that clips it, on either axis, up to `<html>`.
|
|
343
|
+
The viewport is deliberately not a clipper, so a long form on an ordinary
|
|
344
|
+
scrolling page still captures in full. Same fixture after: 100% element, full
|
|
345
|
+
1040×1400. The hover outline is clipped identically, so what you highlight is
|
|
346
|
+
what you get.
|
|
347
|
+
|
|
348
|
+
### "The screenshot looks simulated"
|
|
349
|
+
|
|
350
|
+
Because by default it is. The DOM engine re-draws your page; fonts shift,
|
|
351
|
+
shadows and gradients flatten, `<canvas>` and video come out blank. From
|
|
352
|
+
v0.7.3 a redrawn capture says so under the preview and offers a one-tap **Use
|
|
353
|
+
real screenshots**, which grants tab-share and re-shoots the same selection
|
|
354
|
+
with the exact engine below. Chromium desktop only — on Safari and Firefox the
|
|
355
|
+
redraw is the only engine there is, so for screenshot-critical QA, test in
|
|
356
|
+
Chrome.
|
|
357
|
+
|
|
318
358
|
### `exact` — opt-in, pixel-for-pixel
|
|
319
359
|
|
|
320
360
|
Uses the Screen Capture API to photograph **this tab's real composited
|
|
@@ -1775,6 +1775,8 @@ var STR = {
|
|
|
1775
1775
|
expected_label: "Expected",
|
|
1776
1776
|
exact_label: "Pixel-exact shots",
|
|
1777
1777
|
exact_hint: "Screenshots become a real photo of the tab instead of a redraw. Asks once to share this tab; nothing leaves your device.",
|
|
1778
|
+
redraw_notice: "This is a redraw of the page, not a real photo.",
|
|
1779
|
+
redraw_action: "Use real screenshots",
|
|
1778
1780
|
exact_on: "Pixel-exact screenshots on",
|
|
1779
1781
|
exact_off: "Back to redrawn screenshots",
|
|
1780
1782
|
exact_declined: "Staying on redrawn screenshots",
|
|
@@ -1969,6 +1971,8 @@ var STR = {
|
|
|
1969
1971
|
expected_label: "\u0627\u0644\u0645\u062A\u0648\u0642\u0639",
|
|
1970
1972
|
exact_label: "\u0644\u0642\u0637\u0627\u062A \u0645\u0637\u0627\u0628\u0642\u0629 \u062A\u0645\u0627\u0645\u064B\u0627",
|
|
1971
1973
|
exact_hint: "\u062A\u0635\u0628\u062D \u0627\u0644\u0644\u0642\u0637\u0629 \u0635\u0648\u0631\u0629 \u062D\u0642\u064A\u0642\u064A\u0629 \u0644\u0644\u062A\u0628\u0648\u064A\u0628 \u0628\u062F\u0644 \u0625\u0639\u0627\u062F\u0629 \u0631\u0633\u0645\u0647. \u064A\u064F\u0637\u0644\u0628 \u0627\u0644\u0625\u0630\u0646 \u0645\u0631\u0629 \u0648\u0627\u062D\u062F\u0629\u060C \u0648\u0644\u0627 \u0634\u064A\u0621 \u064A\u063A\u0627\u062F\u0631 \u062C\u0647\u0627\u0632\u0643.",
|
|
1974
|
+
redraw_notice: "\u0647\u0630\u0647 \u0625\u0639\u0627\u062F\u0629 \u0631\u0633\u0645 \u0644\u0644\u0635\u0641\u062D\u0629\u060C \u0648\u0644\u064A\u0633\u062A \u0635\u0648\u0631\u0629 \u062D\u0642\u064A\u0642\u064A\u0629.",
|
|
1975
|
+
redraw_action: "\u0627\u0633\u062A\u062E\u062F\u0645 \u0644\u0642\u0637\u0627\u062A \u062D\u0642\u064A\u0642\u064A\u0629",
|
|
1972
1976
|
exact_on: "\u062A\u0645 \u062A\u0641\u0639\u064A\u0644 \u0627\u0644\u0644\u0642\u0637\u0627\u062A \u0627\u0644\u0645\u0637\u0627\u0628\u0642\u0629",
|
|
1973
1977
|
exact_off: "\u0627\u0644\u0639\u0648\u062F\u0629 \u0625\u0644\u0649 \u0627\u0644\u0644\u0642\u0637\u0627\u062A \u0627\u0644\u0645\u064F\u0639\u0627\u062F \u0631\u0633\u0645\u0647\u0627",
|
|
1974
1978
|
exact_declined: "\u0633\u0646\u0628\u0642\u0649 \u0639\u0644\u0649 \u0627\u0644\u0644\u0642\u0637\u0627\u062A \u0627\u0644\u0645\u064F\u0639\u0627\u062F \u0631\u0633\u0645\u0647\u0627",
|
|
@@ -2530,6 +2534,8 @@ var FALLBACK_PAGE_BACKGROUND = "#ffffff";
|
|
|
2530
2534
|
var MAX_SHOT_EDGE = 1800;
|
|
2531
2535
|
var WEBP_QUALITY = 0.92;
|
|
2532
2536
|
var STUCK_ATTR = "data-qa-stuck";
|
|
2537
|
+
var MAX_CAPTURE_EDGE = 4e3;
|
|
2538
|
+
var MAX_RENDER_DEVICE_PIXELS = 16e6;
|
|
2533
2539
|
function withTimeout(promise, ms) {
|
|
2534
2540
|
let timer;
|
|
2535
2541
|
return Promise.race([
|
|
@@ -2695,20 +2701,94 @@ function cropCanvas(full, rect, scale) {
|
|
|
2695
2701
|
ctx.drawImage(full, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
2696
2702
|
return out;
|
|
2697
2703
|
}
|
|
2704
|
+
function viewportSize() {
|
|
2705
|
+
return {
|
|
2706
|
+
vw: document.documentElement.clientWidth || window.innerWidth,
|
|
2707
|
+
vh: document.documentElement.clientHeight || window.innerHeight
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
function clipToPaintedArea(el, rect) {
|
|
2711
|
+
let left = rect.left;
|
|
2712
|
+
let top = rect.top;
|
|
2713
|
+
let right = rect.left + rect.width;
|
|
2714
|
+
let bottom = rect.top + rect.height;
|
|
2715
|
+
let node = el.parentElement;
|
|
2716
|
+
while (node) {
|
|
2717
|
+
let style;
|
|
2718
|
+
try {
|
|
2719
|
+
style = getComputedStyle(node);
|
|
2720
|
+
} catch {
|
|
2721
|
+
break;
|
|
2722
|
+
}
|
|
2723
|
+
const clipsX = style.overflowX !== "visible";
|
|
2724
|
+
const clipsY = style.overflowY !== "visible";
|
|
2725
|
+
if (clipsX || clipsY) {
|
|
2726
|
+
const b = node.getBoundingClientRect();
|
|
2727
|
+
const padLeft = b.left + node.clientLeft;
|
|
2728
|
+
const padTop = b.top + node.clientTop;
|
|
2729
|
+
if (clipsX) {
|
|
2730
|
+
left = Math.max(left, padLeft);
|
|
2731
|
+
right = Math.min(right, padLeft + node.clientWidth);
|
|
2732
|
+
}
|
|
2733
|
+
if (clipsY) {
|
|
2734
|
+
top = Math.max(top, padTop);
|
|
2735
|
+
bottom = Math.min(bottom, padTop + node.clientHeight);
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
node = node.parentElement;
|
|
2739
|
+
}
|
|
2740
|
+
if (right - left < 2 || bottom - top < 2) return rect;
|
|
2741
|
+
return { left, top, width: right - left, height: bottom - top };
|
|
2742
|
+
}
|
|
2743
|
+
function intersectViewport(rect) {
|
|
2744
|
+
const { vw, vh } = viewportSize();
|
|
2745
|
+
const left = Math.max(0, rect.left);
|
|
2746
|
+
const top = Math.max(0, rect.top);
|
|
2747
|
+
const right = Math.min(vw, rect.left + rect.width);
|
|
2748
|
+
const bottom = Math.min(vh, rect.top + rect.height);
|
|
2749
|
+
if (right - left < 2 || bottom - top < 2) return null;
|
|
2750
|
+
return { left, top, width: right - left, height: bottom - top };
|
|
2751
|
+
}
|
|
2752
|
+
function planRender(rect, sx, sy, vw, vh) {
|
|
2753
|
+
let left = rect.left + sx;
|
|
2754
|
+
let top = rect.top + sy;
|
|
2755
|
+
let width = rect.width;
|
|
2756
|
+
let height = rect.height;
|
|
2757
|
+
if (width > MAX_CAPTURE_EDGE) {
|
|
2758
|
+
left = Math.max(left, Math.min(sx, left + width - MAX_CAPTURE_EDGE));
|
|
2759
|
+
width = MAX_CAPTURE_EDGE;
|
|
2760
|
+
}
|
|
2761
|
+
if (height > MAX_CAPTURE_EDGE) {
|
|
2762
|
+
top = Math.max(top, Math.min(sy, top + height - MAX_CAPTURE_EDGE));
|
|
2763
|
+
height = MAX_CAPTURE_EDGE;
|
|
2764
|
+
}
|
|
2765
|
+
const x = Math.floor(Math.min(sx, left));
|
|
2766
|
+
const y = Math.floor(Math.min(sy, top));
|
|
2767
|
+
const right = Math.ceil(Math.max(sx + vw, left + width));
|
|
2768
|
+
const bottom = Math.ceil(Math.max(sy + vh, top + height));
|
|
2769
|
+
return {
|
|
2770
|
+
render: { x, y, width: right - x, height: bottom - y },
|
|
2771
|
+
crop: { left: left - x, top: top - y, width, height }
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2698
2774
|
async function captureViaDom(rect, sx, sy, aggressiveColors = false) {
|
|
2699
2775
|
const { default: html2canvas } = await import('html2canvas');
|
|
2700
|
-
const
|
|
2701
|
-
const
|
|
2702
|
-
|
|
2776
|
+
const { vw, vh } = viewportSize();
|
|
2777
|
+
const plan = planRender(rect, sx, sy, vw, vh);
|
|
2778
|
+
let scale = Math.min(window.devicePixelRatio || 1, 2);
|
|
2779
|
+
const area = plan.render.width * plan.render.height;
|
|
2780
|
+
if (area * scale * scale > MAX_RENDER_DEVICE_PIXELS) {
|
|
2781
|
+
scale = Math.sqrt(MAX_RENDER_DEVICE_PIXELS / area);
|
|
2782
|
+
}
|
|
2703
2783
|
const marked = markStuckElements();
|
|
2704
2784
|
try {
|
|
2705
2785
|
const full = await withTimeout(
|
|
2706
2786
|
html2canvas(document.body, {
|
|
2707
|
-
//
|
|
2708
|
-
x:
|
|
2709
|
-
y:
|
|
2710
|
-
width:
|
|
2711
|
-
height:
|
|
2787
|
+
// The box to render, in document coordinates.
|
|
2788
|
+
x: plan.render.x,
|
|
2789
|
+
y: plan.render.y,
|
|
2790
|
+
width: plan.render.width,
|
|
2791
|
+
height: plan.render.height,
|
|
2712
2792
|
scale,
|
|
2713
2793
|
useCORS: true,
|
|
2714
2794
|
allowTaint: true,
|
|
@@ -2717,6 +2797,10 @@ async function captureViaDom(rect, sx, sy, aggressiveColors = false) {
|
|
|
2717
2797
|
// CSS Color 4 treatment the document walk applies.
|
|
2718
2798
|
backgroundColor: safeBackgroundColor(resolvePageBackground(), FALLBACK_PAGE_BACKGROUND),
|
|
2719
2799
|
logging: false,
|
|
2800
|
+
// The clone's own scroll + viewport, which is what `position: fixed`
|
|
2801
|
+
// resolves against. It stays the LIVE viewport even when the render
|
|
2802
|
+
// box is bigger, so fixed chrome is drawn where the tester sees it
|
|
2803
|
+
// rather than smeared over the whole render.
|
|
2720
2804
|
scrollX: sx,
|
|
2721
2805
|
scrollY: sy,
|
|
2722
2806
|
windowWidth: vw,
|
|
@@ -2730,7 +2814,7 @@ async function captureViaDom(rect, sx, sy, aggressiveColors = false) {
|
|
|
2730
2814
|
HTML2CANVAS_TIMEOUT_MS
|
|
2731
2815
|
);
|
|
2732
2816
|
if (!full) return null;
|
|
2733
|
-
return cropCanvas(full,
|
|
2817
|
+
return cropCanvas(full, plan.crop, scale);
|
|
2734
2818
|
} finally {
|
|
2735
2819
|
unmarkStuckElements(marked);
|
|
2736
2820
|
}
|
|
@@ -2743,8 +2827,10 @@ async function captureRegion(rect, scroll, prefer = "auto") {
|
|
|
2743
2827
|
const sx = scroll?.x ?? window.scrollX;
|
|
2744
2828
|
const sy = scroll?.y ?? window.scrollY;
|
|
2745
2829
|
if (prefer !== "dom" && getExactCaptureStatus() === "live") {
|
|
2830
|
+
const visible = intersectViewport(rect);
|
|
2831
|
+
if (!visible) return { status: "empty" };
|
|
2746
2832
|
try {
|
|
2747
|
-
const canvas = await withOverlayHidden(() => grabExactRegion(
|
|
2833
|
+
const canvas = await withOverlayHidden(() => grabExactRegion(visible));
|
|
2748
2834
|
if (canvas) {
|
|
2749
2835
|
const blob = await encodeShot(canvas);
|
|
2750
2836
|
if (blob) return { status: "ok", blob, engine: "exact" };
|
|
@@ -8825,6 +8911,7 @@ function CaptureMode() {
|
|
|
8825
8911
|
const [candidate, setCandidate] = useState(null);
|
|
8826
8912
|
const [regionMode, setRegionMode] = useState(false);
|
|
8827
8913
|
const [shot, setShot] = useState(null);
|
|
8914
|
+
const [shotEngine, setShotEngine] = useState(null);
|
|
8828
8915
|
const [shotUrl, setShotUrl] = useState(null);
|
|
8829
8916
|
const [capturing, setCapturing] = useState(false);
|
|
8830
8917
|
const [captureError, setCaptureError] = useState(false);
|
|
@@ -8867,6 +8954,7 @@ function CaptureMode() {
|
|
|
8867
8954
|
return;
|
|
8868
8955
|
}
|
|
8869
8956
|
setCaptureError(outcome.status === "failed");
|
|
8957
|
+
setShotEngine(outcome.status === "ok" ? outcome.engine : null);
|
|
8870
8958
|
setShot(blob);
|
|
8871
8959
|
setShotUrl((old) => {
|
|
8872
8960
|
if (old) URL.revokeObjectURL(old);
|
|
@@ -8931,7 +9019,9 @@ function CaptureMode() {
|
|
|
8931
9019
|
}
|
|
8932
9020
|
const r = el.getBoundingClientRect();
|
|
8933
9021
|
setHover({
|
|
8934
|
-
|
|
9022
|
+
// Highlight only what will actually be captured, so the outline is a
|
|
9023
|
+
// promise the screenshot can keep.
|
|
9024
|
+
rect: clipToPaintedArea(el, { top: r.top, left: r.left, width: r.width, height: r.height }),
|
|
8935
9025
|
selector: getStableSelector(el)
|
|
8936
9026
|
});
|
|
8937
9027
|
}
|
|
@@ -8978,7 +9068,10 @@ function CaptureMode() {
|
|
|
8978
9068
|
const r = el.getBoundingClientRect();
|
|
8979
9069
|
const sel = {
|
|
8980
9070
|
kind: "element",
|
|
8981
|
-
|
|
9071
|
+
// What the browser PAINTS of this element, not its full bounding box —
|
|
9072
|
+
// a row inside a scrolled table is only ever drawn where the table shows
|
|
9073
|
+
// it. See clipToPaintedArea().
|
|
9074
|
+
rect: clipToPaintedArea(el, { top: r.top, left: r.left, width: r.width, height: r.height }),
|
|
8982
9075
|
selector: getStableSelector(el),
|
|
8983
9076
|
text: (el.innerText ?? el.textContent ?? "").trim().slice(0, 120),
|
|
8984
9077
|
tagName: el.tagName.toLowerCase()
|
|
@@ -9589,7 +9682,7 @@ function CaptureMode() {
|
|
|
9589
9682
|
)
|
|
9590
9683
|
] }),
|
|
9591
9684
|
/* @__PURE__ */ jsxs("div", { className: "qa-space-y-2 qa-p-3", children: [
|
|
9592
|
-
/* @__PURE__ */
|
|
9685
|
+
/* @__PURE__ */ jsxs(
|
|
9593
9686
|
"div",
|
|
9594
9687
|
{
|
|
9595
9688
|
className: "qa-flex qa-min-h-16 qa-items-center qa-justify-center qa-rounded-lg qa-border",
|
|
@@ -9597,66 +9690,95 @@ function CaptureMode() {
|
|
|
9597
9690
|
borderColor: "var(--qa-border-subtle)",
|
|
9598
9691
|
background: "var(--qa-surface-0)"
|
|
9599
9692
|
},
|
|
9600
|
-
children:
|
|
9601
|
-
/* @__PURE__ */
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
9693
|
+
children: [
|
|
9694
|
+
capturing ? /* @__PURE__ */ jsxs("span", { className: "qa-flex qa-items-center qa-gap-2 qa-py-4 qa-text-xs qa-text-accent", children: [
|
|
9695
|
+
/* @__PURE__ */ jsx(Icon, { name: "Loader2", size: 16, className: "qa-animate-spin" }),
|
|
9696
|
+
t("capturing")
|
|
9697
|
+
] }) : shotUrl ? /* @__PURE__ */ jsxs(
|
|
9698
|
+
"button",
|
|
9699
|
+
{
|
|
9700
|
+
type: "button",
|
|
9701
|
+
onClick: () => setDrawing(true),
|
|
9702
|
+
title: t("draw_label"),
|
|
9703
|
+
"aria-label": t("draw_label"),
|
|
9704
|
+
className: "qa-relative qa-group qa-tap",
|
|
9705
|
+
style: { background: "transparent", border: "none", padding: 0, cursor: "pointer" },
|
|
9706
|
+
children: [
|
|
9707
|
+
/* @__PURE__ */ jsx(
|
|
9708
|
+
"img",
|
|
9709
|
+
{
|
|
9710
|
+
src: shotUrl,
|
|
9711
|
+
alt: "capture",
|
|
9712
|
+
className: "qa-max-h-32 qa-rounded-md"
|
|
9713
|
+
}
|
|
9714
|
+
),
|
|
9715
|
+
/* @__PURE__ */ jsxs(
|
|
9716
|
+
"span",
|
|
9717
|
+
{
|
|
9718
|
+
className: "qa-absolute qa-inline-flex qa-items-center qa-gap-1 qa-rounded-full qa-px-1.5 qa-py-0.5 qa-text-10",
|
|
9719
|
+
style: {
|
|
9720
|
+
bottom: 4,
|
|
9721
|
+
insetInlineEnd: 4,
|
|
9722
|
+
background: "var(--qa-surface-1)",
|
|
9723
|
+
color: "var(--qa-ink-hi)",
|
|
9724
|
+
border: "1px solid var(--qa-border-subtle)"
|
|
9725
|
+
},
|
|
9726
|
+
children: [
|
|
9727
|
+
/* @__PURE__ */ jsx(Icon, { name: "Pencil", size: 10 }),
|
|
9728
|
+
t("draw_label")
|
|
9729
|
+
]
|
|
9730
|
+
}
|
|
9731
|
+
)
|
|
9732
|
+
]
|
|
9733
|
+
}
|
|
9734
|
+
) : captureError ? (
|
|
9735
|
+
// The render broke rather than being skipped — offer a retry
|
|
9736
|
+
// against the same selection instead of a dead-end message.
|
|
9737
|
+
/* @__PURE__ */ jsxs("span", { className: "qa-flex qa-flex-col qa-items-center qa-gap-2 qa-py-3", children: [
|
|
9738
|
+
/* @__PURE__ */ jsx("span", { className: "qa-text-xs qa-text-red-600", children: t("capture_failed") }),
|
|
9621
9739
|
/* @__PURE__ */ jsxs(
|
|
9622
|
-
"
|
|
9740
|
+
"button",
|
|
9623
9741
|
{
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
background: "var(--qa-surface-1)",
|
|
9629
|
-
color: "var(--qa-ink-hi)",
|
|
9630
|
-
border: "1px solid var(--qa-border-subtle)"
|
|
9631
|
-
},
|
|
9742
|
+
type: "button",
|
|
9743
|
+
onClick: () => selection && void runCapture(selection.rect),
|
|
9744
|
+
className: "qa-tap qa-inline-flex qa-items-center qa-gap-1.5 qa-rounded-md qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-xs qa-text-mid qa-focus-ring",
|
|
9745
|
+
style: { background: "transparent", cursor: "pointer" },
|
|
9632
9746
|
children: [
|
|
9633
|
-
/* @__PURE__ */ jsx(Icon, { name: "
|
|
9634
|
-
t("
|
|
9747
|
+
/* @__PURE__ */ jsx(Icon, { name: "RotateCcw", size: 13 }),
|
|
9748
|
+
t("retry")
|
|
9635
9749
|
]
|
|
9636
9750
|
}
|
|
9637
9751
|
)
|
|
9638
|
-
]
|
|
9639
|
-
}
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9752
|
+
] })
|
|
9753
|
+
) : /* @__PURE__ */ jsx("span", { className: "qa-py-4 qa-text-xs qa-text-slate-400", children: t("no_shot") }),
|
|
9754
|
+
shotUrl && shotEngine === "dom" && exactShots.supported && exactShots.status !== "live" && /* @__PURE__ */ jsxs(
|
|
9755
|
+
"span",
|
|
9756
|
+
{
|
|
9757
|
+
"data-qa-redraw-offer": "true",
|
|
9758
|
+
className: "qa-mt-1.5 qa-flex qa-items-center qa-justify-center qa-flex-wrap qa-gap-1.5 qa-text-10 qa-text-mid",
|
|
9759
|
+
children: [
|
|
9760
|
+
t("redraw_notice"),
|
|
9761
|
+
/* @__PURE__ */ jsxs(
|
|
9762
|
+
"button",
|
|
9763
|
+
{
|
|
9764
|
+
type: "button",
|
|
9765
|
+
onClick: () => void (async () => {
|
|
9766
|
+
const granted = await enableExactShots();
|
|
9767
|
+
if (granted && selection) void runCapture(selection.rect);
|
|
9768
|
+
})(),
|
|
9769
|
+
title: t("exact_hint"),
|
|
9770
|
+
className: "qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-rounded-full qa-border qa-border-subtle qa-px-1.5 qa-py-0.5 qa-text-10 qa-text-hi qa-focus-ring",
|
|
9771
|
+
style: { background: "transparent", cursor: "pointer" },
|
|
9772
|
+
children: [
|
|
9773
|
+
/* @__PURE__ */ jsx(Icon, { name: "Camera", size: 10 }),
|
|
9774
|
+
t("redraw_action")
|
|
9775
|
+
]
|
|
9776
|
+
}
|
|
9777
|
+
)
|
|
9778
|
+
]
|
|
9779
|
+
}
|
|
9780
|
+
)
|
|
9781
|
+
]
|
|
9660
9782
|
}
|
|
9661
9783
|
),
|
|
9662
9784
|
/* @__PURE__ */ jsx(LocationReveal, { target: selection }),
|
|
@@ -9948,5 +10070,5 @@ function Qapture({ config }) {
|
|
|
9948
10070
|
}
|
|
9949
10071
|
|
|
9950
10072
|
export { Qapture, deleteQaDatabase, initQaStudio };
|
|
9951
|
-
//# sourceMappingURL=chunk-
|
|
9952
|
-
//# sourceMappingURL=chunk-
|
|
10073
|
+
//# sourceMappingURL=chunk-BM3ESR2O.js.map
|
|
10074
|
+
//# sourceMappingURL=chunk-BM3ESR2O.js.map
|
|
@@ -1782,6 +1782,8 @@ var STR = {
|
|
|
1782
1782
|
expected_label: "Expected",
|
|
1783
1783
|
exact_label: "Pixel-exact shots",
|
|
1784
1784
|
exact_hint: "Screenshots become a real photo of the tab instead of a redraw. Asks once to share this tab; nothing leaves your device.",
|
|
1785
|
+
redraw_notice: "This is a redraw of the page, not a real photo.",
|
|
1786
|
+
redraw_action: "Use real screenshots",
|
|
1785
1787
|
exact_on: "Pixel-exact screenshots on",
|
|
1786
1788
|
exact_off: "Back to redrawn screenshots",
|
|
1787
1789
|
exact_declined: "Staying on redrawn screenshots",
|
|
@@ -1976,6 +1978,8 @@ var STR = {
|
|
|
1976
1978
|
expected_label: "\u0627\u0644\u0645\u062A\u0648\u0642\u0639",
|
|
1977
1979
|
exact_label: "\u0644\u0642\u0637\u0627\u062A \u0645\u0637\u0627\u0628\u0642\u0629 \u062A\u0645\u0627\u0645\u064B\u0627",
|
|
1978
1980
|
exact_hint: "\u062A\u0635\u0628\u062D \u0627\u0644\u0644\u0642\u0637\u0629 \u0635\u0648\u0631\u0629 \u062D\u0642\u064A\u0642\u064A\u0629 \u0644\u0644\u062A\u0628\u0648\u064A\u0628 \u0628\u062F\u0644 \u0625\u0639\u0627\u062F\u0629 \u0631\u0633\u0645\u0647. \u064A\u064F\u0637\u0644\u0628 \u0627\u0644\u0625\u0630\u0646 \u0645\u0631\u0629 \u0648\u0627\u062D\u062F\u0629\u060C \u0648\u0644\u0627 \u0634\u064A\u0621 \u064A\u063A\u0627\u062F\u0631 \u062C\u0647\u0627\u0632\u0643.",
|
|
1981
|
+
redraw_notice: "\u0647\u0630\u0647 \u0625\u0639\u0627\u062F\u0629 \u0631\u0633\u0645 \u0644\u0644\u0635\u0641\u062D\u0629\u060C \u0648\u0644\u064A\u0633\u062A \u0635\u0648\u0631\u0629 \u062D\u0642\u064A\u0642\u064A\u0629.",
|
|
1982
|
+
redraw_action: "\u0627\u0633\u062A\u062E\u062F\u0645 \u0644\u0642\u0637\u0627\u062A \u062D\u0642\u064A\u0642\u064A\u0629",
|
|
1979
1983
|
exact_on: "\u062A\u0645 \u062A\u0641\u0639\u064A\u0644 \u0627\u0644\u0644\u0642\u0637\u0627\u062A \u0627\u0644\u0645\u0637\u0627\u0628\u0642\u0629",
|
|
1980
1984
|
exact_off: "\u0627\u0644\u0639\u0648\u062F\u0629 \u0625\u0644\u0649 \u0627\u0644\u0644\u0642\u0637\u0627\u062A \u0627\u0644\u0645\u064F\u0639\u0627\u062F \u0631\u0633\u0645\u0647\u0627",
|
|
1981
1985
|
exact_declined: "\u0633\u0646\u0628\u0642\u0649 \u0639\u0644\u0649 \u0627\u0644\u0644\u0642\u0637\u0627\u062A \u0627\u0644\u0645\u064F\u0639\u0627\u062F \u0631\u0633\u0645\u0647\u0627",
|
|
@@ -2537,6 +2541,8 @@ var FALLBACK_PAGE_BACKGROUND = "#ffffff";
|
|
|
2537
2541
|
var MAX_SHOT_EDGE = 1800;
|
|
2538
2542
|
var WEBP_QUALITY = 0.92;
|
|
2539
2543
|
var STUCK_ATTR = "data-qa-stuck";
|
|
2544
|
+
var MAX_CAPTURE_EDGE = 4e3;
|
|
2545
|
+
var MAX_RENDER_DEVICE_PIXELS = 16e6;
|
|
2540
2546
|
function withTimeout(promise, ms) {
|
|
2541
2547
|
let timer;
|
|
2542
2548
|
return Promise.race([
|
|
@@ -2702,20 +2708,94 @@ function cropCanvas(full, rect, scale) {
|
|
|
2702
2708
|
ctx.drawImage(full, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
2703
2709
|
return out;
|
|
2704
2710
|
}
|
|
2711
|
+
function viewportSize() {
|
|
2712
|
+
return {
|
|
2713
|
+
vw: document.documentElement.clientWidth || window.innerWidth,
|
|
2714
|
+
vh: document.documentElement.clientHeight || window.innerHeight
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2717
|
+
function clipToPaintedArea(el, rect) {
|
|
2718
|
+
let left = rect.left;
|
|
2719
|
+
let top = rect.top;
|
|
2720
|
+
let right = rect.left + rect.width;
|
|
2721
|
+
let bottom = rect.top + rect.height;
|
|
2722
|
+
let node = el.parentElement;
|
|
2723
|
+
while (node) {
|
|
2724
|
+
let style;
|
|
2725
|
+
try {
|
|
2726
|
+
style = getComputedStyle(node);
|
|
2727
|
+
} catch {
|
|
2728
|
+
break;
|
|
2729
|
+
}
|
|
2730
|
+
const clipsX = style.overflowX !== "visible";
|
|
2731
|
+
const clipsY = style.overflowY !== "visible";
|
|
2732
|
+
if (clipsX || clipsY) {
|
|
2733
|
+
const b = node.getBoundingClientRect();
|
|
2734
|
+
const padLeft = b.left + node.clientLeft;
|
|
2735
|
+
const padTop = b.top + node.clientTop;
|
|
2736
|
+
if (clipsX) {
|
|
2737
|
+
left = Math.max(left, padLeft);
|
|
2738
|
+
right = Math.min(right, padLeft + node.clientWidth);
|
|
2739
|
+
}
|
|
2740
|
+
if (clipsY) {
|
|
2741
|
+
top = Math.max(top, padTop);
|
|
2742
|
+
bottom = Math.min(bottom, padTop + node.clientHeight);
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
node = node.parentElement;
|
|
2746
|
+
}
|
|
2747
|
+
if (right - left < 2 || bottom - top < 2) return rect;
|
|
2748
|
+
return { left, top, width: right - left, height: bottom - top };
|
|
2749
|
+
}
|
|
2750
|
+
function intersectViewport(rect) {
|
|
2751
|
+
const { vw, vh } = viewportSize();
|
|
2752
|
+
const left = Math.max(0, rect.left);
|
|
2753
|
+
const top = Math.max(0, rect.top);
|
|
2754
|
+
const right = Math.min(vw, rect.left + rect.width);
|
|
2755
|
+
const bottom = Math.min(vh, rect.top + rect.height);
|
|
2756
|
+
if (right - left < 2 || bottom - top < 2) return null;
|
|
2757
|
+
return { left, top, width: right - left, height: bottom - top };
|
|
2758
|
+
}
|
|
2759
|
+
function planRender(rect, sx, sy, vw, vh) {
|
|
2760
|
+
let left = rect.left + sx;
|
|
2761
|
+
let top = rect.top + sy;
|
|
2762
|
+
let width = rect.width;
|
|
2763
|
+
let height = rect.height;
|
|
2764
|
+
if (width > MAX_CAPTURE_EDGE) {
|
|
2765
|
+
left = Math.max(left, Math.min(sx, left + width - MAX_CAPTURE_EDGE));
|
|
2766
|
+
width = MAX_CAPTURE_EDGE;
|
|
2767
|
+
}
|
|
2768
|
+
if (height > MAX_CAPTURE_EDGE) {
|
|
2769
|
+
top = Math.max(top, Math.min(sy, top + height - MAX_CAPTURE_EDGE));
|
|
2770
|
+
height = MAX_CAPTURE_EDGE;
|
|
2771
|
+
}
|
|
2772
|
+
const x = Math.floor(Math.min(sx, left));
|
|
2773
|
+
const y = Math.floor(Math.min(sy, top));
|
|
2774
|
+
const right = Math.ceil(Math.max(sx + vw, left + width));
|
|
2775
|
+
const bottom = Math.ceil(Math.max(sy + vh, top + height));
|
|
2776
|
+
return {
|
|
2777
|
+
render: { x, y, width: right - x, height: bottom - y },
|
|
2778
|
+
crop: { left: left - x, top: top - y, width, height }
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2705
2781
|
async function captureViaDom(rect, sx, sy, aggressiveColors = false) {
|
|
2706
2782
|
const { default: html2canvas } = await import('html2canvas');
|
|
2707
|
-
const
|
|
2708
|
-
const
|
|
2709
|
-
|
|
2783
|
+
const { vw, vh } = viewportSize();
|
|
2784
|
+
const plan = planRender(rect, sx, sy, vw, vh);
|
|
2785
|
+
let scale = Math.min(window.devicePixelRatio || 1, 2);
|
|
2786
|
+
const area = plan.render.width * plan.render.height;
|
|
2787
|
+
if (area * scale * scale > MAX_RENDER_DEVICE_PIXELS) {
|
|
2788
|
+
scale = Math.sqrt(MAX_RENDER_DEVICE_PIXELS / area);
|
|
2789
|
+
}
|
|
2710
2790
|
const marked = markStuckElements();
|
|
2711
2791
|
try {
|
|
2712
2792
|
const full = await withTimeout(
|
|
2713
2793
|
html2canvas(document.body, {
|
|
2714
|
-
//
|
|
2715
|
-
x:
|
|
2716
|
-
y:
|
|
2717
|
-
width:
|
|
2718
|
-
height:
|
|
2794
|
+
// The box to render, in document coordinates.
|
|
2795
|
+
x: plan.render.x,
|
|
2796
|
+
y: plan.render.y,
|
|
2797
|
+
width: plan.render.width,
|
|
2798
|
+
height: plan.render.height,
|
|
2719
2799
|
scale,
|
|
2720
2800
|
useCORS: true,
|
|
2721
2801
|
allowTaint: true,
|
|
@@ -2724,6 +2804,10 @@ async function captureViaDom(rect, sx, sy, aggressiveColors = false) {
|
|
|
2724
2804
|
// CSS Color 4 treatment the document walk applies.
|
|
2725
2805
|
backgroundColor: safeBackgroundColor(resolvePageBackground(), FALLBACK_PAGE_BACKGROUND),
|
|
2726
2806
|
logging: false,
|
|
2807
|
+
// The clone's own scroll + viewport, which is what `position: fixed`
|
|
2808
|
+
// resolves against. It stays the LIVE viewport even when the render
|
|
2809
|
+
// box is bigger, so fixed chrome is drawn where the tester sees it
|
|
2810
|
+
// rather than smeared over the whole render.
|
|
2727
2811
|
scrollX: sx,
|
|
2728
2812
|
scrollY: sy,
|
|
2729
2813
|
windowWidth: vw,
|
|
@@ -2737,7 +2821,7 @@ async function captureViaDom(rect, sx, sy, aggressiveColors = false) {
|
|
|
2737
2821
|
HTML2CANVAS_TIMEOUT_MS
|
|
2738
2822
|
);
|
|
2739
2823
|
if (!full) return null;
|
|
2740
|
-
return cropCanvas(full,
|
|
2824
|
+
return cropCanvas(full, plan.crop, scale);
|
|
2741
2825
|
} finally {
|
|
2742
2826
|
unmarkStuckElements(marked);
|
|
2743
2827
|
}
|
|
@@ -2750,8 +2834,10 @@ async function captureRegion(rect, scroll, prefer = "auto") {
|
|
|
2750
2834
|
const sx = scroll?.x ?? window.scrollX;
|
|
2751
2835
|
const sy = scroll?.y ?? window.scrollY;
|
|
2752
2836
|
if (prefer !== "dom" && getExactCaptureStatus() === "live") {
|
|
2837
|
+
const visible = intersectViewport(rect);
|
|
2838
|
+
if (!visible) return { status: "empty" };
|
|
2753
2839
|
try {
|
|
2754
|
-
const canvas = await withOverlayHidden(() => grabExactRegion(
|
|
2840
|
+
const canvas = await withOverlayHidden(() => grabExactRegion(visible));
|
|
2755
2841
|
if (canvas) {
|
|
2756
2842
|
const blob = await encodeShot(canvas);
|
|
2757
2843
|
if (blob) return { status: "ok", blob, engine: "exact" };
|
|
@@ -8832,6 +8918,7 @@ function CaptureMode() {
|
|
|
8832
8918
|
const [candidate, setCandidate] = React.useState(null);
|
|
8833
8919
|
const [regionMode, setRegionMode] = React.useState(false);
|
|
8834
8920
|
const [shot, setShot] = React.useState(null);
|
|
8921
|
+
const [shotEngine, setShotEngine] = React.useState(null);
|
|
8835
8922
|
const [shotUrl, setShotUrl] = React.useState(null);
|
|
8836
8923
|
const [capturing, setCapturing] = React.useState(false);
|
|
8837
8924
|
const [captureError, setCaptureError] = React.useState(false);
|
|
@@ -8874,6 +8961,7 @@ function CaptureMode() {
|
|
|
8874
8961
|
return;
|
|
8875
8962
|
}
|
|
8876
8963
|
setCaptureError(outcome.status === "failed");
|
|
8964
|
+
setShotEngine(outcome.status === "ok" ? outcome.engine : null);
|
|
8877
8965
|
setShot(blob);
|
|
8878
8966
|
setShotUrl((old) => {
|
|
8879
8967
|
if (old) URL.revokeObjectURL(old);
|
|
@@ -8938,7 +9026,9 @@ function CaptureMode() {
|
|
|
8938
9026
|
}
|
|
8939
9027
|
const r = el.getBoundingClientRect();
|
|
8940
9028
|
setHover({
|
|
8941
|
-
|
|
9029
|
+
// Highlight only what will actually be captured, so the outline is a
|
|
9030
|
+
// promise the screenshot can keep.
|
|
9031
|
+
rect: clipToPaintedArea(el, { top: r.top, left: r.left, width: r.width, height: r.height }),
|
|
8942
9032
|
selector: getStableSelector(el)
|
|
8943
9033
|
});
|
|
8944
9034
|
}
|
|
@@ -8985,7 +9075,10 @@ function CaptureMode() {
|
|
|
8985
9075
|
const r = el.getBoundingClientRect();
|
|
8986
9076
|
const sel = {
|
|
8987
9077
|
kind: "element",
|
|
8988
|
-
|
|
9078
|
+
// What the browser PAINTS of this element, not its full bounding box —
|
|
9079
|
+
// a row inside a scrolled table is only ever drawn where the table shows
|
|
9080
|
+
// it. See clipToPaintedArea().
|
|
9081
|
+
rect: clipToPaintedArea(el, { top: r.top, left: r.left, width: r.width, height: r.height }),
|
|
8989
9082
|
selector: getStableSelector(el),
|
|
8990
9083
|
text: (el.innerText ?? el.textContent ?? "").trim().slice(0, 120),
|
|
8991
9084
|
tagName: el.tagName.toLowerCase()
|
|
@@ -9596,7 +9689,7 @@ function CaptureMode() {
|
|
|
9596
9689
|
)
|
|
9597
9690
|
] }),
|
|
9598
9691
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-space-y-2 qa-p-3", children: [
|
|
9599
|
-
/* @__PURE__ */ jsxRuntime.
|
|
9692
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
9600
9693
|
"div",
|
|
9601
9694
|
{
|
|
9602
9695
|
className: "qa-flex qa-min-h-16 qa-items-center qa-justify-center qa-rounded-lg qa-border",
|
|
@@ -9604,66 +9697,95 @@ function CaptureMode() {
|
|
|
9604
9697
|
borderColor: "var(--qa-border-subtle)",
|
|
9605
9698
|
background: "var(--qa-surface-0)"
|
|
9606
9699
|
},
|
|
9607
|
-
children:
|
|
9608
|
-
/* @__PURE__ */ jsxRuntime.
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9700
|
+
children: [
|
|
9701
|
+
capturing ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-flex qa-items-center qa-gap-2 qa-py-4 qa-text-xs qa-text-accent", children: [
|
|
9702
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Loader2", size: 16, className: "qa-animate-spin" }),
|
|
9703
|
+
t("capturing")
|
|
9704
|
+
] }) : shotUrl ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9705
|
+
"button",
|
|
9706
|
+
{
|
|
9707
|
+
type: "button",
|
|
9708
|
+
onClick: () => setDrawing(true),
|
|
9709
|
+
title: t("draw_label"),
|
|
9710
|
+
"aria-label": t("draw_label"),
|
|
9711
|
+
className: "qa-relative qa-group qa-tap",
|
|
9712
|
+
style: { background: "transparent", border: "none", padding: 0, cursor: "pointer" },
|
|
9713
|
+
children: [
|
|
9714
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9715
|
+
"img",
|
|
9716
|
+
{
|
|
9717
|
+
src: shotUrl,
|
|
9718
|
+
alt: "capture",
|
|
9719
|
+
className: "qa-max-h-32 qa-rounded-md"
|
|
9720
|
+
}
|
|
9721
|
+
),
|
|
9722
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
9723
|
+
"span",
|
|
9724
|
+
{
|
|
9725
|
+
className: "qa-absolute qa-inline-flex qa-items-center qa-gap-1 qa-rounded-full qa-px-1.5 qa-py-0.5 qa-text-10",
|
|
9726
|
+
style: {
|
|
9727
|
+
bottom: 4,
|
|
9728
|
+
insetInlineEnd: 4,
|
|
9729
|
+
background: "var(--qa-surface-1)",
|
|
9730
|
+
color: "var(--qa-ink-hi)",
|
|
9731
|
+
border: "1px solid var(--qa-border-subtle)"
|
|
9732
|
+
},
|
|
9733
|
+
children: [
|
|
9734
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Pencil", size: 10 }),
|
|
9735
|
+
t("draw_label")
|
|
9736
|
+
]
|
|
9737
|
+
}
|
|
9738
|
+
)
|
|
9739
|
+
]
|
|
9740
|
+
}
|
|
9741
|
+
) : captureError ? (
|
|
9742
|
+
// The render broke rather than being skipped — offer a retry
|
|
9743
|
+
// against the same selection instead of a dead-end message.
|
|
9744
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-flex qa-flex-col qa-items-center qa-gap-2 qa-py-3", children: [
|
|
9745
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-text-xs qa-text-red-600", children: t("capture_failed") }),
|
|
9628
9746
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
9629
|
-
"
|
|
9747
|
+
"button",
|
|
9630
9748
|
{
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
background: "var(--qa-surface-1)",
|
|
9636
|
-
color: "var(--qa-ink-hi)",
|
|
9637
|
-
border: "1px solid var(--qa-border-subtle)"
|
|
9638
|
-
},
|
|
9749
|
+
type: "button",
|
|
9750
|
+
onClick: () => selection && void runCapture(selection.rect),
|
|
9751
|
+
className: "qa-tap qa-inline-flex qa-items-center qa-gap-1.5 qa-rounded-md qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-xs qa-text-mid qa-focus-ring",
|
|
9752
|
+
style: { background: "transparent", cursor: "pointer" },
|
|
9639
9753
|
children: [
|
|
9640
|
-
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "
|
|
9641
|
-
t("
|
|
9754
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "RotateCcw", size: 13 }),
|
|
9755
|
+
t("retry")
|
|
9642
9756
|
]
|
|
9643
9757
|
}
|
|
9644
9758
|
)
|
|
9645
|
-
]
|
|
9646
|
-
}
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
9759
|
+
] })
|
|
9760
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-py-4 qa-text-xs qa-text-slate-400", children: t("no_shot") }),
|
|
9761
|
+
shotUrl && shotEngine === "dom" && exactShots.supported && exactShots.status !== "live" && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9762
|
+
"span",
|
|
9763
|
+
{
|
|
9764
|
+
"data-qa-redraw-offer": "true",
|
|
9765
|
+
className: "qa-mt-1.5 qa-flex qa-items-center qa-justify-center qa-flex-wrap qa-gap-1.5 qa-text-10 qa-text-mid",
|
|
9766
|
+
children: [
|
|
9767
|
+
t("redraw_notice"),
|
|
9768
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
9769
|
+
"button",
|
|
9770
|
+
{
|
|
9771
|
+
type: "button",
|
|
9772
|
+
onClick: () => void (async () => {
|
|
9773
|
+
const granted = await enableExactShots();
|
|
9774
|
+
if (granted && selection) void runCapture(selection.rect);
|
|
9775
|
+
})(),
|
|
9776
|
+
title: t("exact_hint"),
|
|
9777
|
+
className: "qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-rounded-full qa-border qa-border-subtle qa-px-1.5 qa-py-0.5 qa-text-10 qa-text-hi qa-focus-ring",
|
|
9778
|
+
style: { background: "transparent", cursor: "pointer" },
|
|
9779
|
+
children: [
|
|
9780
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Camera", size: 10 }),
|
|
9781
|
+
t("redraw_action")
|
|
9782
|
+
]
|
|
9783
|
+
}
|
|
9784
|
+
)
|
|
9785
|
+
]
|
|
9786
|
+
}
|
|
9787
|
+
)
|
|
9788
|
+
]
|
|
9667
9789
|
}
|
|
9668
9790
|
),
|
|
9669
9791
|
/* @__PURE__ */ jsxRuntime.jsx(LocationReveal, { target: selection }),
|
|
@@ -9957,5 +10079,5 @@ function Qapture({ config }) {
|
|
|
9957
10079
|
exports.Qapture = Qapture;
|
|
9958
10080
|
exports.deleteQaDatabase = deleteQaDatabase;
|
|
9959
10081
|
exports.initQaStudio = initQaStudio;
|
|
9960
|
-
//# sourceMappingURL=chunk-
|
|
9961
|
-
//# sourceMappingURL=chunk-
|
|
10082
|
+
//# sourceMappingURL=chunk-C5WJFSMN.cjs.map
|
|
10083
|
+
//# sourceMappingURL=chunk-C5WJFSMN.cjs.map
|
package/dist/index.cjs
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkC5WJFSMN_cjs = require('./chunk-C5WJFSMN.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Object.defineProperty(exports, "QaStudio", {
|
|
8
8
|
enumerable: true,
|
|
9
|
-
get: function () { return
|
|
9
|
+
get: function () { return chunkC5WJFSMN_cjs.Qapture; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "Qapture", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkC5WJFSMN_cjs.Qapture; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "deleteQaDatabase", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunkC5WJFSMN_cjs.deleteQaDatabase; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "initQaStudio", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunkC5WJFSMN_cjs.initQaStudio; }
|
|
22
22
|
});
|
|
23
23
|
//# sourceMappingURL=index.cjs.map
|
|
24
24
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.js
CHANGED
package/dist/next.cjs
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var chunkC5WJFSMN_cjs = require('./chunk-C5WJFSMN.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "QaStudio", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunkC5WJFSMN_cjs.Qapture; }
|
|
11
11
|
});
|
|
12
12
|
Object.defineProperty(exports, "Qapture", {
|
|
13
13
|
enumerable: true,
|
|
14
|
-
get: function () { return
|
|
14
|
+
get: function () { return chunkC5WJFSMN_cjs.Qapture; }
|
|
15
15
|
});
|
|
16
16
|
Object.defineProperty(exports, "initQaStudio", {
|
|
17
17
|
enumerable: true,
|
|
18
|
-
get: function () { return
|
|
18
|
+
get: function () { return chunkC5WJFSMN_cjs.initQaStudio; }
|
|
19
19
|
});
|
|
20
20
|
//# sourceMappingURL=next.cjs.map
|
|
21
21
|
//# sourceMappingURL=next.cjs.map
|
package/dist/next.js
CHANGED
package/dist/standalone.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkC5WJFSMN_cjs = require('./chunk-C5WJFSMN.cjs');
|
|
4
4
|
|
|
5
5
|
// src/standalone.ts
|
|
6
6
|
if (typeof window !== "undefined" && typeof customElements !== "undefined" && !customElements.get("qapture-widget")) {
|
|
@@ -38,7 +38,7 @@ if (typeof window !== "undefined" && typeof customElements !== "undefined" && !c
|
|
|
38
38
|
|
|
39
39
|
Object.defineProperty(exports, "initQaStudio", {
|
|
40
40
|
enumerable: true,
|
|
41
|
-
get: function () { return
|
|
41
|
+
get: function () { return chunkC5WJFSMN_cjs.initQaStudio; }
|
|
42
42
|
});
|
|
43
43
|
//# sourceMappingURL=standalone.cjs.map
|
|
44
44
|
//# sourceMappingURL=standalone.cjs.map
|
package/dist/standalone.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qapture2",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "Drop-in, AI-aware in-browser QA capture widget. A tester annotates the live app (element/region + auto-screenshot + note), tracks a graded testing journey, and exports a ZIP your own coding agent reads. Ships zero AI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
"standalone-smoke": "node scripts/standalone-smoke.mjs",
|
|
90
90
|
"browser-test": "node scripts/browser-test.mjs",
|
|
91
91
|
"capture-accuracy-test": "node scripts/capture-accuracy-test.mjs",
|
|
92
|
+
"element-capture-test": "node scripts/element-capture-test.mjs",
|
|
92
93
|
"loop-features-test": "node scripts/loop-features-test.mjs",
|
|
93
94
|
"walk-test": "node scripts/walk-test.mjs",
|
|
94
95
|
"modern-css-test": "node scripts/modern-css-test.mjs",
|