@loupekit/sdk 0.9.3-next.37 → 0.9.3-next.38
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 +64 -6
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +64 -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 = [];
|
|
@@ -2826,7 +2854,7 @@ var Loupe = (() => {
|
|
|
2826
2854
|
const recordBtn = this.toolBtn(RECORD_ICON, "Record", "record");
|
|
2827
2855
|
recordBtn.title = "Drag a box, record a screen video of it, and comment";
|
|
2828
2856
|
recordBtn.onclick = () => this.setMode(this.mode === "record" ? "off" : "record");
|
|
2829
|
-
const canRecord = typeof navigator.mediaDevices?.getDisplayMedia === "function";
|
|
2857
|
+
const canRecord = !isTouchDevice() && typeof navigator.mediaDevices?.getDisplayMedia === "function";
|
|
2830
2858
|
tools.append(inspectBtn, freeBtn, regionBtn, ...canRecord ? [recordBtn] : []);
|
|
2831
2859
|
const listHead = el("div", "listhead");
|
|
2832
2860
|
listHead.append(document.createTextNode("Comments"));
|
|
@@ -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,14 @@ 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();
|
|
3070
3127
|
} else if (mode === "region" || mode === "record") {
|
|
3071
3128
|
document.addEventListener("pointerdown", this.onRegionDown, true);
|
|
3072
3129
|
}
|
|
3073
3130
|
}
|
|
3074
3131
|
// ---- composer -------------------------------------------------------------
|
|
3075
|
-
openComposer(target, x, y) {
|
|
3132
|
+
openComposer(target, x, y, seedFiles = []) {
|
|
3076
3133
|
this.pending = target;
|
|
3077
3134
|
const isRecording = target.kind === "region" && !!target.recording;
|
|
3078
3135
|
const c = this.composer;
|
|
@@ -3080,14 +3137,14 @@ var Loupe = (() => {
|
|
|
3080
3137
|
const label = el(
|
|
3081
3138
|
"div",
|
|
3082
3139
|
"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"
|
|
3140
|
+
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
3141
|
);
|
|
3085
3142
|
const title = el("input", "title");
|
|
3086
3143
|
title.type = "text";
|
|
3087
3144
|
title.placeholder = "Title \u2014 one line: what's wrong, or what you need";
|
|
3088
3145
|
const ta = el("textarea");
|
|
3089
3146
|
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 =
|
|
3147
|
+
const files = seedFiles.slice();
|
|
3091
3148
|
const attach = el("div", "attach");
|
|
3092
3149
|
const pick = el("button", "pick", "\uFF0B Attach images / videos");
|
|
3093
3150
|
const input = document.createElement("input");
|
|
@@ -3129,6 +3186,7 @@ var Loupe = (() => {
|
|
|
3129
3186
|
};
|
|
3130
3187
|
pick.onclick = () => input.click();
|
|
3131
3188
|
attach.append(pick, input, chips, err);
|
|
3189
|
+
if (files.length) drawChips();
|
|
3132
3190
|
const row = el("div", "row");
|
|
3133
3191
|
let box = null;
|
|
3134
3192
|
if (target.kind !== "free" && !isRecording) {
|
|
@@ -3160,7 +3218,7 @@ var Loupe = (() => {
|
|
|
3160
3218
|
const left = Math.min(Math.max(8, x + 12), window.innerWidth - w - 8);
|
|
3161
3219
|
const top = Math.min(Math.max(8, y + 12), window.innerHeight - h - 8);
|
|
3162
3220
|
Object.assign(c.style, { display: "block", left: left + "px", top: top + "px" });
|
|
3163
|
-
ta.focus();
|
|
3221
|
+
if (!isTouchDevice()) ta.focus();
|
|
3164
3222
|
}
|
|
3165
3223
|
closeComposer() {
|
|
3166
3224
|
this.composer.style.display = "none";
|