@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.js
CHANGED
|
@@ -308,6 +308,14 @@ var STYLES = (
|
|
|
308
308
|
page stays visible and tappable; the list returns when the tool closes. */
|
|
309
309
|
.dock.inspecting { height: auto !important; }
|
|
310
310
|
.dock.inspecting .listhead, .dock.inspecting .list { display: none; }
|
|
311
|
+
/* The composer is a bottom sheet on a phone: full width, thumb-reachable, and it
|
|
312
|
+
scrolls when the on-screen keyboard is up. JS sets its position inline (hence the
|
|
313
|
+
!important) \u2014 on small screens the sheet wins. */
|
|
314
|
+
.composer {
|
|
315
|
+
top: auto !important; left: 0 !important; right: 0 !important; bottom: 0 !important;
|
|
316
|
+
width: auto !important; max-height: 80vh; overflow-y: auto;
|
|
317
|
+
border-radius: 16px 16px 0 0; padding-bottom: 16px;
|
|
318
|
+
}
|
|
311
319
|
}
|
|
312
320
|
`
|
|
313
321
|
);
|
|
@@ -2431,6 +2439,26 @@ function hash(s) {
|
|
|
2431
2439
|
for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
|
|
2432
2440
|
return h;
|
|
2433
2441
|
}
|
|
2442
|
+
function isTouchDevice() {
|
|
2443
|
+
try {
|
|
2444
|
+
return typeof window.matchMedia === "function" && window.matchMedia("(pointer: coarse)").matches;
|
|
2445
|
+
} catch {
|
|
2446
|
+
return false;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
function dataUrlToFile(dataUrl, name) {
|
|
2450
|
+
const m = /^data:([^;,]+)(;base64)?,([\s\S]*)$/.exec(dataUrl);
|
|
2451
|
+
if (!m) return null;
|
|
2452
|
+
const [, type, b64, payload = ""] = m;
|
|
2453
|
+
try {
|
|
2454
|
+
const raw = b64 ? atob(payload) : decodeURIComponent(payload);
|
|
2455
|
+
const bytes = new Uint8Array(raw.length);
|
|
2456
|
+
for (let i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i);
|
|
2457
|
+
return new File([bytes], name, { type: type || "image/png" });
|
|
2458
|
+
} catch {
|
|
2459
|
+
return null;
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2434
2462
|
var LoupeApp = class {
|
|
2435
2463
|
constructor(cfg) {
|
|
2436
2464
|
this.comments = [];
|
|
@@ -2797,7 +2825,7 @@ var LoupeApp = class {
|
|
|
2797
2825
|
regionBtn.title = "Drag a free-size box, screenshot it, and comment";
|
|
2798
2826
|
regionBtn.onclick = () => this.setMode(this.mode === "region" ? "off" : "region");
|
|
2799
2827
|
const recordBtn = this.toolBtn(RECORD_ICON, "Record", "record");
|
|
2800
|
-
recordBtn.title = "Drag a box, record a screen video of it, and comment";
|
|
2828
|
+
recordBtn.title = isTouchDevice() ? "Record your screen, then describe the issue" : "Drag a box, record a screen video of it, and comment";
|
|
2801
2829
|
recordBtn.onclick = () => this.setMode(this.mode === "record" ? "off" : "record");
|
|
2802
2830
|
const canRecord = typeof navigator.mediaDevices?.getDisplayMedia === "function";
|
|
2803
2831
|
tools.append(inspectBtn, freeBtn, regionBtn, ...canRecord ? [recordBtn] : []);
|
|
@@ -2947,6 +2975,34 @@ var LoupeApp = class {
|
|
|
2947
2975
|
const region = { x: vp.x + window.scrollX, y: vp.y + window.scrollY, w: vp.w, h: vp.h, rel };
|
|
2948
2976
|
return { region, element: centerEl };
|
|
2949
2977
|
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Touch path for the Region tool: capture what is on screen right now and open the
|
|
2980
|
+
* composer with it already attached. The reporter scrolls to the part of the page they
|
|
2981
|
+
* mean FIRST, then taps Region — no drag, so nothing fights the page scroll.
|
|
2982
|
+
*/
|
|
2983
|
+
async captureViewportForComposer() {
|
|
2984
|
+
const vp = { x: 0, y: 0, w: window.innerWidth, h: window.innerHeight };
|
|
2985
|
+
const capture = this.cfg.captureRegion ?? captureRegionScreenshot;
|
|
2986
|
+
let shot;
|
|
2987
|
+
try {
|
|
2988
|
+
shot = await capture(vp);
|
|
2989
|
+
} catch {
|
|
2990
|
+
shot = void 0;
|
|
2991
|
+
}
|
|
2992
|
+
const file = shot ? dataUrlToFile(shot, "screenshot.png") : null;
|
|
2993
|
+
const docX = window.scrollX + vp.w / 2;
|
|
2994
|
+
const docY = window.scrollY + vp.h / 2;
|
|
2995
|
+
const docW = Math.max(1, document.documentElement.scrollWidth);
|
|
2996
|
+
const docH = Math.max(1, document.documentElement.scrollHeight);
|
|
2997
|
+
const offset = { x: clamp(docX / docW), y: clamp(docY / docH) };
|
|
2998
|
+
this.setMode("off");
|
|
2999
|
+
this.openComposer(
|
|
3000
|
+
{ kind: "free", offset, point: { x: docX, y: docY }, label: "Screenshot \xB7 attached" },
|
|
3001
|
+
8,
|
|
3002
|
+
window.innerHeight / 2,
|
|
3003
|
+
file ? [file] : []
|
|
3004
|
+
);
|
|
3005
|
+
}
|
|
2950
3006
|
/** Capture the selected viewport rect, then open the composer for a region comment. */
|
|
2951
3007
|
async finishRegion(vp) {
|
|
2952
3008
|
this.selbox.style.display = "none";
|
|
@@ -3030,7 +3086,6 @@ var LoupeApp = class {
|
|
|
3030
3086
|
document.removeEventListener("click", this.onClick, true);
|
|
3031
3087
|
document.removeEventListener("click", this.onFreeClick, true);
|
|
3032
3088
|
document.removeEventListener("pointerdown", this.onRegionDown, true);
|
|
3033
|
-
document.documentElement.style.touchAction = mode === "region" || mode === "record" ? "none" : "";
|
|
3034
3089
|
if (mode === "off") return;
|
|
3035
3090
|
document.addEventListener("keydown", this.onKey, true);
|
|
3036
3091
|
this.closeComposer();
|
|
@@ -3040,12 +3095,18 @@ var LoupeApp = class {
|
|
|
3040
3095
|
document.addEventListener("click", this.onClick, true);
|
|
3041
3096
|
} else if (mode === "free") {
|
|
3042
3097
|
document.addEventListener("click", this.onFreeClick, true);
|
|
3098
|
+
} else if (mode === "region" && isTouchDevice()) {
|
|
3099
|
+
void this.captureViewportForComposer();
|
|
3100
|
+
} else if (mode === "record" && isTouchDevice()) {
|
|
3101
|
+
const vp = { x: 0, y: 0, w: window.innerWidth, h: window.innerHeight };
|
|
3102
|
+
this.setMode("off");
|
|
3103
|
+
void this.finishRecording(vp);
|
|
3043
3104
|
} else if (mode === "region" || mode === "record") {
|
|
3044
3105
|
document.addEventListener("pointerdown", this.onRegionDown, true);
|
|
3045
3106
|
}
|
|
3046
3107
|
}
|
|
3047
3108
|
// ---- composer -------------------------------------------------------------
|
|
3048
|
-
openComposer(target, x, y) {
|
|
3109
|
+
openComposer(target, x, y, seedFiles = []) {
|
|
3049
3110
|
this.pending = target;
|
|
3050
3111
|
const isRecording = target.kind === "region" && !!target.recording;
|
|
3051
3112
|
const c = this.composer;
|
|
@@ -3053,14 +3114,14 @@ var LoupeApp = class {
|
|
|
3053
3114
|
const label = el(
|
|
3054
3115
|
"div",
|
|
3055
3116
|
"target",
|
|
3056
|
-
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"
|
|
3117
|
+
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"
|
|
3057
3118
|
);
|
|
3058
3119
|
const title = el("input", "title");
|
|
3059
3120
|
title.type = "text";
|
|
3060
3121
|
title.placeholder = "Title \u2014 one line: what's wrong, or what you need";
|
|
3061
3122
|
const ta = el("textarea");
|
|
3062
3123
|
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";
|
|
3063
|
-
const files =
|
|
3124
|
+
const files = seedFiles.slice();
|
|
3064
3125
|
const attach = el("div", "attach");
|
|
3065
3126
|
const pick = el("button", "pick", "\uFF0B Attach images / videos");
|
|
3066
3127
|
const input = document.createElement("input");
|
|
@@ -3102,6 +3163,7 @@ var LoupeApp = class {
|
|
|
3102
3163
|
};
|
|
3103
3164
|
pick.onclick = () => input.click();
|
|
3104
3165
|
attach.append(pick, input, chips, err);
|
|
3166
|
+
if (files.length) drawChips();
|
|
3105
3167
|
const row = el("div", "row");
|
|
3106
3168
|
let box = null;
|
|
3107
3169
|
if (target.kind !== "free" && !isRecording) {
|
|
@@ -3133,7 +3195,7 @@ var LoupeApp = class {
|
|
|
3133
3195
|
const left = Math.min(Math.max(8, x + 12), window.innerWidth - w - 8);
|
|
3134
3196
|
const top = Math.min(Math.max(8, y + 12), window.innerHeight - h - 8);
|
|
3135
3197
|
Object.assign(c.style, { display: "block", left: left + "px", top: top + "px" });
|
|
3136
|
-
ta.focus();
|
|
3198
|
+
if (!isTouchDevice()) ta.focus();
|
|
3137
3199
|
}
|
|
3138
3200
|
closeComposer() {
|
|
3139
3201
|
this.composer.style.display = "none";
|