@loupekit/sdk 0.9.3 → 0.10.0-next.40
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.global.js +68 -6
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +68 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.global.js
CHANGED
|
@@ -335,6 +335,14 @@ var Loupe = (() => {
|
|
|
335
335
|
page stays visible and tappable; the list returns when the tool closes. */
|
|
336
336
|
.dock.inspecting { height: auto !important; }
|
|
337
337
|
.dock.inspecting .listhead, .dock.inspecting .list { display: none; }
|
|
338
|
+
/* The composer is a bottom sheet on a phone: full width, thumb-reachable, and it
|
|
339
|
+
scrolls when the on-screen keyboard is up. JS sets its position inline (hence the
|
|
340
|
+
!important) \u2014 on small screens the sheet wins. */
|
|
341
|
+
.composer {
|
|
342
|
+
top: auto !important; left: 0 !important; right: 0 !important; bottom: 0 !important;
|
|
343
|
+
width: auto !important; max-height: 80vh; overflow-y: auto;
|
|
344
|
+
border-radius: 16px 16px 0 0; padding-bottom: 16px;
|
|
345
|
+
}
|
|
338
346
|
}
|
|
339
347
|
`
|
|
340
348
|
);
|
|
@@ -2458,6 +2466,26 @@ var Loupe = (() => {
|
|
|
2458
2466
|
for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
|
|
2459
2467
|
return h;
|
|
2460
2468
|
}
|
|
2469
|
+
function isTouchDevice() {
|
|
2470
|
+
try {
|
|
2471
|
+
return typeof window.matchMedia === "function" && window.matchMedia("(pointer: coarse)").matches;
|
|
2472
|
+
} catch {
|
|
2473
|
+
return false;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
function dataUrlToFile(dataUrl, name) {
|
|
2477
|
+
const m = /^data:([^;,]+)(;base64)?,([\s\S]*)$/.exec(dataUrl);
|
|
2478
|
+
if (!m) return null;
|
|
2479
|
+
const [, type, b64, payload = ""] = m;
|
|
2480
|
+
try {
|
|
2481
|
+
const raw = b64 ? atob(payload) : decodeURIComponent(payload);
|
|
2482
|
+
const bytes = new Uint8Array(raw.length);
|
|
2483
|
+
for (let i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i);
|
|
2484
|
+
return new File([bytes], name, { type: type || "image/png" });
|
|
2485
|
+
} catch {
|
|
2486
|
+
return null;
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2461
2489
|
var LoupeApp = class {
|
|
2462
2490
|
constructor(cfg) {
|
|
2463
2491
|
this.comments = [];
|
|
@@ -2824,7 +2852,7 @@ var Loupe = (() => {
|
|
|
2824
2852
|
regionBtn.title = "Drag a free-size box, screenshot it, and comment";
|
|
2825
2853
|
regionBtn.onclick = () => this.setMode(this.mode === "region" ? "off" : "region");
|
|
2826
2854
|
const recordBtn = this.toolBtn(RECORD_ICON, "Record", "record");
|
|
2827
|
-
recordBtn.title = "Drag a box, record a screen video of it, and comment";
|
|
2855
|
+
recordBtn.title = isTouchDevice() ? "Record your screen, then describe the issue" : "Drag a box, record a screen video of it, and comment";
|
|
2828
2856
|
recordBtn.onclick = () => this.setMode(this.mode === "record" ? "off" : "record");
|
|
2829
2857
|
const canRecord = typeof navigator.mediaDevices?.getDisplayMedia === "function";
|
|
2830
2858
|
tools.append(inspectBtn, freeBtn, regionBtn, ...canRecord ? [recordBtn] : []);
|
|
@@ -2974,6 +3002,34 @@ var Loupe = (() => {
|
|
|
2974
3002
|
const region = { x: vp.x + window.scrollX, y: vp.y + window.scrollY, w: vp.w, h: vp.h, rel };
|
|
2975
3003
|
return { region, element: centerEl };
|
|
2976
3004
|
}
|
|
3005
|
+
/**
|
|
3006
|
+
* Touch path for the Region tool: capture what is on screen right now and open the
|
|
3007
|
+
* composer with it already attached. The reporter scrolls to the part of the page they
|
|
3008
|
+
* mean FIRST, then taps Region — no drag, so nothing fights the page scroll.
|
|
3009
|
+
*/
|
|
3010
|
+
async captureViewportForComposer() {
|
|
3011
|
+
const vp = { x: 0, y: 0, w: window.innerWidth, h: window.innerHeight };
|
|
3012
|
+
const capture = this.cfg.captureRegion ?? captureRegionScreenshot;
|
|
3013
|
+
let shot;
|
|
3014
|
+
try {
|
|
3015
|
+
shot = await capture(vp);
|
|
3016
|
+
} catch {
|
|
3017
|
+
shot = void 0;
|
|
3018
|
+
}
|
|
3019
|
+
const file = shot ? dataUrlToFile(shot, "screenshot.png") : null;
|
|
3020
|
+
const docX = window.scrollX + vp.w / 2;
|
|
3021
|
+
const docY = window.scrollY + vp.h / 2;
|
|
3022
|
+
const docW = Math.max(1, document.documentElement.scrollWidth);
|
|
3023
|
+
const docH = Math.max(1, document.documentElement.scrollHeight);
|
|
3024
|
+
const offset = { x: clamp(docX / docW), y: clamp(docY / docH) };
|
|
3025
|
+
this.setMode("off");
|
|
3026
|
+
this.openComposer(
|
|
3027
|
+
{ kind: "free", offset, point: { x: docX, y: docY }, label: "Screenshot \xB7 attached" },
|
|
3028
|
+
8,
|
|
3029
|
+
window.innerHeight / 2,
|
|
3030
|
+
file ? [file] : []
|
|
3031
|
+
);
|
|
3032
|
+
}
|
|
2977
3033
|
/** Capture the selected viewport rect, then open the composer for a region comment. */
|
|
2978
3034
|
async finishRegion(vp) {
|
|
2979
3035
|
this.selbox.style.display = "none";
|
|
@@ -3057,7 +3113,6 @@ var Loupe = (() => {
|
|
|
3057
3113
|
document.removeEventListener("click", this.onClick, true);
|
|
3058
3114
|
document.removeEventListener("click", this.onFreeClick, true);
|
|
3059
3115
|
document.removeEventListener("pointerdown", this.onRegionDown, true);
|
|
3060
|
-
document.documentElement.style.touchAction = mode === "region" || mode === "record" ? "none" : "";
|
|
3061
3116
|
if (mode === "off") return;
|
|
3062
3117
|
document.addEventListener("keydown", this.onKey, true);
|
|
3063
3118
|
this.closeComposer();
|
|
@@ -3067,12 +3122,18 @@ var Loupe = (() => {
|
|
|
3067
3122
|
document.addEventListener("click", this.onClick, true);
|
|
3068
3123
|
} else if (mode === "free") {
|
|
3069
3124
|
document.addEventListener("click", this.onFreeClick, true);
|
|
3125
|
+
} else if (mode === "region" && isTouchDevice()) {
|
|
3126
|
+
void this.captureViewportForComposer();
|
|
3127
|
+
} else if (mode === "record" && isTouchDevice()) {
|
|
3128
|
+
const vp = { x: 0, y: 0, w: window.innerWidth, h: window.innerHeight };
|
|
3129
|
+
this.setMode("off");
|
|
3130
|
+
void this.finishRecording(vp);
|
|
3070
3131
|
} else if (mode === "region" || mode === "record") {
|
|
3071
3132
|
document.addEventListener("pointerdown", this.onRegionDown, true);
|
|
3072
3133
|
}
|
|
3073
3134
|
}
|
|
3074
3135
|
// ---- composer -------------------------------------------------------------
|
|
3075
|
-
openComposer(target, x, y) {
|
|
3136
|
+
openComposer(target, x, y, seedFiles = []) {
|
|
3076
3137
|
this.pending = target;
|
|
3077
3138
|
const isRecording = target.kind === "region" && !!target.recording;
|
|
3078
3139
|
const c = this.composer;
|
|
@@ -3080,14 +3141,14 @@ var Loupe = (() => {
|
|
|
3080
3141
|
const label = el(
|
|
3081
3142
|
"div",
|
|
3082
3143
|
"target",
|
|
3083
|
-
target.kind === "element" ? describe(target.element) : target.kind === "region" ? isRecording ? `\u23FA Recording \xB7 ${Math.round(target.region.w)}\xD7${Math.round(target.region.h)} px` : `Region \xB7 ${Math.round(target.region.w)}\xD7${Math.round(target.region.h)} px` : "Free note \xB7 anywhere on the page"
|
|
3144
|
+
target.kind === "element" ? describe(target.element) : target.kind === "region" ? isRecording ? `\u23FA Recording \xB7 ${Math.round(target.region.w)}\xD7${Math.round(target.region.h)} px` : `Region \xB7 ${Math.round(target.region.w)}\xD7${Math.round(target.region.h)} px` : target.label ?? "Free note \xB7 anywhere on the page"
|
|
3084
3145
|
);
|
|
3085
3146
|
const title = el("input", "title");
|
|
3086
3147
|
title.type = "text";
|
|
3087
3148
|
title.placeholder = "Title \u2014 one line: what's wrong, or what you need";
|
|
3088
3149
|
const ta = el("textarea");
|
|
3089
3150
|
ta.placeholder = isRecording ? "Describe the issue in this recording\u2026" : target.kind === "region" ? "Describe the issue in this area\u2026" : target.kind === "free" ? "Describe this note\u2026" : "Describe what should change here\u2026";
|
|
3090
|
-
const files =
|
|
3151
|
+
const files = seedFiles.slice();
|
|
3091
3152
|
const attach = el("div", "attach");
|
|
3092
3153
|
const pick = el("button", "pick", "\uFF0B Attach images / videos");
|
|
3093
3154
|
const input = document.createElement("input");
|
|
@@ -3129,6 +3190,7 @@ var Loupe = (() => {
|
|
|
3129
3190
|
};
|
|
3130
3191
|
pick.onclick = () => input.click();
|
|
3131
3192
|
attach.append(pick, input, chips, err);
|
|
3193
|
+
if (files.length) drawChips();
|
|
3132
3194
|
const row = el("div", "row");
|
|
3133
3195
|
let box = null;
|
|
3134
3196
|
if (target.kind !== "free" && !isRecording) {
|
|
@@ -3160,7 +3222,7 @@ var Loupe = (() => {
|
|
|
3160
3222
|
const left = Math.min(Math.max(8, x + 12), window.innerWidth - w - 8);
|
|
3161
3223
|
const top = Math.min(Math.max(8, y + 12), window.innerHeight - h - 8);
|
|
3162
3224
|
Object.assign(c.style, { display: "block", left: left + "px", top: top + "px" });
|
|
3163
|
-
ta.focus();
|
|
3225
|
+
if (!isTouchDevice()) ta.focus();
|
|
3164
3226
|
}
|
|
3165
3227
|
closeComposer() {
|
|
3166
3228
|
this.composer.style.display = "none";
|