@omg-dev/sdk 0.4.29 → 0.4.31

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.
@@ -1,808 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- //#region src/auth/bridge.ts
4
- let current = {
5
- user: null,
6
- token: null,
7
- authReady: false
8
- };
9
- const authChangeListeners = /* @__PURE__ */ new Set();
10
- function setAuthContext(snap) {
11
- current = snap;
12
- for (const cb of authChangeListeners) cb(snap);
13
- }
14
- function getAuthContext() {
15
- return current;
16
- }
17
- /**
18
- * Subscribe to auth snapshot changes (user / token / authReady). Returns an
19
- * unsubscribe fn. Data hooks use this to re-establish a subscription when the
20
- * JWT finishes loading (null→token) or on sign-out (token→null) — without it
21
- * the WS connects once, often before getToken() resolves, and never retries
22
- * with a bearer.
23
- */
24
- function subscribeAuthChange(cb) {
25
- authChangeListeners.add(cb);
26
- return () => authChangeListeners.delete(cb);
27
- }
28
- const authRequiredListeners = /* @__PURE__ */ new Set();
29
- /** Subscribe to auth-required signals. Returns an unsubscribe fn. */
30
- function subscribeAuthRequired(cb) {
31
- authRequiredListeners.add(cb);
32
- return () => authRequiredListeners.delete(cb);
33
- }
34
- /** Notify all subscribers that a request hit the server's auth-required signal. */
35
- function notifyAuthRequired() {
36
- for (const cb of authRequiredListeners) cb();
37
- }
38
- //#endregion
39
- //#region src/storage/useUpload.ts
40
- function bearerHeaders() {
41
- const { token } = getAuthContext();
42
- return token ? { Authorization: `Bearer ${token}` } : {};
43
- }
44
- /**
45
- * Headless upload hook. Returns the upload function plus state.
46
- *
47
- * Usage:
48
- * const { upload, uploading, progress, error } = useUpload()
49
- * await upload(file, { key: `avatars/${user.id}.png` })
50
- */
51
- function useUpload() {
52
- const [state, setState] = useState({
53
- uploading: false,
54
- progress: 0,
55
- error: null,
56
- result: null
57
- });
58
- const reset = useCallback(() => {
59
- setState({
60
- uploading: false,
61
- progress: 0,
62
- error: null,
63
- result: null
64
- });
65
- }, []);
66
- const upload = useCallback(async (file, opts) => {
67
- setState({
68
- uploading: true,
69
- progress: 0,
70
- error: null,
71
- result: null
72
- });
73
- const presignUrl = opts.presignUrl ?? "/api/_storage/presign";
74
- const contentType = opts.contentType ?? file.type ?? "application/octet-stream";
75
- try {
76
- const presignRes = await fetch(presignUrl, {
77
- method: "POST",
78
- headers: {
79
- "Content-Type": "application/json",
80
- ...bearerHeaders()
81
- },
82
- body: JSON.stringify({
83
- action: "put",
84
- key: opts.key,
85
- contentType,
86
- scope: opts.scope ?? "user"
87
- })
88
- });
89
- if (!presignRes.ok) {
90
- if (presignRes.status === 401 && getAuthContext().authReady) notifyAuthRequired();
91
- const text = await presignRes.text().catch(() => "");
92
- throw new Error(`presign ${presignRes.status}: ${text.slice(0, 200)}`);
93
- }
94
- const { url, key: storedKey, maxBytes } = await presignRes.json();
95
- if (typeof maxBytes === "number" && file.size > maxBytes) throw new Error(`file too large (${file.size} > ${maxBytes} bytes)`);
96
- await new Promise((resolve, reject) => {
97
- const xhr = new XMLHttpRequest();
98
- xhr.open("PUT", url);
99
- xhr.setRequestHeader("Content-Type", contentType);
100
- xhr.upload.onprogress = (e) => {
101
- if (e.lengthComputable) {
102
- const frac = e.loaded / e.total;
103
- setState((s) => ({
104
- ...s,
105
- progress: frac
106
- }));
107
- opts.onProgress?.(frac);
108
- }
109
- };
110
- xhr.onload = () => {
111
- if (xhr.status >= 200 && xhr.status < 300) resolve();
112
- else reject(/* @__PURE__ */ new Error(`PUT ${xhr.status}: ${xhr.responseText.slice(0, 200)}`));
113
- };
114
- xhr.onerror = () => reject(/* @__PURE__ */ new Error("network error during upload"));
115
- xhr.send(file);
116
- });
117
- const dlRes = await fetch(presignUrl, {
118
- method: "POST",
119
- headers: {
120
- "Content-Type": "application/json",
121
- ...bearerHeaders()
122
- },
123
- body: JSON.stringify({
124
- action: "get",
125
- key: opts.key,
126
- scope: opts.scope ?? "user"
127
- })
128
- });
129
- let downloadUrl = "";
130
- if (dlRes.ok) downloadUrl = (await dlRes.json()).url ?? "";
131
- fetch(presignUrl.replace(/\/presign$/, "/notify"), {
132
- method: "POST",
133
- headers: {
134
- "Content-Type": "application/json",
135
- ...bearerHeaders()
136
- },
137
- body: JSON.stringify({
138
- action: "upload",
139
- key: opts.key,
140
- size: file.size,
141
- contentType,
142
- scope: opts.scope ?? "user"
143
- })
144
- }).catch(() => {});
145
- const result = {
146
- key: storedKey,
147
- downloadUrl,
148
- size: file.size,
149
- contentType
150
- };
151
- setState({
152
- uploading: false,
153
- progress: 1,
154
- error: null,
155
- result
156
- });
157
- return result;
158
- } catch (err) {
159
- setState({
160
- uploading: false,
161
- progress: 0,
162
- error: err.message ?? String(err),
163
- result: null
164
- });
165
- throw err;
166
- }
167
- }, []);
168
- return {
169
- ...state,
170
- upload,
171
- reset
172
- };
173
- }
174
- //#endregion
175
- //#region src/feedback/gestures.ts
176
- /** Default gestures when the host doesn't specify any. Permission-free and
177
- * OS-gesture-safe — notably excludes "shake" (see the note above). */
178
- const DEFAULT_GESTURES = ["two-finger-press"];
179
- const DEFAULTS = {
180
- edgeSize: 28,
181
- swipeThreshold: 90,
182
- swipeMaxDuration: 600,
183
- shakeThreshold: 24,
184
- pressDuration: 450,
185
- cooldown: 1200
186
- };
187
- function hasWindow() {
188
- return typeof window !== "undefined" && typeof document !== "undefined";
189
- }
190
- function motionPermissionState() {
191
- if (!hasWindow() || typeof window.DeviceMotionEvent === "undefined") return "unsupported";
192
- return typeof window.DeviceMotionEvent.requestPermission === "function" ? "prompt" : "granted";
193
- }
194
- /**
195
- * Ask for accelerometer access (iOS 13+). MUST be called from inside a user
196
- * gesture handler (tap), or iOS rejects it. No-op elsewhere (returns true).
197
- */
198
- async function requestMotionPermission() {
199
- const state = motionPermissionState();
200
- if (state === "granted") return true;
201
- if (state === "unsupported") return false;
202
- const ctor = window.DeviceMotionEvent;
203
- try {
204
- return await ctor.requestPermission() === "granted";
205
- } catch {
206
- return false;
207
- }
208
- }
209
- /**
210
- * Attach gesture listeners to the document. Returns a teardown function.
211
- * `onTrigger` fires (debounced by `cooldown`) whenever any armed gesture
212
- * completes. Pure DOM — usable without React.
213
- */
214
- function attachGestureListeners(onTrigger, options = {}) {
215
- if (!hasWindow()) return () => {};
216
- const opts = {
217
- ...DEFAULTS,
218
- ...options
219
- };
220
- const gestures = new Set(options.gestures ?? DEFAULT_GESTURES);
221
- const cleanups = [];
222
- let lastTrigger = 0;
223
- function fire() {
224
- const now = Date.now();
225
- if (now - lastTrigger < opts.cooldown) return;
226
- lastTrigger = now;
227
- try {
228
- navigator.vibrate?.(12);
229
- } catch {}
230
- onTrigger();
231
- }
232
- if (gestures.has("swipe-up") || gestures.has("swipe-down") || gestures.has("swipe-left") || gestures.has("swipe-right")) {
233
- let sx = 0;
234
- let sy = 0;
235
- let st = 0;
236
- let fromEdge = null;
237
- const onStart = (e) => {
238
- if (e.touches.length !== 1) {
239
- fromEdge = null;
240
- return;
241
- }
242
- const t = e.touches[0];
243
- sx = t.clientX;
244
- sy = t.clientY;
245
- st = Date.now();
246
- const w = window.innerWidth;
247
- const h = window.innerHeight;
248
- fromEdge = gestures.has("swipe-down") && sy <= opts.edgeSize ? "top" : gestures.has("swipe-up") && sy >= h - opts.edgeSize ? "bottom" : gestures.has("swipe-right") && sx <= opts.edgeSize ? "left" : gestures.has("swipe-left") && sx >= w - opts.edgeSize ? "right" : null;
249
- };
250
- const onEnd = (e) => {
251
- if (!fromEdge) return;
252
- const edge = fromEdge;
253
- fromEdge = null;
254
- const t = e.changedTouches[0];
255
- if (!t) return;
256
- const dx = t.clientX - sx;
257
- const dy = t.clientY - sy;
258
- if (Date.now() - st > opts.swipeMaxDuration) return;
259
- if (edge === "bottom" && -dy >= opts.swipeThreshold && Math.abs(dy) > Math.abs(dx) || edge === "top" && dy >= opts.swipeThreshold && Math.abs(dy) > Math.abs(dx) || edge === "right" && -dx >= opts.swipeThreshold && Math.abs(dx) > Math.abs(dy) || edge === "left" && dx >= opts.swipeThreshold && Math.abs(dx) > Math.abs(dy)) fire();
260
- };
261
- document.addEventListener("touchstart", onStart, { passive: true });
262
- document.addEventListener("touchend", onEnd, { passive: true });
263
- cleanups.push(() => {
264
- document.removeEventListener("touchstart", onStart);
265
- document.removeEventListener("touchend", onEnd);
266
- });
267
- }
268
- if (gestures.has("two-finger-press")) {
269
- let timer = null;
270
- const clear = () => {
271
- if (timer) {
272
- clearTimeout(timer);
273
- timer = null;
274
- }
275
- };
276
- const onStart = (e) => {
277
- if (e.touches.length === 2) {
278
- clear();
279
- timer = setTimeout(fire, opts.pressDuration);
280
- } else clear();
281
- };
282
- document.addEventListener("touchstart", onStart, { passive: true });
283
- document.addEventListener("touchend", clear, { passive: true });
284
- document.addEventListener("touchmove", clear, { passive: true });
285
- document.addEventListener("touchcancel", clear, { passive: true });
286
- cleanups.push(() => {
287
- clear();
288
- document.removeEventListener("touchstart", onStart);
289
- document.removeEventListener("touchend", clear);
290
- document.removeEventListener("touchmove", clear);
291
- document.removeEventListener("touchcancel", clear);
292
- });
293
- }
294
- if (gestures.has("shake")) {
295
- let lx = 0;
296
- let ly = 0;
297
- let lz = 0;
298
- let seeded = false;
299
- let jolts = 0;
300
- let windowStart = 0;
301
- const onMotion = (e) => {
302
- const a = e.accelerationIncludingGravity;
303
- if (!a || a.x == null || a.y == null || a.z == null) return;
304
- if (!seeded) {
305
- lx = a.x;
306
- ly = a.y;
307
- lz = a.z;
308
- seeded = true;
309
- return;
310
- }
311
- const delta = Math.abs(a.x - lx) + Math.abs(a.y - ly) + Math.abs(a.z - lz);
312
- lx = a.x;
313
- ly = a.y;
314
- lz = a.z;
315
- if (delta < opts.shakeThreshold) return;
316
- const now = Date.now();
317
- if (now - windowStart > 1e3) {
318
- windowStart = now;
319
- jolts = 0;
320
- }
321
- if (++jolts >= 3) {
322
- jolts = 0;
323
- fire();
324
- }
325
- };
326
- window.addEventListener("devicemotion", onMotion);
327
- cleanups.push(() => window.removeEventListener("devicemotion", onMotion));
328
- }
329
- return () => {
330
- for (const c of cleanups) c();
331
- };
332
- }
333
- /**
334
- * React hook: arm the feedback gestures and call `onTrigger` when one fires.
335
- * Returns the motion-permission state + a primer to request it (iOS).
336
- */
337
- function useFeedbackGesture(onTrigger, options = {}) {
338
- const cb = useRef(onTrigger);
339
- cb.current = onTrigger;
340
- const [motionPermission, setMotionPermission] = useState(() => motionPermissionState());
341
- const { enabled = true } = options;
342
- useEffect(() => {
343
- if (!enabled) return;
344
- return attachGestureListeners(() => cb.current(), options);
345
- }, [JSON.stringify({
346
- g: options.gestures ?? DEFAULT_GESTURES,
347
- e: options.edgeSize,
348
- s: options.swipeThreshold,
349
- d: options.swipeMaxDuration,
350
- t: options.shakeThreshold,
351
- p: options.pressDuration,
352
- c: options.cooldown,
353
- enabled
354
- })]);
355
- return {
356
- motionPermission,
357
- requestMotionPermission: async () => {
358
- const ok = await requestMotionPermission();
359
- setMotionPermission(ok ? "granted" : "denied");
360
- return ok;
361
- }
362
- };
363
- }
364
- //#endregion
365
- //#region src/feedback/trace.ts
366
- let installed = null;
367
- function push(b) {
368
- if (!installed) return;
369
- installed.buffer.push(b);
370
- if (installed.buffer.length > installed.max) installed.buffer.shift();
371
- }
372
- function safeString(v) {
373
- if (typeof v === "string") return v;
374
- if (v instanceof Error) return v.message;
375
- try {
376
- return JSON.stringify(v);
377
- } catch {
378
- return String(v);
379
- }
380
- }
381
- /** Begin collecting breadcrumbs. Idempotent — a second call is a no-op. */
382
- function installTrace(options = {}) {
383
- if (installed) return installed.teardown;
384
- if (typeof window === "undefined") return () => {};
385
- const max = options.max ?? 50;
386
- const buffer = [];
387
- const cleanups = [];
388
- installed = {
389
- buffer,
390
- teardown: () => {},
391
- max
392
- };
393
- if (options.console !== false && typeof console !== "undefined") for (const level of ["error", "warn"]) {
394
- const orig = console[level];
395
- if (typeof orig !== "function") continue;
396
- console[level] = (...args) => {
397
- push({
398
- at: Date.now(),
399
- kind: "console",
400
- level,
401
- message: args.map(safeString).join(" ").slice(0, 1e3)
402
- });
403
- return orig.apply(console, args);
404
- };
405
- cleanups.push(() => {
406
- console[level] = orig;
407
- });
408
- }
409
- if (options.errors !== false) {
410
- const onError = (e) => {
411
- push({
412
- at: Date.now(),
413
- kind: "error",
414
- message: e.message || "Uncaught error",
415
- data: {
416
- source: e.filename,
417
- line: e.lineno,
418
- col: e.colno,
419
- stack: e.error?.stack?.slice(0, 2e3)
420
- }
421
- });
422
- };
423
- const onRejection = (e) => {
424
- const r = e.reason;
425
- push({
426
- at: Date.now(),
427
- kind: "rejection",
428
- message: safeString(r).slice(0, 1e3),
429
- data: { stack: r?.stack?.slice(0, 2e3) }
430
- });
431
- };
432
- window.addEventListener("error", onError);
433
- window.addEventListener("unhandledrejection", onRejection);
434
- cleanups.push(() => {
435
- window.removeEventListener("error", onError);
436
- window.removeEventListener("unhandledrejection", onRejection);
437
- });
438
- }
439
- if (options.network !== false && typeof window.fetch === "function") {
440
- const origFetch = window.fetch.bind(window);
441
- window.fetch = async (...args) => {
442
- const started = Date.now();
443
- const url = typeof args[0] === "string" ? args[0] : args[0]?.url ?? String(args[0]);
444
- const method = (args[1]?.method ?? args[0]?.method ?? "GET").toUpperCase();
445
- try {
446
- const res = await origFetch(...args);
447
- const durationMs = Date.now() - started;
448
- if (!res.ok || durationMs > 2e3) push({
449
- at: started,
450
- kind: "network",
451
- level: method,
452
- message: `${res.status} ${method} ${url}`.slice(0, 500),
453
- data: {
454
- status: res.status,
455
- durationMs
456
- }
457
- });
458
- return res;
459
- } catch (err) {
460
- push({
461
- at: started,
462
- kind: "network",
463
- level: method,
464
- message: `FAILED ${method} ${url}`.slice(0, 500),
465
- data: {
466
- error: safeString(err),
467
- durationMs: Date.now() - started
468
- }
469
- });
470
- throw err;
471
- }
472
- };
473
- cleanups.push(() => {
474
- window.fetch = origFetch;
475
- });
476
- }
477
- const teardown = () => {
478
- for (const c of cleanups) c();
479
- installed = null;
480
- };
481
- installed.teardown = teardown;
482
- return teardown;
483
- }
484
- /** Snapshot the current breadcrumbs (most-recent last). Empty if not installed. */
485
- function getTrace() {
486
- return installed ? [...installed.buffer] : [];
487
- }
488
- /** Clear the buffer without uninstalling (e.g. after a report is sent). */
489
- function clearTrace() {
490
- if (installed) installed.buffer.length = 0;
491
- }
492
- //#endregion
493
- //#region src/feedback/screenshot.ts
494
- async function captureScreenshot(options = {}) {
495
- if (typeof document === "undefined" || typeof window === "undefined") return null;
496
- const target = options.target ?? document.body;
497
- if (!target) return null;
498
- try {
499
- return await (await import("modern-screenshot")).domToPng(target, {
500
- scale: options.scale ?? Math.min(window.devicePixelRatio || 1, 2),
501
- backgroundColor: getComputedStyle(document.body).backgroundColor || "#ffffff",
502
- filter: (node) => !(node instanceof HTMLElement && node.hasAttribute("data-vibes-feedback"))
503
- });
504
- } catch {
505
- return null;
506
- }
507
- }
508
- //#endregion
509
- //#region src/feedback/VibesFeedback.tsx
510
- const STYLE_ID = "vibes-feedback-styles";
511
- function ensureStyles() {
512
- if (typeof document === "undefined" || document.getElementById(STYLE_ID)) return;
513
- const el = document.createElement("style");
514
- el.id = STYLE_ID;
515
- el.textContent = `
516
- @keyframes vibes-fb-in { from { transform: translateY(100%); } to { transform: translateY(0); } }
517
- @keyframes vibes-fb-fade { from { opacity: 0; } to { opacity: 1; } }
518
- .vibes-fb-sheet { animation: vibes-fb-in .26s cubic-bezier(.22,1,.36,1); }
519
- .vibes-fb-backdrop { animation: vibes-fb-fade .2s ease-out; }
520
- .vibes-fb-btn:active { transform: scale(.96); }
521
- `;
522
- document.head.appendChild(el);
523
- }
524
- async function defaultSubmit(report) {
525
- const res = await fetch("/_report", {
526
- method: "POST",
527
- headers: { "content-type": "application/json" },
528
- body: JSON.stringify(report)
529
- });
530
- if (!res.ok) throw new Error(`Feedback failed (${res.status})`);
531
- }
532
- function VibesFeedback({ gestures, showButton = true, traceLimit = 50, screenshot = true, captureScreenshot: captureScreenshot$1, onSubmit, accent = "#6366f1", title = "Send feedback" }) {
533
- const [open, setOpen] = useState(false);
534
- const [message, setMessage] = useState("");
535
- const [shot, setShot] = useState(null);
536
- const [trace, setTrace] = useState([]);
537
- const [sending, setSending] = useState(false);
538
- const [done, setDone] = useState(false);
539
- const [error, setError] = useState(null);
540
- const { upload } = useUpload();
541
- const capturer = useMemo(() => captureScreenshot$1 ?? (screenshot ? () => captureScreenshot() : null), [captureScreenshot$1, screenshot]);
542
- useEffect(() => installTrace({ max: traceLimit }), [traceLimit]);
543
- const summon = useCallback(() => {
544
- ensureStyles();
545
- setTrace(getTrace());
546
- setMessage("");
547
- setShot(null);
548
- setError(null);
549
- setDone(false);
550
- setOpen(true);
551
- if (capturer) capturer().then((s) => setShot(s)).catch(() => setShot(null));
552
- }, [capturer]);
553
- const { motionPermission, requestMotionPermission } = useFeedbackGesture(summon, { gestures });
554
- useEffect(() => {
555
- if (typeof window === "undefined") return;
556
- const onOpen = () => summon();
557
- window.addEventListener("vibes:feedback:open", onOpen);
558
- return () => window.removeEventListener("vibes:feedback:open", onOpen);
559
- }, [summon]);
560
- const shakeEnabled = (gestures ?? DEFAULT_GESTURES).includes("shake");
561
- const primedRef = useRef(false);
562
- useEffect(() => {
563
- if (!shakeEnabled || motionPermission !== "prompt" || primedRef.current) return;
564
- const prime = () => {
565
- if (primedRef.current) return;
566
- primedRef.current = true;
567
- requestMotionPermission();
568
- };
569
- window.addEventListener("pointerdown", prime, {
570
- once: true,
571
- capture: true
572
- });
573
- return () => window.removeEventListener("pointerdown", prime, { capture: true });
574
- }, [
575
- shakeEnabled,
576
- motionPermission,
577
- requestMotionPermission
578
- ]);
579
- async function uploadShot(dataUrl) {
580
- try {
581
- const res = await upload(await (await fetch(dataUrl)).blob(), {
582
- key: `feedback/${Date.now()}-${Math.random().toString(36).slice(2, 8)}.png`,
583
- scope: "app",
584
- contentType: "image/png"
585
- });
586
- return {
587
- screenshotKey: res.key,
588
- screenshotUrl: res.downloadUrl
589
- };
590
- } catch {
591
- return {};
592
- }
593
- }
594
- async function send() {
595
- const text = message.trim();
596
- if (!text || sending) return;
597
- setSending(true);
598
- setError(null);
599
- const shotRefs = shot ? await uploadShot(shot) : {};
600
- const { user } = getAuthContext();
601
- const report = {
602
- message: text,
603
- ...shotRefs,
604
- trace,
605
- reporterEmail: user?.email,
606
- pageUrl: typeof window !== "undefined" ? window.location.href : "",
607
- userAgent: typeof navigator !== "undefined" ? navigator.userAgent : ""
608
- };
609
- try {
610
- await (onSubmit ?? defaultSubmit)(report);
611
- clearTrace();
612
- setDone(true);
613
- setTimeout(() => setOpen(false), 1100);
614
- } catch (e) {
615
- setError(e instanceof Error ? e.message : "Couldn't send. Try again.");
616
- } finally {
617
- setSending(false);
618
- }
619
- }
620
- return /* @__PURE__ */ jsxs(Fragment, { children: [showButton && /* @__PURE__ */ jsx("button", {
621
- "aria-label": "Send feedback",
622
- "data-vibes-feedback": "",
623
- className: "vibes-fb-btn",
624
- onClick: () => {
625
- if (shakeEnabled && motionPermission === "prompt") requestMotionPermission();
626
- summon();
627
- },
628
- style: {
629
- position: "fixed",
630
- right: 16,
631
- bottom: "calc(16px + env(safe-area-inset-bottom))",
632
- zIndex: 2147482e3,
633
- width: 44,
634
- height: 44,
635
- borderRadius: 999,
636
- border: "none",
637
- background: accent,
638
- color: "#fff",
639
- display: "grid",
640
- placeItems: "center",
641
- boxShadow: "0 4px 16px rgba(0,0,0,.25)",
642
- cursor: "pointer",
643
- transition: "transform .12s ease"
644
- },
645
- children: /* @__PURE__ */ jsxs("svg", {
646
- width: "20",
647
- height: "20",
648
- viewBox: "0 0 24 24",
649
- fill: "none",
650
- stroke: "currentColor",
651
- strokeWidth: "2",
652
- strokeLinecap: "round",
653
- strokeLinejoin: "round",
654
- children: [/* @__PURE__ */ jsx("path", { d: "m3 11 18-5v12L3 14v-3z" }), /* @__PURE__ */ jsx("path", { d: "M11.6 16.8a3 3 0 1 1-5.8-1.6" })]
655
- })
656
- }), open && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
657
- className: "vibes-fb-backdrop",
658
- "data-vibes-feedback": "",
659
- onClick: () => !sending && setOpen(false),
660
- style: {
661
- position: "fixed",
662
- inset: 0,
663
- zIndex: 2147482900,
664
- background: "rgba(0,0,0,.4)"
665
- }
666
- }), /* @__PURE__ */ jsxs("div", {
667
- className: "vibes-fb-sheet",
668
- "data-vibes-feedback": "",
669
- role: "dialog",
670
- "aria-label": title,
671
- style: {
672
- position: "fixed",
673
- left: 0,
674
- right: 0,
675
- bottom: 0,
676
- zIndex: 2147483e3,
677
- margin: "0 auto",
678
- maxWidth: 480,
679
- background: "#fff",
680
- color: "#0b0b0f",
681
- borderTopLeftRadius: 20,
682
- borderTopRightRadius: 20,
683
- padding: "16px 16px calc(16px + env(safe-area-inset-bottom))",
684
- boxShadow: "0 -8px 40px rgba(0,0,0,.22)",
685
- fontFamily: "system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
686
- },
687
- children: [/* @__PURE__ */ jsx("div", { style: {
688
- width: 36,
689
- height: 4,
690
- borderRadius: 999,
691
- background: "#e2e2e8",
692
- margin: "0 auto 12px"
693
- } }), done ? /* @__PURE__ */ jsxs("div", {
694
- style: {
695
- textAlign: "center",
696
- padding: "20px 0 28px"
697
- },
698
- children: [/* @__PURE__ */ jsx("div", {
699
- style: {
700
- fontSize: 32,
701
- marginBottom: 6
702
- },
703
- children: "✓"
704
- }), /* @__PURE__ */ jsx("div", {
705
- style: { fontWeight: 600 },
706
- children: "Thanks — sent."
707
- })]
708
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [
709
- /* @__PURE__ */ jsx("div", {
710
- style: {
711
- fontWeight: 650,
712
- fontSize: 17,
713
- marginBottom: 10
714
- },
715
- children: title
716
- }),
717
- /* @__PURE__ */ jsx("textarea", {
718
- autoFocus: true,
719
- value: message,
720
- onChange: (e) => setMessage(e.target.value),
721
- placeholder: "What happened?",
722
- disabled: sending,
723
- style: {
724
- width: "100%",
725
- minHeight: 96,
726
- resize: "none",
727
- borderRadius: 12,
728
- border: "1px solid #e3e3ea",
729
- padding: 12,
730
- fontSize: 15,
731
- fontFamily: "inherit",
732
- outline: "none",
733
- boxSizing: "border-box"
734
- }
735
- }),
736
- shot && /* @__PURE__ */ jsx("img", {
737
- src: shot,
738
- alt: "screenshot",
739
- style: {
740
- marginTop: 10,
741
- width: "100%",
742
- maxHeight: 160,
743
- objectFit: "cover",
744
- borderRadius: 10,
745
- border: "1px solid #eee"
746
- }
747
- }),
748
- /* @__PURE__ */ jsxs("div", {
749
- style: {
750
- marginTop: 8,
751
- fontSize: 12,
752
- color: "#8a8a96"
753
- },
754
- children: [trace.length > 0 ? `${trace.length} trace event${trace.length === 1 ? "" : "s"} attached` : "No errors captured", capturer ? shot ? " · screenshot ready" : " · capturing screenshot…" : ""]
755
- }),
756
- error && /* @__PURE__ */ jsx("div", {
757
- style: {
758
- marginTop: 8,
759
- fontSize: 13,
760
- color: "#d4163c"
761
- },
762
- children: error
763
- }),
764
- /* @__PURE__ */ jsxs("div", {
765
- style: {
766
- display: "flex",
767
- gap: 8,
768
- marginTop: 14
769
- },
770
- children: [/* @__PURE__ */ jsx("button", {
771
- onClick: () => setOpen(false),
772
- disabled: sending,
773
- style: {
774
- flex: "0 0 auto",
775
- height: 44,
776
- padding: "0 16px",
777
- borderRadius: 12,
778
- border: "1px solid #e3e3ea",
779
- background: "#fff",
780
- color: "#0b0b0f",
781
- fontSize: 15,
782
- cursor: "pointer"
783
- },
784
- children: "Cancel"
785
- }), /* @__PURE__ */ jsx("button", {
786
- className: "vibes-fb-btn",
787
- onClick: send,
788
- disabled: !message.trim() || sending,
789
- style: {
790
- flex: 1,
791
- height: 44,
792
- borderRadius: 12,
793
- border: "none",
794
- background: message.trim() && !sending ? accent : "#c7c7d1",
795
- color: "#fff",
796
- fontSize: 15,
797
- fontWeight: 600,
798
- cursor: message.trim() && !sending ? "pointer" : "default",
799
- transition: "transform .12s ease, background .12s ease"
800
- },
801
- children: sending ? "Sending…" : "Send"
802
- })]
803
- })
804
- ] })]
805
- })] })] });
806
- }
807
- //#endregion
808
- export { installTrace as a, requestMotionPermission as c, getAuthContext as d, notifyAuthRequired as f, subscribeAuthRequired as h, getTrace as i, useFeedbackGesture as l, subscribeAuthChange as m, captureScreenshot as n, attachGestureListeners as o, setAuthContext as p, clearTrace as r, motionPermissionState as s, VibesFeedback as t, useUpload as u };