@lumen-stack/react 0.1.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 +218 -0
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +106 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +787 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# @lumen-stack/react
|
|
2
|
+
|
|
3
|
+
React SDK for Lumen — a floating feedback button + capture modal you drop
|
|
4
|
+
into any React app.
|
|
5
|
+
|
|
6
|
+
```tsx
|
|
7
|
+
import { LumenProvider } from "@lumen-stack/react";
|
|
8
|
+
import "@lumen-stack/react/styles.css";
|
|
9
|
+
|
|
10
|
+
<LumenProvider apiKey="lk_pub_…">
|
|
11
|
+
<App />
|
|
12
|
+
</LumenProvider>;
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The default behavior renders a bottom-right floating button that respects
|
|
16
|
+
iOS safe-areas, fades while the soft-keyboard is open, auto-detects fixed
|
|
17
|
+
bottom nav bars, and warns (in dev) if another element is occluding it.
|
|
18
|
+
|
|
19
|
+
When you don't pass a `trigger` prop, the SDK fetches the widget config
|
|
20
|
+
from your dashboard at `/api/v1/sdk/config` and renders that. So you can
|
|
21
|
+
toggle the widget on/off, change its position, or switch to the iOS-style
|
|
22
|
+
notch from Settings without redeploying the host app. Local `trigger`
|
|
23
|
+
prop always wins.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Avoiding collisions
|
|
28
|
+
|
|
29
|
+
The SDK already does the right thing in most apps — but if you have a
|
|
30
|
+
fixed bottom navigation, give it a selector and the button will sit
|
|
31
|
+
exactly above it without any host-side CSS:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<LumenProvider
|
|
35
|
+
apiKey="lk_pub_…"
|
|
36
|
+
trigger={{
|
|
37
|
+
kind: "floating",
|
|
38
|
+
avoid: "#bottom-nav",
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<App />
|
|
42
|
+
</LumenProvider>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Pass an array for multiple competing widgets:
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
trigger={{ kind: "floating", avoid: ["#bottom-nav", "[data-tab-bar]"] }}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If you need to **disable** the auto-detect heuristic entirely (the SDK
|
|
52
|
+
defaults to scanning for any full-width fixed element near the viewport
|
|
53
|
+
bottom), pass `avoid: false`:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
trigger={{ kind: "floating", avoid: false, offset: { y: 80 } }}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Other floating-trigger options
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
trigger: {
|
|
63
|
+
kind: "floating",
|
|
64
|
+
placement?: "br" | "bl" | "tr" | "tl", // default "br"
|
|
65
|
+
offset?: { x?: number; y?: number }, // default { x: 16, y: 16 }
|
|
66
|
+
safeArea?: boolean, // default true
|
|
67
|
+
avoid?: string | string[] | false, // default auto-detect
|
|
68
|
+
hideOnKeyboard?: boolean, // default true on touch devices
|
|
69
|
+
zIndex?: number, // default 2147483600
|
|
70
|
+
label?: string, // default "Feedback"
|
|
71
|
+
icon?: ReactNode,
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Edge notch (iOS-style)
|
|
78
|
+
|
|
79
|
+
A small tab anchored to a screen edge. Resting state is just the handle;
|
|
80
|
+
on hover/tap/drag-away-from-edge it expands and opens the modal. Top edge
|
|
81
|
+
slides down (Dynamic-Island feel); right edge is a vertical pull-tab.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<LumenProvider apiKey="…" trigger={{ kind: "notch", edge: "top" }} />
|
|
85
|
+
<LumenProvider apiKey="…" trigger={{ kind: "notch", edge: "right" }} />
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Edges: `"top" | "right" | "bottom" | "left"`. Top/bottom are
|
|
89
|
+
horizontal pills; left/right are vertical tabs with rotated text.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Headless trigger (render your own button)
|
|
94
|
+
|
|
95
|
+
When you want the button to live inside your own UI (sidebar item,
|
|
96
|
+
command palette, account menu), opt out of the floating trigger and call
|
|
97
|
+
`open()` from the `useLumen()` hook:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { LumenProvider, useLumen } from "@lumen-stack/react";
|
|
101
|
+
|
|
102
|
+
function MyFeedbackButton() {
|
|
103
|
+
const { open } = useLumen();
|
|
104
|
+
return <button onClick={open}>Send feedback</button>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
<LumenProvider apiKey="…" trigger={{ kind: "headless" }}>
|
|
108
|
+
<MyFeedbackButton />
|
|
109
|
+
</LumenProvider>;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The hook also exposes `close`, `isOpen`, `submit` (a pass-through to
|
|
113
|
+
`client.submit`), `isNativeShell` (true when running inside a React
|
|
114
|
+
Native WebView, Capacitor, etc.), and the underlying `client`.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Screenshot capture
|
|
119
|
+
|
|
120
|
+
The React SDK uses `@lumen-stack/core`'s capture pipeline. By default it tries
|
|
121
|
+
browser DOM capture first, records capture metadata, warns about weak
|
|
122
|
+
surfaces such as iframes/video/canvas, and lets the user retake with browser
|
|
123
|
+
screen permission or upload/paste a screenshot.
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
<LumenProvider
|
|
127
|
+
apiKey="lk_pub_..."
|
|
128
|
+
capture={{
|
|
129
|
+
mode: "auto", // "auto" | "dom" | "true-screen" | "manual" | "custom"
|
|
130
|
+
maxScale: 2,
|
|
131
|
+
onWarning: (warnings) => console.warn("[lumen capture]", warnings),
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
<App />
|
|
135
|
+
</LumenProvider>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Native wrappers can inject app-screen pixels directly:
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<LumenProvider
|
|
142
|
+
apiKey="lk_pub_..."
|
|
143
|
+
capture={{
|
|
144
|
+
mode: "custom",
|
|
145
|
+
provider: () => NativeBridge.captureLumenScreenshot(),
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
<App />
|
|
149
|
+
</LumenProvider>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The provider returns `{ blob, method, platform, viewport, pixelRatio,
|
|
153
|
+
warnings }`. See `docs/screenshot-ingestion.md` for raw API and native
|
|
154
|
+
bridge examples.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Inline mount
|
|
159
|
+
|
|
160
|
+
To mount the trigger into a specific DOM node — useful for design-system
|
|
161
|
+
toolbars or fixed sidebars — pass a ref or element:
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
const slot = useRef<HTMLDivElement>(null);
|
|
165
|
+
|
|
166
|
+
<>
|
|
167
|
+
<div ref={slot} />
|
|
168
|
+
<LumenProvider apiKey="…" trigger={{ kind: "inline", mount: slot }}>
|
|
169
|
+
<App />
|
|
170
|
+
</LumenProvider>
|
|
171
|
+
</>;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Theming
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
<LumenProvider apiKey="…" theme="dark" /> // forces dark
|
|
180
|
+
<LumenProvider apiKey="…" theme="auto" /> // follows prefers-color-scheme (default)
|
|
181
|
+
<LumenProvider apiKey="…" theme={{
|
|
182
|
+
background: "#0e0e10",
|
|
183
|
+
foreground: "#fafafa",
|
|
184
|
+
accent: "#7c3aed",
|
|
185
|
+
radius: "1rem",
|
|
186
|
+
}} />
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Hide on certain routes
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
<LumenProvider
|
|
195
|
+
apiKey="…"
|
|
196
|
+
hideOn={({ pathname }) =>
|
|
197
|
+
pathname.startsWith("/auth") || pathname.startsWith("/legal")
|
|
198
|
+
}
|
|
199
|
+
/>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The trigger hides; if the modal is already open it stays open until the
|
|
203
|
+
user (or your code) closes it.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Migration from v0.0.x
|
|
208
|
+
|
|
209
|
+
v0.1 is **purely additive**. Existing call sites work unchanged and
|
|
210
|
+
silently inherit the new collision/safe-area improvements:
|
|
211
|
+
|
|
212
|
+
```diff
|
|
213
|
+
- <LumenProvider apiKey="lk_pub_…" />
|
|
214
|
+
+ <LumenProvider apiKey="lk_pub_…" /> // same; now safe-area aware
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`floatingButton={false}` is still honored — it's equivalent to
|
|
218
|
+
`trigger={{ kind: "headless" }}`.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';var react=require('react'),sonner=require('sonner'),jsxRuntime=require('react/jsx-runtime'),reactDom=require('react-dom');var k=class extends Error{constructor(e,t,n){super(e),this.code=t,this.status=n,this.name="LumenError";}code;status},be=class extends k{constructor(){super("Origin not allowed. Add this origin in your Lumen project settings.","ORIGIN_NOT_ALLOWED",403),this.name="LumenOriginError";}},he=class extends k{constructor(e){super(`Rate limited \u2014 retry after ${e}s.`,"RATE_LIMITED",429),this.retryAfter=e,this.name="LumenRateLimitError";}retryAfter},X=class extends k{constructor(e){super(e,"NETWORK_ERROR"),this.name="LumenNetworkError";}},st="https://shakebugs.vercel.app",ut="/api/v1/sdk/submit",ct=new Set([502,503,504]),Ee=class{apiKey;apiUrl;user;constructor(e){if(!e.apiKey||!e.apiKey.startsWith("lk_pub_"))throw new k("Invalid Lumen apiKey \u2014 expected a key starting with `lk_pub_`.","INVALID_API_KEY");this.apiKey=e.apiKey,this.apiUrl=(e.apiUrl??st).replace(/\/$/,""),this.user=e.user;}async submit(e,t={}){let n=`${this.apiUrl}${ut}`,r=this.#e(e),a=null;for(let o=0;o<3;o++){o>0&&await mt(2**o*250);try{return await this.#t(n,r,t)}catch(i){if(a=i,i instanceof be||i instanceof he||i instanceof k&&typeof i.status=="number"&&!ct.has(i.status))throw i}}throw a instanceof Error?a:new X("Submit failed after 3 attempts.")}#e(e){let t=new FormData;return e.rawText&&t.append("rawText",e.rawText),e.category&&t.append("category",e.category),e.submitterEmail&&t.append("submitterEmail",e.submitterEmail),e.submitterFingerprint&&t.append("submitterFingerprint",e.submitterFingerprint),t.append("context",JSON.stringify(e.context)),e.screenshot&&t.append("screenshot",e.screenshot,dt(e.screenshot)),e.audio&&t.append("audio",e.audio,"audio"),e.audioDurationMs!=null&&t.append("audioDurationMs",String(e.audioDurationMs)),t}#t(e,t,n){return new Promise((r,a)=>{let o=new XMLHttpRequest;o.open("POST",e,true),o.setRequestHeader("X-Lumen-Api-Key",this.apiKey),o.responseType="text";let{onUploadProgress:i,signal:l}=n;i&&o.upload.addEventListener("progress",d=>{d.lengthComputable&&d.total>0&&i(Math.min(1,d.loaded/d.total));});let s=()=>{o.abort(),a(new X("Submit aborted."));};if(l){if(l.aborted){a(new X("Submit aborted."));return}l.addEventListener("abort",s,{once:true});}o.addEventListener("load",()=>{if(l&&l.removeEventListener("abort",s),o.status===403){a(new be);return}if(o.status===429){let d=Number(o.getResponseHeader("Retry-After"))||60;a(new he(d));return}if(o.status<200||o.status>=300){a(new k(pt(o.responseText)??`HTTP ${o.status}`,"HTTP_ERROR",o.status));return}try{let d=JSON.parse(o.responseText);r(d);}catch{a(new k("Malformed response from server.","BAD_RESPONSE"));}}),o.addEventListener("error",()=>{l&&l.removeEventListener("abort",s),a(new X("Network error during submit."));}),o.send(t);})}};function dt(e){return `screenshot.${e.type==="image/jpeg"?"jpg":e.type==="image/webp"?"webp":"png"}`}function mt(e){return new Promise(t=>setTimeout(t,e))}function pt(e){try{let t=JSON.parse(e);return t.error??t.message??null}catch{return e||null}}var ft=2,$=8*1024*1024;async function ee(e={}){Te();let t=e.mode??"auto";if(e.provider&&(t==="auto"||t==="custom"))try{return bt(await e.provider(),e)}catch(n){if(t==="custom")throw n;e.onWarning?.(["Custom capture provider failed; falling back to browser DOM capture."]);}if(t==="manual")throw new k("Manual screenshot upload is required for this capture mode.","MANUAL_CAPTURE_REQUIRED");if(t==="true-screen")return ye(e);if(t==="dom")return we(e);if(t==="custom")throw new k("`capture.provider` is required when capture mode is `custom`.","CAPTURE_PROVIDER_REQUIRED");try{return await we(e)}catch(n){if(Le()){let r=await ye(e);return r.warnings=K(["DOM screenshot failed; used browser screen permission fallback.",...r.warnings]),e.onWarning?.(r.warnings),r}throw n}}function Ce(e,t=[]){Te();let n=G();return {blob:e,method:"manual-upload",platform:"web",viewport:n,pixelRatio:window.devicePixelRatio??1,warnings:K(t)}}async function we(e){let t=await gt(),n=e.target??document.documentElement,r=G(),a=Math.max(1,Math.min(window.devicePixelRatio??1,e.maxScale??ft)),o=ht(n),i=await t(n,{backgroundColor:null,logging:false,useCORS:true,allowTaint:false,width:r.width,height:r.height,windowWidth:r.width,windowHeight:r.height,scrollX:window.scrollX,scrollY:window.scrollY,x:window.scrollX,y:window.scrollY,scale:a,ignoreElements:s=>s instanceof HTMLElement&&(s.dataset.lumenCaptureIgnore==="true"||!!s.closest("[data-lumen-capture-ignore='true']"))}),l={blob:await ke(i,o),method:"web-dom",platform:"web",viewport:r,pixelRatio:a,warnings:K(o)};return l.warnings.length>0&&e.onWarning?.(l.warnings),l}async function gt(){let{default:e}=await import('html2canvas-pro');return e}async function ye(e){if(!Le())throw new k("Browser screen capture is unavailable in this environment.","DISPLAY_MEDIA_UNAVAILABLE");let t=await navigator.mediaDevices.getDisplayMedia({video:true,audio:false});try{let n=document.createElement("video");n.muted=!0,n.playsInline=!0,n.srcObject=t,await wt(n);let r=document.createElement("canvas");r.width=n.videoWidth,r.height=n.videoHeight;let a=r.getContext("2d");if(!a)throw new k("Could not create a drawing context for screen capture.","CANVAS_CONTEXT_UNAVAILABLE");a.drawImage(n,0,0,r.width,r.height);let o=G(),i=["Browser screen capture uses the window or tab selected by the user."],l={blob:await ke(r,i),method:"web-display-media",platform:"web",viewport:o,pixelRatio:o.width>0?r.width/o.width:void 0,warnings:K(i)};return e.onWarning?.(l.warnings),l}finally{for(let n of t.getTracks())n.stop();}}function bt(e,t){let n=G(),r={...e,method:e.method??"custom",platform:e.platform??"custom",viewport:e.viewport??n,pixelRatio:e.pixelRatio??window.devicePixelRatio??1,warnings:K(e.warnings??[])};return r.warnings.length>0&&t.onWarning?.(r.warnings),r}function ht(e){let t=new Set;e.querySelector("iframe")&&t.add("Embedded iframes may be blank or incomplete in DOM capture."),e.querySelector("video")&&t.add("Video frames may be blank or stale in DOM capture."),e.querySelector("canvas")&&t.add("Canvas/WebGL content may be blank if it is cross-origin tainted."),document.fonts&&document.fonts.status!=="loaded"&&t.add("Web fonts were still loading when the screenshot was captured.");let n=0;for(let r of Array.from(e.querySelectorAll("img"))){let a=r.currentSrc||r.src;if(a)try{new URL(a,window.location.href).origin!==window.location.origin&&!r.crossOrigin&&(n+=1);}catch{}}return n>0&&t.add(`${n} cross-origin image${n===1?"":"s"} without CORS may be omitted from the screenshot.`),Array.from(t)}async function ke(e,t){let n=await ve(e,"image/png",.92);if(n.size<=$)return n;t.push("Screenshot exceeded the upload cap and was compressed.");let r=e,a=0;for(;n.size>$&&a<6;){let o=Math.max(.35,Math.min(.95,Math.sqrt($/n.size)*.9)),i=document.createElement("canvas");i.width=Math.max(1,Math.round(r.width*o)),i.height=Math.max(1,Math.round(r.height*o));let l=i.getContext("2d");if(!l)return n;l.drawImage(r,0,0,i.width,i.height),r=i;for(let s of [.86,.74,.62,.5])if(n=await ve(r,"image/jpeg",s),n.size<=$)return n;if(a+=1,i.width<480||i.height<320)break}return n.size>$&&t.push("Screenshot remained large after compression; upload may be rejected."),n}function ve(e,t,n){return new Promise((r,a)=>{e.toBlob(o=>{if(!o){a(new k("Could not encode screenshot.","ENCODE_FAILED"));return}r(o);},t,n);})}function G(){let e=window.visualViewport;return {width:Math.round(e?.width??window.innerWidth),height:Math.round(e?.height??window.innerHeight)}}function Le(){return !!navigator.mediaDevices?.getDisplayMedia}async function wt(e){await new Promise((r,a)=>{let o=()=>r(),i=()=>a(new k("Could not read screen capture video.","VIDEO_FAILED"));e.addEventListener("loadedmetadata",o,{once:true}),e.addEventListener("error",i,{once:true}),e.play().catch(i);});let t=performance.now(),n=1500;for(;e.videoWidth===0||e.videoHeight===0;){if(performance.now()-t>n)throw new k("Screen capture did not produce a frame in time.","VIDEO_TIMEOUT");await new Promise(r=>requestAnimationFrame(()=>r()));}}function Te(){if(typeof window>"u"||typeof document>"u")throw new k("Screenshot capture can only run in the browser.","INVALID_ENV")}function K(e){return Array.from(new Set(e.filter(Boolean)))}function Re(e){return typeof window>"u"?{url:"",userAgent:"",viewport:{width:0,height:0},capture:xe(e),consoleLog:[],networkLog:[]}:{url:window.location.href,userAgent:navigator.userAgent,viewport:{width:window.innerWidth,height:window.innerHeight},capture:xe(e),consoleLog:[],networkLog:[]}}function xe(e){if(e)return {method:e.method,platform:e.platform,viewport:e.viewport,pixelRatio:e.pixelRatio,warnings:e.warnings}}async function Ae(e=60){if(typeof window>"u"||!navigator.mediaDevices)throw new k("Audio recording requires a browser with MediaDevices.","INVALID_ENV");let t;try{t=await navigator.mediaDevices.getUserMedia({audio:!0});}catch{throw new k("Microphone access denied or unavailable.","MIC_DENIED")}let n=["audio/webm;codecs=opus","audio/webm","audio/mp4","audio/ogg;codecs=opus"].find(s=>MediaRecorder.isTypeSupported(s))??"",r=new MediaRecorder(t,n?{mimeType:n}:void 0),a=[],o=performance.now(),i=null;r.addEventListener("dataavailable",s=>{s.data&&s.data.size>0&&a.push(s.data);}),r.start(),i=window.setTimeout(()=>{r.state!=="inactive"&&r.stop();},e*1e3);function l(){i!==null&&window.clearTimeout(i),i=null;for(let s of t.getTracks())s.stop();}return {stream:t,cancel(){try{r.state!=="inactive"&&r.stop();}catch{}l();},stop(){return new Promise((s,d)=>{if(r.state==="inactive"){l(),d(new k("Recorder already stopped.","RECORDER_STOPPED"));return}r.addEventListener("stop",()=>{let f=performance.now()-o,g=r.mimeType||n||"audio/webm",C=new Blob(a,{type:g});l(),s({blob:C,durationMs:f,mimeType:g});},{once:true}),r.stop();})}}}var te=react.createContext(null);function q(){let e=react.useContext(te);if(!e)throw new Error("useLumen() must be used inside <LumenProvider>.");return e}function Pe({screenshot:e,tool:t,color:n="rgb(239, 68, 68)",strokeWidth:r=4,onDrawingChange:a,ref:o}){let i=react.useRef(null),l=react.useRef(null),s=react.useRef(null),[d,f]=react.useState(null),[g,C]=react.useState([]),y=react.useRef(null),h=react.useRef(false),v=react.useRef(0),L=react.useRef(false);react.useEffect(()=>{let c=false,p=null;return (async()=>{try{let b=await createImageBitmap(e);if(c){b.close?.();return}p=b,f(b);}catch{}})(),()=>{c=true,p?.close?.();}},[e]),react.useEffect(()=>{if(!d)return;let c=i.current,p=l.current;if(!c||!p)return;c.width=d.width,c.height=d.height,p.width=d.width,p.height=d.height;let b=c.getContext("2d");b&&(b.imageSmoothingEnabled=false,b.drawImage(d,0,0));},[d]),react.useEffect(()=>{let c=l.current;c&&J(c,g,y.current);},[g]),react.useImperativeHandle(o,()=>({hasAnnotations:()=>g.length>0,reset:()=>C([]),undo:()=>C(c=>c.length===0?c:c.slice(0,-1)),async flatten(){if(!d)return e;let c=document.createElement("canvas");c.width=d.width,c.height=d.height;let p=c.getContext("2d");if(!p)return e;p.imageSmoothingEnabled=false,p.drawImage(d,0,0);for(let b of g)re(p,b);return await new Promise(b=>{c.toBlob(P=>{b(P??e);},"image/png",.92);})}}),[g,d,e]);function T(c){let p=l.current;if(!p)return {x:0,y:0};let b=p.getBoundingClientRect(),P=p.width/Math.max(b.width,1),U=p.height/Math.max(b.height,1);return {x:(c.clientX-b.left)*P,y:(c.clientY-b.top)*U}}function M(){L.current||(L.current=true,requestAnimationFrame(()=>{L.current=false;let c=l.current;c&&J(c,g,y.current);}));}function E(c){if(c.button!==void 0&&c.button!==0)return;c.currentTarget.setPointerCapture(c.pointerId),h.current=true,a?.(true);let p=T(c);t==="arrow"?y.current={kind:"arrow",from:p,to:p,color:n,width:r}:t==="rect"?y.current={kind:"rect",from:p,to:p,color:n,width:r}:y.current={kind:"freehand",points:[p],color:n,width:r},v.current=performance.now(),M();}function R(c){if(!h.current||!y.current)return;let p=T(c),b=y.current;if(b.kind==="arrow"||b.kind==="rect")b.to=p;else {let P=b.points[b.points.length-1],U=P?(P.x-p.x)**2+(P.y-p.y)**2:1/0,D=performance.now(),W=D-v.current;(U>16||W>16)&&(b.points.push(p),v.current=D);}M();}function S(){if(!h.current)return;h.current=false,a?.(false);let c=y.current;if(y.current=null,!!c){if(c.kind==="arrow"||c.kind==="rect"){let p=c.to.x-c.from.x,b=c.to.y-c.from.y;if(p*p+b*b<16){let P=l.current;P&&J(P,g,null);return}}else if(c.points.length<2){let p=l.current;p&&J(p,g,null);return}C(p=>[...p,c].slice(-50));}}return jsxRuntime.jsxs("div",{ref:s,className:"lumen-annotate-frame",children:[jsxRuntime.jsx("canvas",{ref:i}),jsxRuntime.jsx("canvas",{ref:l,className:"lumen-annotate-overlay",onPointerDown:E,onPointerMove:R,onPointerUp:S,onPointerCancel:S})]})}function J(e,t,n){let r=e.getContext("2d");if(r){r.clearRect(0,0,e.width,e.height);for(let a of t)re(r,a);n&&re(r,n);}}function re(e,t){if(e.save(),e.strokeStyle=t.color,e.fillStyle=t.color,e.lineWidth=t.width,e.lineCap="round",e.lineJoin="round",t.kind==="rect"){let n=Math.min(t.from.x,t.to.x),r=Math.min(t.from.y,t.to.y),a=Math.abs(t.to.x-t.from.x),o=Math.abs(t.to.y-t.from.y);e.strokeRect(n,r,a,o);}else if(t.kind==="freehand")Ct(e,t.points);else {let{from:n,to:r,width:a}=t;e.beginPath(),e.moveTo(n.x,n.y),e.lineTo(r.x,r.y),e.stroke();let o=Math.atan2(r.y-n.y,r.x-n.x),i=Math.max(12,a*4);e.beginPath(),e.moveTo(r.x,r.y),e.lineTo(r.x-i*Math.cos(o-Math.PI/7),r.y-i*Math.sin(o-Math.PI/7)),e.lineTo(r.x-i*Math.cos(o+Math.PI/7),r.y-i*Math.sin(o+Math.PI/7)),e.closePath(),e.fill();}e.restore();}function Ct(e,t){if(t.length===0)return;e.beginPath();let n=t[0];if(!n)return;if(e.moveTo(n.x,n.y),t.length===1){e.arc(n.x,n.y,e.lineWidth/2,0,Math.PI*2),e.fill();return}if(t.length===2){let a=t[1];e.lineTo(a.x,a.y),e.stroke();return}for(let a=1;a<t.length-1;a++){let o=t[a],i=t[a+1],l=(o.x+i.x)/2,s=(o.y+i.y)/2;e.quadraticCurveTo(o.x,o.y,l,s);}let r=t[t.length-1];e.lineTo(r.x,r.y),e.stroke();}var oe=60,Ne=[{label:"Red",value:"rgb(239, 68, 68)"},{label:"Amber",value:"rgb(245, 158, 11)"},{label:"Blue",value:"rgb(59, 130, 246)"},{label:"Green",value:"rgb(34, 197, 94)"},{label:"Neutral",value:"rgb(244, 244, 245)"}],Ie=[{label:"Small",value:2,dot:6},{label:"Medium",value:4,dot:9},{label:"Large",value:6,dot:12}],Lt=[{value:"bug",label:"Bug"},{value:"feature",label:"Idea"},{value:"other",label:"Other"}];function le(){let{client:e,isOpen:t,closeCapture:n,user:r,capture:a}=q(),[o,i]=react.useState({kind:"idle"}),[l,s]=react.useState("arrow"),[d,f]=react.useState(Ne[0].value),[g,C]=react.useState(Ie[1].value),[y,h]=react.useState(""),[v,L]=react.useState("bug"),[T,M]=react.useState(r?.email??""),[E,R]=react.useState(null),[S,c]=react.useState(null),[p,b]=react.useState(0),[P,U]=react.useState(false),D=react.useRef(null),W=react.useRef(null),me=react.useRef(null),V=react.useRef(false);if(react.useEffect(()=>{if(t)return V.current=false,i({kind:"capturing"}),h(""),c(null),b(0),(async()=>{if(a?.mode==="manual"){V.current||i({kind:"manual"});return}try{let u=await ee({...a,target:a?.target??document.documentElement});if(V.current)return;ge(u),i({kind:"ready",capture:u});}catch(u){if(V.current)return;i({kind:"manual",error:u instanceof Error?u.message:"Automatic screenshot capture was unavailable."});}})(),()=>{V.current=true;}},[t,a]),react.useEffect(()=>{if(!t)return;me.current=document.activeElement??null;let u=document.documentElement,w=u.style.overflow,N=u.style.paddingRight,A=window.innerWidth-u.clientWidth;u.style.overflow="hidden",A>0&&(u.style.paddingRight=`${A}px`);let O=false,H=()=>{if(!O){O=true,u.style.overflow=w,u.style.paddingRight=N;try{me.current?.focus?.();}catch{}}};return window.addEventListener("pagehide",H),()=>{window.removeEventListener("pagehide",H),H();}},[t]),react.useEffect(()=>{if(!t)return;function u(w){if(w.key==="Escape"){w.preventDefault(),n();return}if(w.key!=="Tab")return;let N=W.current;if(!N)return;let A=Oe(N);if(A.length===0)return;let O=A[0],H=A[A.length-1],Q=document.activeElement;w.shiftKey&&(Q===O||!N.contains(Q))?(w.preventDefault(),H.focus()):!w.shiftKey&&Q===H&&(w.preventDefault(),O.focus());}return document.addEventListener("keydown",u),()=>document.removeEventListener("keydown",u)},[t,n]),react.useEffect(()=>{if(!t||o.kind!=="ready"&&o.kind!=="manual")return;let u=W.current;if(!u)return;let w=Oe(u);(u.querySelector("textarea.lumen-input")??w[0])?.focus();},[t,o.kind]),react.useEffect(()=>{if(!t||o.kind!=="manual")return;function u(w){let O=Array.from(w.clipboardData?.items??[]).find(H=>H.type.startsWith("image/"))?.getAsFile();O&&(w.preventDefault(),fe(O));}return window.addEventListener("paste",u),()=>window.removeEventListener("paste",u)},[t,o.kind]),react.useEffect(()=>{if(!E)return;let u=Date.now(),w=window.setInterval(()=>{let N=Math.floor((Date.now()-u)/1e3);b(N),N>=oe&&pe();},250);return ()=>window.clearInterval(w)},[E]),!t)return null;async function rt(){try{let u=await Ae(oe);R(u);}catch(u){sonner.toast.error(u instanceof Error?u.message:"Microphone unavailable");}}async function pe(){if(E)try{let u=await E.stop();c({blob:u.blob,durationMs:u.durationMs});}catch(u){sonner.toast.error(u instanceof Error?u.message:"Could not stop recording");}finally{R(null);}}function ot(){E?.cancel(),R(null),c(null),b(0);}function fe(u){if(!u.type.startsWith("image/")){sonner.toast.error("Choose a PNG, JPEG, or WebP screenshot.");return}let w=Ce(u,["Manual screenshot upload used."]);i({kind:"ready",capture:w});}async function at(){i({kind:"capturing"});try{let u=await ee({...a,mode:"true-screen",target:a?.target??document.documentElement});ge(u),i({kind:"ready",capture:u});}catch(u){i({kind:"manual",error:u instanceof Error?u.message:"Browser screen capture was unavailable."});}}function ge(u){u.warnings.length!==0&&sonner.toast.warning("Screenshot captured, but iframe, video, canvas, or cross-origin content may be incomplete.");}async function it(){if(o.kind!=="ready")return;let u=y.trim();if(!S&&u.length===0){sonner.toast.error("Add a voice note or write a description.");return}let{capture:w}=o,N=w.blob;i({kind:"uploading",capture:w,progress:0});try{let A=await D.current?.flatten()??N,O=await e.submit({rawText:u.length>0?u:void 0,category:v,submitterEmail:T.trim()||void 0,screenshot:A,audio:S?.blob,audioDurationMs:S?.durationMs,context:Re(w)},{onUploadProgress:H=>{i({kind:"uploading",capture:w,progress:Math.min(.95,H)});}});i({kind:"uploading",capture:w,progress:1}),sonner.toast.success("Feedback sent \u2014 thank you."),O.id,n();}catch(A){sonner.toast.error(A instanceof Error?A.message:"Could not submit feedback"),i({kind:"ready",capture:w});}}let Y=o.kind==="uploading"?Math.round(o.progress*100):0,j=o.kind==="uploading";function lt(u){u.target===u.currentTarget&&n();}return jsxRuntime.jsx("div",{role:"dialog","aria-modal":"true","aria-label":"Send feedback",className:"lumen-modal-backdrop","data-lumen-capture-ignore":"true",onMouseDown:lt,children:jsxRuntime.jsxs("div",{ref:W,className:"lumen-modal","data-lumen-drawing":P?"true":void 0,children:[jsxRuntime.jsx(Tt,{onDismiss:n}),jsxRuntime.jsxs("header",{className:"lumen-modal-header",children:[jsxRuntime.jsxs("h2",{className:"lumen-modal-title",children:[jsxRuntime.jsx("span",{className:"lumen-modal-title-dot","aria-hidden":"true"}),"Send feedback"]}),jsxRuntime.jsx("button",{type:"button",onClick:n,className:"lumen-icon-btn","aria-label":"Close",children:"\xD7"})]}),jsxRuntime.jsxs("div",{className:"lumen-modal-body",children:[o.kind==="capturing"?jsxRuntime.jsxs("p",{className:"lumen-status",children:[jsxRuntime.jsx("span",{className:"lumen-spinner","aria-hidden":"true"}),"Capturing screenshot\u2026"]}):null,o.kind==="manual"?jsxRuntime.jsxs("div",{className:"lumen-manual-capture",children:[jsxRuntime.jsx("p",{className:"lumen-status",children:o.error?"Automatic screenshot capture was unavailable. Upload or paste a screenshot to continue.":"Upload or paste a screenshot to continue."}),jsxRuntime.jsxs("label",{className:"lumen-manual-drop",children:[jsxRuntime.jsx("span",{children:"Choose or paste a screenshot"}),jsxRuntime.jsx("input",{type:"file",accept:"image/png,image/jpeg,image/webp",onChange:u=>{let w=u.currentTarget.files?.[0];w&&fe(w),u.currentTarget.value="";}})]})]}):null,o.kind==="ready"||o.kind==="uploading"?jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsxs("div",{className:"lumen-toolbar",children:[jsxRuntime.jsxs("div",{className:"lumen-segmented",role:"group","aria-label":"Annotation tool",children:[jsxRuntime.jsx(ae,{label:"Arrow",active:l==="arrow",onClick:()=>s("arrow")}),jsxRuntime.jsx(ae,{label:"Box",active:l==="rect",onClick:()=>s("rect")}),jsxRuntime.jsx(ae,{label:"Draw",active:l==="freehand",onClick:()=>s("freehand")})]}),jsxRuntime.jsx("span",{className:"lumen-toolbar-sep"}),jsxRuntime.jsx("div",{className:"lumen-swatches",role:"group","aria-label":"Color",children:Ne.map(u=>jsxRuntime.jsx("button",{type:"button",className:"lumen-swatch",style:{background:u.value},"aria-label":u.label,"aria-pressed":d===u.value,onClick:()=>f(u.value)},u.value))}),jsxRuntime.jsx("div",{className:"lumen-stroke-sizes",role:"group","aria-label":"Stroke width",children:Ie.map(u=>jsxRuntime.jsx("button",{type:"button",className:"lumen-stroke-size","aria-label":u.label,"aria-pressed":g===u.value,onClick:()=>C(u.value),children:jsxRuntime.jsx("span",{className:"lumen-stroke-size-dot",style:{width:u.dot,height:u.dot}})},u.value))}),jsxRuntime.jsx("span",{className:"lumen-toolbar-spacer"}),jsxRuntime.jsx("button",{type:"button",className:"lumen-btn-ghost",onClick:()=>D.current?.undo(),title:"Undo",children:"Undo"}),jsxRuntime.jsx("button",{type:"button",className:"lumen-btn-ghost",onClick:()=>D.current?.reset(),title:"Clear all annotations",children:"Clear"})]}),jsxRuntime.jsx("div",{className:"lumen-annotate",children:jsxRuntime.jsx(Pe,{ref:D,screenshot:o.capture.blob,tool:l,color:d,strokeWidth:g,onDrawingChange:U})}),jsxRuntime.jsxs("div",{className:"lumen-form",children:[jsxRuntime.jsxs("div",{className:"lumen-label",children:[jsxRuntime.jsx("span",{children:"Category"}),jsxRuntime.jsx("div",{className:"lumen-category",role:"group","aria-label":"Feedback category",children:Lt.map(u=>jsxRuntime.jsx("button",{type:"button",className:"lumen-category-opt","aria-pressed":v===u.value,onClick:()=>L(u.value),children:u.label},u.value))})]}),jsxRuntime.jsxs("label",{className:"lumen-label",children:[jsxRuntime.jsxs("span",{children:["Description ",S?"(optional with audio)":""]}),jsxRuntime.jsx("textarea",{value:y,onChange:u=>h(u.target.value),rows:3,placeholder:"What happened?",className:"lumen-input"})]}),jsxRuntime.jsxs("label",{className:"lumen-label",children:[jsxRuntime.jsx("span",{children:"Your email (optional)"}),jsxRuntime.jsx("input",{type:"email",value:T,onChange:u=>M(u.target.value),placeholder:"you@example.com",className:"lumen-input"})]}),jsxRuntime.jsxs("div",{className:"lumen-audio-row",children:[!E&&!S?jsxRuntime.jsx("button",{type:"button",onClick:rt,className:"lumen-btn-ghost",children:"Record voice note"}):null,E?jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsxs("span",{className:"lumen-recording",children:[jsxRuntime.jsx("span",{className:"lumen-rec-dot","aria-hidden":"true"}),p,"s / ",oe,"s"]}),jsxRuntime.jsx("button",{type:"button",onClick:pe,className:"lumen-btn-ghost",children:"Stop"}),jsxRuntime.jsx("button",{type:"button",onClick:ot,className:"lumen-btn-ghost",children:"Cancel"})]}):null,S&&!E?jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsxs("span",{className:"lumen-audio-meta",children:["Voice note \xB7 ",Math.round(S.durationMs/1e3),"s"]}),jsxRuntime.jsx("button",{type:"button",onClick:()=>c(null),className:"lumen-btn-ghost",children:"Remove"})]}):null,jsxRuntime.jsx("span",{className:"lumen-toolbar-spacer"}),jsxRuntime.jsx("button",{type:"button",className:"lumen-btn-ghost",onClick:at,title:"Use the browser's screen capture",children:"Screen capture"}),jsxRuntime.jsx("button",{type:"button",className:"lumen-btn-ghost",onClick:()=>i({kind:"manual"}),title:"Upload a screenshot file",children:"Upload"})]})]})]}):null]}),o.kind==="ready"||o.kind==="uploading"?jsxRuntime.jsxs("footer",{className:"lumen-modal-footer",children:[j?jsxRuntime.jsx("div",{className:"lumen-progress",role:"progressbar","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Y,children:jsxRuntime.jsx("div",{className:"lumen-progress-fill",style:{width:`${Y}%`}})}):null,jsxRuntime.jsxs("div",{className:"lumen-modal-actions",children:[jsxRuntime.jsx("button",{type:"button",onClick:n,className:"lumen-btn-ghost",disabled:j,children:"Cancel"}),jsxRuntime.jsx("button",{type:"button",onClick:it,className:"lumen-btn-primary",disabled:j,children:j?Y<95?`Uploading ${Y}%\u2026`:"Almost done\u2026":"Send feedback"})]})]}):null]})})}function ae({label:e,active:t,onClick:n}){return jsxRuntime.jsx("button",{type:"button",onClick:n,className:"lumen-tool","aria-pressed":t,children:e})}function Tt({onDismiss:e}){let t=react.useRef(null),n=react.useRef(0),r=react.useRef(null);function a(l){l.currentTarget.setPointerCapture(l.pointerId),t.current=l.clientY,n.current=performance.now(),r.current=l.currentTarget.parentElement;}function o(l){if(t.current==null||!r.current)return;let s=Math.max(0,l.clientY-t.current);r.current.style.transform=`translateY(${s}px)`;}function i(l){if(t.current==null||!r.current)return;let s=Math.max(0,l.clientY-t.current),d=Math.max(1,performance.now()-n.current),f=s/d;r.current.style.transform="",r.current.style.transition="",t.current=null,(s>80||f>.6)&&e();}return jsxRuntime.jsx("div",{className:"lumen-modal-grabber","aria-hidden":"true",onPointerDown:a,onPointerMove:o,onPointerUp:i,onPointerCancel:i})}var Rt='a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';function Oe(e){return Array.from(e.querySelectorAll(Rt)).filter(t=>!t.hasAttribute("disabled")&&t.offsetParent!==null)}function He(){if(typeof document>"u"||typeof window>"u")return 0;let e=window.innerWidth,t=window.innerHeight;if(e===0||t===0)return 0;let n=t-4,r=[Math.round(e*.1),Math.round(e*.5),Math.round(e*.9)],a=new Set,o=0;for(let i of r){let l=document.elementsFromPoint(i,n);for(let s of l){if(a.has(s))continue;a.add(s);let d=window.getComputedStyle(s);if(d.position!=="fixed"&&d.position!=="sticky")continue;let f=s.getBoundingClientRect();f.bottom<t-8||f.bottom>t+8||f.width<e*.6||f.height>o&&(o=f.height);}}return o}function se(e,t){if(typeof document>"u"||typeof window>"u")return 0;let n=window.innerWidth,r=window.innerHeight,a=0;for(let o of e){let i;try{i=document.querySelectorAll(o);}catch{continue}for(let l of Array.from(i)){let s=l.getBoundingClientRect();if(s.height===0||s.width===0)continue;let d=0;switch(t){case "bottom":if(s.bottom<r-8)break;d=Math.max(0,r-s.top);break;case "top":if(s.top>8)break;d=Math.max(0,s.bottom);break;case "left":if(s.left>8)break;d=Math.max(0,s.right);break;case "right":if(s.right<n-8)break;d=Math.max(0,n-s.left);break}d>a&&(a=d);}}return a}function De(e){let[t,n]=react.useState(()=>({bottom:e.offset.y,right:e.offset.x,left:e.offset.x,top:e.offset.y})),r=react.useRef(t),a=Pt(e.avoid),o=e.placement,i=e.offset.x,l=e.offset.y;return react.useEffect(()=>{if(typeof window>"u")return;let s=0,d=false,f=()=>{if(s=0,d)return;let v=o[0]==="b"?"bottom":"top",L=o[1]==="r"?"right":"left",T=0,M=0;e.avoid==="auto"&&v==="bottom"?T=He():Array.isArray(e.avoid)&&(T=se(e.avoid,v),M=se(e.avoid,L));let E={bottom:v==="bottom"?l+T:0,top:v==="top"?l+T:0,right:L==="right"?i+M:0,left:L==="left"?i+M:0},R=r.current;(E.bottom!==R.bottom||E.top!==R.top||E.right!==R.right||E.left!==R.left)&&(r.current=E,n(E));},g=()=>{s||(s=window.requestAnimationFrame(f));};g();let C=typeof ResizeObserver<"u"?new ResizeObserver(g):null;C?.observe(document.documentElement);let y=typeof MutationObserver<"u"?new MutationObserver(g):null;y?.observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:["class","style","hidden"]}),window.addEventListener("resize",g);let h=window.visualViewport;return h?.addEventListener("resize",g),h?.addEventListener("scroll",g),()=>{d=true,s&&window.cancelAnimationFrame(s),C?.disconnect(),y?.disconnect(),window.removeEventListener("resize",g),h?.removeEventListener("resize",g),h?.removeEventListener("scroll",g);}},[o,i,l,a]),t}function Pt(e){return e===false?"false":e==="auto"?"auto":e.join("|")}function Fe(e){let[t,n]=react.useState(false);return react.useEffect(()=>{if(!e){n(false);return}if(typeof window>"u")return;let r=window.visualViewport;if(!r||typeof window.matchMedia!="function"||!window.matchMedia("(pointer: coarse)").matches)return;let a=100,o=0,i=()=>{o=0;let s=window.innerHeight-r.height;n(s>a);},l=()=>{o||(o=window.requestAnimationFrame(i));};return l(),r.addEventListener("resize",l),r.addEventListener("scroll",l),()=>{o&&window.cancelAnimationFrame(o),r.removeEventListener("resize",l),r.removeEventListener("scroll",l);}},[e]),t}function Ue({config:e,portalTarget:t,hidden:n,onClick:r}){let[a,o]=react.useState(null),i=react.useRef(null),l=De(e),s=Fe(e.hideOnKeyboard);if(react.useEffect(()=>{typeof document>"u"||o(t??document.body);},[t]),Dt(i,!n&&!s),!a)return null;let d=e.placement,f=d[0]==="b"?"bottom":"top",g=d[1]==="r"?"right":"left",C=f==="bottom"?l.bottom:l.top,y=g==="right"?l.right:l.left,h={position:"fixed",[f]:e.safeArea?`calc(${C}px + env(safe-area-inset-${f}, 0px))`:`${C}px`,[g]:e.safeArea?`calc(${y}px + env(safe-area-inset-${g}, 0px))`:`${y}px`,zIndex:e.zIndex,opacity:n||s?0:1,pointerEvents:n||s?"none":"auto",transition:"opacity 160ms ease"};return reactDom.createPortal(jsxRuntime.jsxs("button",{ref:i,type:"button",className:"lumen-btn lumen-btn-primary lumen-btn-floating",style:h,"aria-label":e.label,"aria-hidden":n||s?true:void 0,tabIndex:n||s?-1:0,onClick:r,"data-lumen-trigger":"",children:[e.icon,jsxRuntime.jsx("span",{children:e.label})]}),a)}function Dt(e,t){let n=react.useRef(false);react.useEffect(()=>{if(process.env.NODE_ENV==="production"||!t||n.current||typeof window>"u")return;let r=window.setTimeout(()=>{let a=e.current;if(!a)return;let o=a.getBoundingClientRect();if(o.width===0||o.height===0)return;let i=o.left+o.width/2,l=o.top+o.height/2,s=document.elementsFromPoint(i,l),d=Be(window.getComputedStyle(a).zIndex);for(let f of s){if(f===a||a.contains(f))continue;if(Be(window.getComputedStyle(f).zIndex)>d){n.current=true,console.warn("[lumen] trigger is occluded by",f);break}}},250);return ()=>window.clearTimeout(r)},[t,e]);}function Be(e){let t=parseInt(e,10);return Number.isNaN(t)?0:t}function We({mount:e,label:t,icon:n,onClick:r}){let[a,o]=react.useState(null);return react.useEffect(()=>{if(typeof document>"u")return;let i=e instanceof HTMLElement?e:e.current??null;o(i);},[e]),a?reactDom.createPortal(jsxRuntime.jsxs("button",{type:"button",className:"lumen-btn lumen-btn-primary",onClick:r,"aria-label":t,"data-lumen-trigger":"",children:[n,jsxRuntime.jsx("span",{children:t})]}),a):null}function Ke({config:e,portalTarget:t,hidden:n,onClick:r}){let[a,o]=react.useState(null),[i,l]=react.useState(false),s=react.useRef(null);if(react.useEffect(()=>{typeof document>"u"||o(t??document.body);},[t]),!a)return null;let d=`lumen-notch lumen-notch-${e.edge}${i?" lumen-notch-expanded":""}`;function f(h){s.current={x:h.clientX,y:h.clientY},l(true),h.currentTarget.setPointerCapture(h.pointerId);}function g(h){let v=s.current;if(!v)return;let L=h.clientX-v.x,T=h.clientY-v.y;(e.edge==="top"&&T>16||e.edge==="bottom"&&T<-16||e.edge==="right"&&L<-16||e.edge==="left"&&L>16)&&(s.current=null,r());}function C(){s.current=null,l(false);}function y(){s.current=null,l(false);}return reactDom.createPortal(jsxRuntime.jsxs("button",{type:"button",className:d,style:{zIndex:e.zIndex,opacity:n?0:1,pointerEvents:n?"none":"auto"},"aria-label":e.label,"aria-hidden":n?true:void 0,tabIndex:n?-1:0,onClick:r,onPointerDown:f,onPointerMove:g,onPointerUp:C,onPointerCancel:y,onMouseEnter:()=>l(true),onMouseLeave:()=>l(false),"data-lumen-trigger":"",children:[jsxRuntime.jsx("span",{className:"lumen-notch-handle","aria-hidden":"true"}),jsxRuntime.jsxs("span",{className:"lumen-notch-label",children:[e.icon,e.label]})]}),a)}function qe(e){return {kind:"notch",edge:e.edge??"top",label:e.label??"Feedback",icon:e.icon,zIndex:e.zIndex??2147483600}}function Ye(e){return {kind:"floating",placement:e.placement??"br",offset:{x:e.offset?.x??16,y:e.offset?.y??16},safeArea:e.safeArea??true,avoid:e.avoid===false?false:e.avoid==null?"auto":Array.isArray(e.avoid)?e.avoid:[e.avoid],hideOnKeyboard:e.hideOnKeyboard??true,zIndex:e.zIndex??2147483600,label:e.label??"Feedback",icon:e.icon}}var Gt={"--lumen-bg":"background","--lumen-fg":"foreground","--lumen-primary":"accent","--lumen-radius":"radius"};function je(e){let t=react.useId();react.useEffect(()=>{if(typeof document>"u")return;let n=document.documentElement,r=`data-lumen-theme-${Jt(t)}`,a={};if(e==="auto"||e==null)n.removeAttribute("data-lumen-theme");else if(e==="light"||e==="dark")a["data-lumen-theme"]=n.getAttribute("data-lumen-theme"),n.setAttribute("data-lumen-theme",e);else for(let[o,i]of Object.entries(Gt)){let l=e[i];typeof l=="string"&&l.length>0&&(a[o]=n.style.getPropertyValue(o)||null,n.style.setProperty(o,l));}return n.setAttribute(r,""),()=>{n.removeAttribute(r);for(let[o,i]of Object.entries(a))o.startsWith("--")?i?n.style.setProperty(o,i):n.style.removeProperty(o):i==null?n.removeAttribute(o):n.setAttribute(o,i);}},[e,t]);}function Jt(e){return e.replace(/[^a-zA-Z0-9]/g,"")}var Xe=Symbol.for("lumen.history.patched"),ue="lumen:locationchange";function en(){if(typeof window>"u")return;let e=window;if(e[Xe])return;e[Xe]=true;let t=()=>window.dispatchEvent(new Event(ue)),n=history.pushState;history.pushState=function(...a){let o=n.apply(this,a);return t(),o};let r=history.replaceState;history.replaceState=function(...a){let o=r.apply(this,a);return t(),o},window.addEventListener("popstate",t);}function Ge(e){let[t,n]=react.useState(false);return react.useEffect(()=>{if(!e){n(false);return}if(typeof window>"u")return;en();let r=()=>{try{n(!!e({pathname:window.location.pathname}));}catch{n(false);}};return r(),window.addEventListener(ue,r),window.addEventListener("popstate",r),()=>{window.removeEventListener(ue,r),window.removeEventListener("popstate",r);}},[e]),t}var rn=["wv","Capacitor","Cordova","Expo","FBAN","FBAV","Instagram","Line/","Twitter"];function Je(){let[e,t]=react.useState(false);return react.useEffect(()=>{if(typeof window>"u")return;let n=window;if(n.ReactNativeWebView){t(true);return}let r=n.webkit;if(r&&r.messageHandlers){t(true);return}let a=navigator.userAgent||"";rn.some(o=>a.includes(o))&&t(true);},[]),e}var Qe="lumen.config.v1.",ln=3600*1e3;function et(e,t){let[n,r]=react.useState(()=>({trigger:null,enabled:true,loading:true}));return react.useEffect(()=>{if(typeof window>"u")return;let a=cn(e);a&&r({trigger:a.trigger,enabled:Ze(a.trigger),loading:false});let o=new AbortController,i=window.setTimeout(()=>o.abort(),5e3),l=`${t.replace(/\/$/,"")}/api/v1/sdk/config?apiKey=${encodeURIComponent(e)}`;return fetch(l,{method:"GET",mode:"cors",credentials:"omit",signal:o.signal}).then(async s=>{if(!s.ok)throw new Error(`HTTP ${s.status}`);let d=await s.json(),f=tt(d.trigger);if(!f)throw new Error("Malformed config response");dn(e,f),r({trigger:f,enabled:Ze(f),loading:false});}).catch(()=>{r(s=>s.loading?{trigger:null,enabled:true,loading:false}:s);}).finally(()=>window.clearTimeout(i)),()=>{window.clearTimeout(i),o.abort();}},[e,t]),n}function Ze(e){return e.kind!=="headless"}var sn=["br","bl","tr","tl"],un=["top","right","bottom","left"];function tt(e){if(!e||typeof e!="object")return null;let t=e;if(t.enabled===false)return {kind:"headless"};let n=typeof t.label=="string"?t.label:"Feedback";return t.kind==="floating"?{kind:"floating",placement:sn.find(a=>a===t.placement)??"br",label:n}:t.kind==="notch"?{kind:"notch",edge:un.find(a=>a===t.edge)??"top",label:n}:null}function cn(e){try{let t=window.localStorage.getItem(Qe+e);if(!t)return null;let n=JSON.parse(t);if(typeof n?.fetchedAt!="number"||Date.now()-n.fetchedAt>ln)return null;let r=tt(n.trigger);return r?{fetchedAt:n.fetchedAt,trigger:r}:null}catch{return null}}function dn(e,t){try{window.localStorage.setItem(Qe+e,JSON.stringify({fetchedAt:Date.now(),trigger:t}));}catch{}}var mn="https://shakebugs.vercel.app";function pn({apiKey:e,apiUrl:t,user:n,floatingButton:r=true,trigger:a,hideOn:o,theme:i,portalTarget:l,capture:s,children:d}){let[f,g]=react.useState(false),[C]=react.useState(false),[y]=react.useState(null),h=react.useMemo(()=>new Ee({apiKey:e,apiUrl:t,user:n}),[e,t,n?.id,n?.email,n?.name]),v=react.useCallback(()=>g(true),[]),L=react.useCallback(()=>g(false),[]),T=react.useCallback(h.submit.bind(h),[h]),M=Je(),E=Ge(o);je(i);let R=et(a?"":e,t??mn),S=react.useMemo(()=>({client:h,user:n,isOpen:f,isSubmitting:C,error:y,open:v,close:L,openCapture:v,closeCapture:L,submit:T,isNativeShell:M,capture:s}),[h,n,f,C,y,v,L,T,M,s]),c=fn({explicit:a,remote:R.trigger,remoteLoading:R.loading,floatingButton:r});return jsxRuntime.jsxs(te.Provider,{value:S,children:[d,c?.kind==="floating"?jsxRuntime.jsx(Ue,{config:Ye(c),portalTarget:l,hidden:E,onClick:v}):null,c?.kind==="notch"?jsxRuntime.jsx(Ke,{config:qe(c),portalTarget:l,hidden:E,onClick:v}):null,c?.kind==="inline"?jsxRuntime.jsx(We,{mount:c.mount,label:c.label??"Feedback",icon:c.icon,onClick:v}):null,jsxRuntime.jsx(le,{})]})}function fn(e){return e.explicit?e.explicit:e.floatingButton===false?{kind:"headless"}:e.remote?e.remote:e.remoteLoading?null:{kind:"floating"}}function bn({variant:e="default",floating:t=false,className:n,children:r="Feedback",onClick:a,...o}){let{openCapture:i}=q(),l=["lumen-btn",e==="default"?"lumen-btn-primary":null,e==="ghost"?"lumen-btn-ghost":null,e==="outline"?"lumen-btn-outline":null,t?"lumen-btn-floating":null,n].filter(Boolean).join(" ");return jsxRuntime.jsx("button",{type:"button",className:l,onClick:s=>{a?.(s),s.defaultPrevented||i();},...o,children:r})}
|
|
3
|
+
exports.CaptureModal=le;exports.FeedbackButton=bn;exports.LumenProvider=pn;exports.useLumen=q;//# sourceMappingURL=index.cjs.map
|
|
4
|
+
//# sourceMappingURL=index.cjs.map
|