@stella-loop/feedback-react 0.2.16

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 ADDED
@@ -0,0 +1,22 @@
1
+ # @stella-loop/feedback-react
2
+
3
+ React 18/19 bindings for `@stella-loop/feedback`. `FeedbackProvider` accepts a
4
+ prebuilt client or browser configuration, `useFeedback` exposes the channel,
5
+ and `useFeedbackForm` supplies a headless form binding. `FeedbackButton` and
6
+ `FeedbackDialog` provide an accessible prebuilt path with honest accepted,
7
+ queued, and failed states. This package implements feedback contract
8
+ `ingest/v1`.
9
+
10
+ ```tsx
11
+ <FeedbackProvider config={{ endpoint, key }}>
12
+ <FeedbackButton dialogProps={{ destination: "Example product feedback" }} />
13
+ </FeedbackProvider>
14
+ ```
15
+
16
+ `FeedbackButton` includes the dialog by default for the one-component path.
17
+ Set `withDialog={false}` when rendering a separately controlled
18
+ `FeedbackDialog`, or use `renderTrigger` to provide a fully custom trigger.
19
+
20
+ The components are SSR-safe and StrictMode-safe. Theme them with the same
21
+ `--slf-bg`, `--slf-fg`, `--slf-muted`, `--slf-border`, `--slf-accent`, and
22
+ `--slf-accent-fg` custom properties used by the vanilla widget.
package/dist/index.cjs ADDED
@@ -0,0 +1,4 @@
1
+ "use strict";var D=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var H=Object.prototype.hasOwnProperty;var _=(e,l)=>{for(var c in l)D(e,c,{get:l[c],enumerable:!0})},W=(e,l,c,f)=>{if(l&&typeof l=="object"||typeof l=="function")for(let t of L(l))!H.call(e,t)&&t!==c&&D(e,t,{get:()=>l[t],enumerable:!(f=z(l,t))||f.enumerable});return e};var J=e=>W(D({},"__esModule",{value:!0}),e);var X={};_(X,{FEEDBACK_FLOW_EDGES:()=>S.FEEDBACK_FLOW_EDGES,FEEDBACK_FLOW_STATES:()=>S.FEEDBACK_FLOW_STATES,FeedbackButton:()=>j,FeedbackDialog:()=>N,FeedbackProvider:()=>U,useFeedback:()=>E,useFeedbackForm:()=>K});module.exports=J(X);var n=require("react"),B=require("react-dom"),S=require("@stella-loop/feedback"),a=require("react/jsx-runtime"),R=(0,n.createContext)(null);function $(e){return e?JSON.stringify({endpoint:e.endpoint,key:e.key,app:e.app,device:e.device,locale:e.locale,route:e.route,customContext:e.customContext,collectors:e.collectors,diagnostics:e.diagnostics,queue:e.queue,failFast:e.failFast,discoverable:e.discoverable}):""}function U({client:e,config:l,children:c}){let[f,t]=(0,n.useState)(null),[v,h]=(0,n.useState)(!1),r=(0,n.useRef)(void 0),p=(0,n.useRef)(null),b=$(l);(0,n.useEffect)(()=>{let o=Symbol("feedback-provider-effect");if(p.current=o,e)return t(null),()=>{p.current===o&&(p.current=null),e.flush()};if(!l){t(null);return}let u=r.current,s=u?.signature===b?u:{client:(0,S.createFeedbackClient)(l),signature:b};return r.current=s,t(s.client),u&&u!==s&&u.client.flush().finally(()=>u.client.destroy()),()=>{p.current===o&&(p.current=null),s.client.flush().finally(()=>{queueMicrotask(()=>{p.current===null&&r.current===s&&(s.client.destroy(),r.current=void 0)})})}},[e,b,l?.scrubContext,l?.scrubDiagnostics]);let m=e??f,g=(0,n.useCallback)(()=>h(!0),[]),y=(0,n.useCallback)(()=>h(!1),[]),x=(0,n.useMemo)(()=>({client:m,open:g,close:y,openDialog:g,openState:v,submit:async o=>m?.submit(o)??{status:"failed",reason:"client_initializing"},submitOrThrow:async o=>{if(!m)throw new Error("Feedback client is still initializing.");return await m.submitOrThrow(o)},identify:(o,u)=>m?.identify(o,u),clearIdentity:async()=>await m?.clearIdentity(),peekContext:()=>m?.peekContext(),captureScreenshot:async o=>await m?.captureScreenshot(o)??null,queueState:()=>m?.queueState()??{pending:0}}),[m,y,g,v]);return(0,n.createElement)(R.Provider,{value:x},c)}function E(){let e=(0,n.useContext)(R);if(!e)throw new Error("useFeedback must be used inside <FeedbackProvider>. See https://github.com/Jacq-OS/stella-loop/tree/main/packages/feedback-react#readme for setup.");return e}function K(e={}){let l=E(),{client:c}=l,[f,t]=(0,n.useState)({message:"",kind:"other",...e}),[v,h]=(0,n.useState)(null),[r,p]=(0,n.useState)("idle"),[b,m]=(0,n.useState)(null),[g,y]=(0,n.useState)(null),x=(0,n.useRef)(null),o=(0,n.useCallback)(s=>{p(d=>d===s||(0,S.isFeedbackFlowEdge)(d,s)?s:d)},[]);(0,n.useEffect)(()=>{if(!c)return;let s=!0;return c.descriptor().then(d=>{s&&(m(d),t(F=>({...F,kind:F.kind&&d.capturePolicy.kinds.includes(F.kind)?F.kind:d.capturePolicy.defaultKind})))}).catch(()=>{s&&m(null)}),()=>{s=!1}},[c]);let u=(0,n.useCallback)(async s=>{if(!c){let k={status:"failed",reason:"client_initializing"};return h(k),k}if(x.current)return await x.current;if(r==="capturing"){let k={status:"failed",reason:"capture_in_progress"};return h(k),k}o("submitting");let d=s??f,F={...d,attachments:g?[g]:d.attachments},w=c.submit(F).then(k=>(h(k),p(k.status==="accepted"?"success":k.status==="queued"?"queued":"failure"),k)).finally(()=>{x.current=null});return x.current=w,await w},[g,c,r,o,f]);return{state:r,value:f,setValue(s){t(s),r==="failure"&&o("composing")},setField(s,d){t(F=>({...F,[s]:d})),r==="failure"&&o("composing")},descriptor:b,attachment:g,result:v,submitting:r==="submitting",submit:u,queue:c?.queueState()??{pending:0},open(){r==="idle"&&(o("open"),l.open(),queueMicrotask(()=>o("composing")))},close(){["open","composing","success","queued"].includes(r)&&p("idle"),l.close()},async attachScreenshot(s){if(!c||r!=="composing")return null;o("capturing");try{let d=await c.captureScreenshot(s);return y(d),d}catch{return y(null),null}finally{o("composing")}},removeAttachment(){y(null)},reset(){t({message:"",kind:b?.capturePolicy.defaultKind??"other",...e}),y(null),h(null),p(l.openState?"composing":"idle")}}}function j({children:e="Send feedback",floating:l=!1,withDialog:c=!0,dialogProps:f,renderTrigger:t,style:v,...h}){let{open:r,openState:p}=E(),b={onClick:r,"aria-haspopup":"dialog","aria-expanded":p};return(0,a.jsxs)(a.Fragment,{children:[t?t(b):(0,a.jsx)("button",{type:"button",...b,...h,style:l?{position:"fixed",insetInlineEnd:20,bottom:20,...v}:v,children:e}),c?(0,a.jsx)(N,{...f}):null]})}var G=`
2
+ .slf-backdrop{position:fixed;inset:0;z-index:var(--slf-z,2147000000);background:rgb(0 0 0/.42);display:grid;place-items:center}.slf-dialog{--slf-bg:#fff;--slf-fg:#171717;--slf-muted:#666;--slf-border:#d4d4d4;--slf-accent:#12665f;--slf-accent-fg:#fff;box-sizing:border-box;width:min(92vw,500px);max-height:90vh;overflow:auto;border:1px solid var(--slf-border);border-radius:var(--slf-radius,12px);background:var(--slf-bg);color:var(--slf-fg);padding:20px;font:var(--slf-font,14px/1.45 system-ui,sans-serif)}.slf-backdrop[data-slf-theme=dark] .slf-dialog{--slf-bg:#171717;--slf-fg:#f5f5f5;--slf-muted:#aaa;--slf-border:#525252;--slf-accent:#4eaaa1;--slf-accent-fg:#081715}.slf-dialog h2{margin:0 0 4px;font-size:16px}.slf-destination,.slf-contrast,.slf-status,.slf-details{color:var(--slf-muted);font-size:12px}.slf-field{display:grid;gap:6px;margin:12px 0;text-align:start}.slf-field textarea,.slf-field select,.slf-field input{box-sizing:border-box;width:100%;border:1px solid var(--slf-border);border-radius:var(--slf-control-radius,6px);background:var(--slf-bg);color:inherit;padding:9px}.slf-field textarea{min-height:130px}.slf-actions,.slf-tools{display:flex;justify-content:flex-end;gap:8px}.slf-tools{justify-content:flex-start;margin:10px 0}.slf-actions button,.slf-tools button{border:1px solid var(--slf-border);border-radius:var(--slf-control-radius,6px);background:transparent;color:inherit;padding:8px 12px}.slf-actions .slf-primary{background:var(--slf-accent);border-color:var(--slf-accent);color:var(--slf-accent-fg)}.slf-status{min-height:20px}.slf-details pre{white-space:pre-wrap;word-break:break-word}.slf-placement-right{margin-inline-start:auto;height:100%;max-height:100vh;border-radius:0}@media(prefers-color-scheme:dark){.slf-backdrop[data-slf-theme=auto] .slf-dialog{--slf-bg:#171717;--slf-fg:#f5f5f5;--slf-muted:#aaa;--slf-border:#525252;--slf-accent:#4eaaa1;--slf-accent-fg:#081715}}@media(prefers-reduced-motion:reduce){.slf-dialog{scroll-behavior:auto}}
3
+ `;function N(e){let l=E(),c=(0,n.useId)(),f=e.open??l.openState,t=K({kind:e.defaultKind,...e.initial}),[v,h]=(0,n.useState)(e.email??""),[r,p]=(0,n.useState)(null),[b,m]=(0,n.useState)(!1),[g,y]=(0,n.useState)(!1),[x,o]=(0,n.useState)(null),[u,s]=(0,n.useState)(null),d=()=>{b||t.submitting||(t.close(),e.onOpenChange?.(!1),l.close())},F=typeof XMLSerializer<"u"&&typeof HTMLCanvasElement<"u",w=(0,n.useRef)(null),k=(0,n.useRef)(null);if((0,n.useEffect)(()=>{if(!(!f||typeof document>"u"))return k.current=document.activeElement instanceof HTMLElement?document.activeElement:null,w.current?.querySelector("textarea")?.focus(),()=>k.current?.focus()},[f]),(0,n.useEffect)(()=>{f&&(t.open(),t.setValue({message:"",kind:e.defaultKind??"other",...e.initial}),h(e.email??""),p(null),y(!1),o(null))},[f]),(0,n.useEffect)(()=>{if(!f||!l.client)return;let i=!0;return l.client.descriptor().then(C=>{i&&s(C)}).catch(()=>{i&&s(null)}),()=>{i=!1}},[l.client,f]),(0,n.useEffect)(()=>{u&&t.value.kind&&!u.capturePolicy.kinds.includes(t.value.kind)&&t.setValue({...t.value,kind:u.capturePolicy.defaultKind})},[u,t.setValue,t.value]),!f||typeof document>"u")return null;let q=e.emailMode??u?.capturePolicy.emailCollection??"optional",O=u?.capturePolicy.kinds??["bug","idea","praise","question","other"],T=(e.allowScreenshot??u?.capturePolicy.allowAttachments??!1)&&F&&!g,A=async i=>{if(i.preventDefault(),b)return;let C={...t.value,attachments:r?[r]:t.value.attachments};try{o(null),await e.beforeSubmit?.(C,v.trim()),t.setValue(C),(await t.submit(C)).status==="accepted"&&setTimeout(d,800)}catch(P){o(P instanceof Error?P.message:"Submission preparation failed.")}},M=(0,a.jsxs)("div",{className:"slf-backdrop","data-slf-theme":e.scheme??"auto",dir:e.direction,onKeyDown:i=>{if(i.key==="Escape"&&!b&&!t.submitting&&d(),i.key==="Tab"){let C=[...w.current?.querySelectorAll("button:not([disabled]),input:not([disabled]),textarea:not([disabled]),select:not([disabled]),summary,[tabindex]:not([tabindex='-1'])")??[]].filter(V=>!V.hidden),P=C[0],I=C.at(-1);i.shiftKey&&document.activeElement===P?(i.preventDefault(),I?.focus()):!i.shiftKey&&document.activeElement===I&&(i.preventDefault(),P?.focus())}},children:[(0,a.jsx)("style",{children:G}),(0,a.jsxs)("div",{ref:w,style:e.theme,className:`slf-dialog ${e.placement==="right"?"slf-placement-right":""}`,role:"dialog","aria-modal":"true","aria-labelledby":c,children:[(0,a.jsx)("h2",{id:c,children:e.title??"Send feedback"}),e.destination||u?.publicLabel?(0,a.jsxs)("p",{className:"slf-destination",children:["Sending to ",e.destination??u?.publicLabel]}):null,e.contrast?(0,a.jsx)("p",{className:"slf-contrast",children:e.contrast}):null,(0,a.jsxs)("form",{onSubmit:i=>void A(i),children:[(0,a.jsxs)("label",{className:"slf-field",children:[(0,a.jsx)("span",{children:"What would you like us to know?"}),(0,a.jsx)("textarea",{required:!0,value:t.value.message,onChange:i=>t.setValue({...t.value,message:i.target.value})})]}),(0,a.jsxs)("label",{className:"slf-field",children:[(0,a.jsx)("span",{children:"Kind"}),(0,a.jsx)("select",{value:t.value.kind,onChange:i=>t.setValue({...t.value,kind:i.target.value}),children:O.map(i=>(0,a.jsx)("option",{children:i},i))})]}),q!=="off"?(0,a.jsxs)("label",{className:"slf-field",children:[(0,a.jsxs)("span",{children:["Email for a reply",q==="required"?"":" (optional)"]}),(0,a.jsx)("input",{type:"email",autoComplete:"email",required:q==="required",value:v,onChange:i=>h(i.target.value)}),(0,a.jsx)("small",{children:"Remove this address to send anonymously."})]}):null,T?(0,a.jsx)("div",{className:"slf-tools",children:(0,a.jsx)("button",{type:"button",disabled:b,onClick:()=>{m(!0),l.client?.captureScreenshot({...e.screenshotOptions,direction:e.direction??e.screenshotOptions?.direction}).then(i=>{p(i),i||y(!0)}).catch(()=>{p(null),y(!0)}).finally(()=>m(!1))},children:b?"Opening screenshot editor\u2026":r?"Screenshot attached":"Attach screenshot"})}):null,(0,a.jsxs)("details",{className:"slf-details",children:[(0,a.jsx)("summary",{children:"Details included"}),(0,a.jsx)("pre",{children:JSON.stringify({context:l.client?.peekContext()??{},diagnostics:l.client?.peekDiagnostics()??[],attachments:r?[{contentType:r.contentType,size:r.size}]:[]},null,2)})]}),(0,a.jsx)("p",{className:"slf-status",role:"status","aria-live":"polite",children:x?`Could not send: ${x}`:g?"Screenshot capture is unavailable in this environment.":t.result?t.result.status==="accepted"?"Feedback sent.":t.result.status==="queued"?"Saved offline for retry.":`Could not send: ${t.result.reason}`:""}),(0,a.jsxs)("div",{className:"slf-actions",children:[(0,a.jsx)("button",{type:"button",onClick:d,disabled:b||t.submitting,children:"Cancel"}),(0,a.jsx)("button",{className:"slf-primary",type:"submit",disabled:t.submitting||b||!l.client,children:"Send"})]})]})]})]});return(0,B.createPortal)(M,document.body)}0&&(module.exports={FEEDBACK_FLOW_EDGES,FEEDBACK_FLOW_STATES,FeedbackButton,FeedbackDialog,FeedbackProvider,useFeedback,useFeedbackForm});
4
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type FormEvent,\n type PropsWithChildren,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport {\n createFeedbackClient,\n type FeedbackBrowserClient,\n type FeedbackClientConfig,\n type FeedbackInput,\n type FeedbackKind,\n type FeedbackResult,\n type AttachmentHandle,\n type FeedbackDescriptor,\n FEEDBACK_FLOW_EDGES,\n FEEDBACK_FLOW_STATES,\n isFeedbackFlowEdge,\n type FeedbackFlowState,\n} from \"@stella-loop/feedback\";\n\nexport type FeedbackContextValue = {\n client: FeedbackBrowserClient | null;\n open(): void;\n close(): void;\n openDialog(): void;\n openState: boolean;\n submit(input: FeedbackInput): Promise<FeedbackResult>;\n submitOrThrow(input: FeedbackInput): Promise<{ submissionId: string }>;\n identify: FeedbackBrowserClient[\"identify\"];\n clearIdentity(): Promise<void>;\n peekContext(): ReturnType<FeedbackBrowserClient[\"peekContext\"]> | undefined;\n captureScreenshot: FeedbackBrowserClient[\"captureScreenshot\"];\n queueState(): ReturnType<FeedbackBrowserClient[\"queueState\"]>;\n};\nconst Context = createContext<FeedbackContextValue | null>(null);\n\nexport type FeedbackProviderProps = PropsWithChildren<{\n client?: FeedbackBrowserClient;\n config?: FeedbackClientConfig;\n}>;\n\nfunction configSignature(config: FeedbackClientConfig | undefined): string {\n if (!config) return \"\";\n return JSON.stringify({\n endpoint: config.endpoint,\n key: config.key,\n app: config.app,\n device: config.device,\n locale: config.locale,\n route: config.route,\n customContext: config.customContext,\n collectors: config.collectors,\n diagnostics: config.diagnostics,\n queue: config.queue,\n failFast: config.failFast,\n discoverable: config.discoverable,\n });\n}\n\nexport function FeedbackProvider({\n client: provided,\n config,\n children,\n}: FeedbackProviderProps): ReactNode {\n const [owned, setOwned] = useState<FeedbackBrowserClient | null>(null);\n const [openState, setOpen] = useState(false);\n const ownedRef = useRef<{ client: FeedbackBrowserClient; signature: string } | undefined>(\n undefined,\n );\n const activeEffect = useRef<symbol | null>(null);\n const signature = configSignature(config);\n useEffect(() => {\n const token = Symbol(\"feedback-provider-effect\");\n activeEffect.current = token;\n if (provided) {\n setOwned(null);\n return () => {\n if (activeEffect.current === token) activeEffect.current = null;\n void provided.flush();\n };\n }\n if (!config) {\n setOwned(null);\n return;\n }\n const previous = ownedRef.current;\n const record =\n previous?.signature === signature\n ? previous\n : { client: createFeedbackClient(config), signature };\n ownedRef.current = record;\n setOwned(record.client);\n if (previous && previous !== record) {\n void previous.client.flush().finally(() => previous.client.destroy());\n }\n return () => {\n if (activeEffect.current === token) activeEffect.current = null;\n // React StrictMode immediately re-runs effects. The microtask check\n // preserves the same owned client across that probe while a real\n // unmount still flush-attempts before releasing instrumentation.\n void record.client.flush().finally(() => {\n queueMicrotask(() => {\n if (activeEffect.current === null && ownedRef.current === record) {\n record.client.destroy();\n ownedRef.current = undefined;\n }\n });\n });\n };\n }, [provided, signature, config?.scrubContext, config?.scrubDiagnostics]);\n const client = provided ?? owned;\n const open = useCallback(() => setOpen(true), []);\n const close = useCallback(() => setOpen(false), []);\n const value = useMemo(\n (): FeedbackContextValue => ({\n client,\n open,\n close,\n openDialog: open,\n openState,\n submit: async (input) =>\n client?.submit(input) ?? { status: \"failed\", reason: \"client_initializing\" },\n submitOrThrow: async (input) => {\n if (!client) throw new Error(\"Feedback client is still initializing.\");\n return await client.submitOrThrow(input);\n },\n identify: (identity, verification) => client?.identify(identity, verification),\n clearIdentity: async () => await client?.clearIdentity(),\n peekContext: () => client?.peekContext(),\n captureScreenshot: async (options) => (await client?.captureScreenshot(options)) ?? null,\n queueState: () => client?.queueState() ?? { pending: 0 },\n }),\n [client, close, open, openState],\n );\n return createElement(Context.Provider, { value }, children);\n}\n\nexport function useFeedback(): FeedbackContextValue {\n const value = useContext(Context);\n if (!value)\n throw new Error(\n \"useFeedback must be used inside <FeedbackProvider>. See https://github.com/Jacq-OS/stella-loop/tree/main/packages/feedback-react#readme for setup.\",\n );\n return value;\n}\n\nexport type FeedbackFormBinding = {\n state: FeedbackFlowState;\n value: FeedbackInput;\n descriptor: FeedbackDescriptor | null;\n attachment: AttachmentHandle | null;\n result: FeedbackResult | null;\n submitting: boolean;\n queue: ReturnType<FeedbackBrowserClient[\"queueState\"]>;\n open(): void;\n close(): void;\n setField<Key extends keyof FeedbackInput>(key: Key, value: FeedbackInput[Key]): void;\n setValue(value: FeedbackInput): void;\n attachScreenshot(\n options?: Parameters<FeedbackBrowserClient[\"captureScreenshot\"]>[0],\n ): Promise<AttachmentHandle | null>;\n removeAttachment(): void;\n submit(override?: FeedbackInput): Promise<FeedbackResult>;\n reset(): void;\n};\n\nexport function useFeedbackForm(initial: Partial<FeedbackInput> = {}): FeedbackFormBinding {\n const context = useFeedback();\n const { client } = context;\n const [value, setValue] = useState<FeedbackInput>({ message: \"\", kind: \"other\", ...initial });\n const [result, setResult] = useState<FeedbackResult | null>(null);\n const [state, setState] = useState<FeedbackFlowState>(\"idle\");\n const [descriptor, setDescriptor] = useState<FeedbackDescriptor | null>(null);\n const [attachment, setAttachment] = useState<AttachmentHandle | null>(null);\n const inFlight = useRef<Promise<FeedbackResult> | null>(null);\n const transition = useCallback((next: FeedbackFlowState) => {\n setState((current) => (current === next || isFeedbackFlowEdge(current, next) ? next : current));\n }, []);\n useEffect(() => {\n if (!client) return;\n let active = true;\n void client\n .descriptor()\n .then((next) => {\n if (!active) return;\n setDescriptor(next);\n setValue((current) => ({\n ...current,\n kind:\n current.kind && next.capturePolicy.kinds.includes(current.kind)\n ? current.kind\n : next.capturePolicy.defaultKind,\n }));\n })\n .catch(() => {\n if (active) setDescriptor(null);\n });\n return () => {\n active = false;\n };\n }, [client]);\n const submit = useCallback(\n async (override?: FeedbackInput) => {\n if (!client) {\n const unavailable: FeedbackResult = { status: \"failed\", reason: \"client_initializing\" };\n setResult(unavailable);\n return unavailable;\n }\n if (inFlight.current) return await inFlight.current;\n if (state === \"capturing\") {\n const blocked: FeedbackResult = { status: \"failed\", reason: \"capture_in_progress\" };\n setResult(blocked);\n return blocked;\n }\n transition(\"submitting\");\n const submission = override ?? value;\n const withAttachment = {\n ...submission,\n attachments: attachment ? [attachment] : submission.attachments,\n };\n const request = client\n .submit(withAttachment)\n .then((next) => {\n setResult(next);\n setState(\n next.status === \"accepted\"\n ? \"success\"\n : next.status === \"queued\"\n ? \"queued\"\n : \"failure\",\n );\n return next;\n })\n .finally(() => {\n inFlight.current = null;\n });\n inFlight.current = request;\n return await request;\n },\n [attachment, client, state, transition, value],\n );\n return {\n state,\n value,\n setValue(next) {\n setValue(next);\n if (state === \"failure\") transition(\"composing\");\n },\n setField(key, next) {\n setValue((current) => ({ ...current, [key]: next }));\n if (state === \"failure\") transition(\"composing\");\n },\n descriptor,\n attachment,\n result,\n submitting: state === \"submitting\",\n submit,\n queue: client?.queueState() ?? { pending: 0 },\n open() {\n if (state !== \"idle\") return;\n transition(\"open\");\n context.open();\n queueMicrotask(() => transition(\"composing\"));\n },\n close() {\n if ([\"open\", \"composing\", \"success\", \"queued\"].includes(state)) setState(\"idle\");\n context.close();\n },\n async attachScreenshot(options) {\n if (!client || state !== \"composing\") return null;\n transition(\"capturing\");\n try {\n const next = await client.captureScreenshot(options);\n setAttachment(next);\n return next;\n } catch {\n setAttachment(null);\n return null;\n } finally {\n transition(\"composing\");\n }\n },\n removeAttachment() {\n setAttachment(null);\n },\n reset() {\n setValue({ message: \"\", kind: descriptor?.capturePolicy.defaultKind ?? \"other\", ...initial });\n setAttachment(null);\n setResult(null);\n setState(context.openState ? \"composing\" : \"idle\");\n },\n };\n}\n\nexport { FEEDBACK_FLOW_EDGES, FEEDBACK_FLOW_STATES };\n\nexport type FeedbackButtonProps = {\n children?: ReactNode;\n className?: string;\n style?: CSSProperties;\n floating?: boolean;\n withDialog?: boolean;\n dialogProps?: Omit<FeedbackDialogProps, \"open\" | \"onOpenChange\">;\n renderTrigger?(props: {\n onClick(): void;\n \"aria-haspopup\": \"dialog\";\n \"aria-expanded\": boolean;\n }): ReactNode;\n};\nexport function FeedbackButton({\n children = \"Send feedback\",\n floating = false,\n withDialog = true,\n dialogProps,\n renderTrigger,\n style,\n ...props\n}: FeedbackButtonProps) {\n const { open, openState } = useFeedback();\n const triggerProps = {\n onClick: open,\n \"aria-haspopup\": \"dialog\" as const,\n \"aria-expanded\": openState,\n };\n return (\n <>\n {renderTrigger ? (\n renderTrigger(triggerProps)\n ) : (\n <button\n type=\"button\"\n {...triggerProps}\n {...props}\n style={floating ? { position: \"fixed\", insetInlineEnd: 20, bottom: 20, ...style } : style}\n >\n {children}\n </button>\n )}\n {withDialog ? <FeedbackDialog {...dialogProps} /> : null}\n </>\n );\n}\n\nexport type FeedbackDialogProps = {\n open?: boolean;\n onOpenChange?(open: boolean): void;\n title?: string;\n destination?: string;\n placement?: \"center\" | \"right\";\n initial?: Partial<FeedbackInput>;\n defaultKind?: FeedbackKind;\n /** Optional account address offered for this submission. It is never sent\n * unless the submitter leaves it in the field and `beforeSubmit` identifies\n * it on the client. */\n email?: string;\n emailMode?: \"off\" | \"optional\" | \"required\";\n contrast?: string;\n allowScreenshot?: boolean;\n screenshotOptions?: Parameters<FeedbackBrowserClient[\"captureScreenshot\"]>[0];\n /** Public integration seam for server-minted identity verification. */\n beforeSubmit?(value: FeedbackInput, email: string): void | Promise<void>;\n /** Map a host design system onto the documented --slf-* custom properties. */\n theme?: CSSProperties;\n scheme?: \"light\" | \"dark\" | \"auto\";\n direction?: \"ltr\" | \"rtl\";\n};\n\nconst styles = `\n.slf-backdrop{position:fixed;inset:0;z-index:var(--slf-z,2147000000);background:rgb(0 0 0/.42);display:grid;place-items:center}.slf-dialog{--slf-bg:#fff;--slf-fg:#171717;--slf-muted:#666;--slf-border:#d4d4d4;--slf-accent:#12665f;--slf-accent-fg:#fff;box-sizing:border-box;width:min(92vw,500px);max-height:90vh;overflow:auto;border:1px solid var(--slf-border);border-radius:var(--slf-radius,12px);background:var(--slf-bg);color:var(--slf-fg);padding:20px;font:var(--slf-font,14px/1.45 system-ui,sans-serif)}.slf-backdrop[data-slf-theme=dark] .slf-dialog{--slf-bg:#171717;--slf-fg:#f5f5f5;--slf-muted:#aaa;--slf-border:#525252;--slf-accent:#4eaaa1;--slf-accent-fg:#081715}.slf-dialog h2{margin:0 0 4px;font-size:16px}.slf-destination,.slf-contrast,.slf-status,.slf-details{color:var(--slf-muted);font-size:12px}.slf-field{display:grid;gap:6px;margin:12px 0;text-align:start}.slf-field textarea,.slf-field select,.slf-field input{box-sizing:border-box;width:100%;border:1px solid var(--slf-border);border-radius:var(--slf-control-radius,6px);background:var(--slf-bg);color:inherit;padding:9px}.slf-field textarea{min-height:130px}.slf-actions,.slf-tools{display:flex;justify-content:flex-end;gap:8px}.slf-tools{justify-content:flex-start;margin:10px 0}.slf-actions button,.slf-tools button{border:1px solid var(--slf-border);border-radius:var(--slf-control-radius,6px);background:transparent;color:inherit;padding:8px 12px}.slf-actions .slf-primary{background:var(--slf-accent);border-color:var(--slf-accent);color:var(--slf-accent-fg)}.slf-status{min-height:20px}.slf-details pre{white-space:pre-wrap;word-break:break-word}.slf-placement-right{margin-inline-start:auto;height:100%;max-height:100vh;border-radius:0}@media(prefers-color-scheme:dark){.slf-backdrop[data-slf-theme=auto] .slf-dialog{--slf-bg:#171717;--slf-fg:#f5f5f5;--slf-muted:#aaa;--slf-border:#525252;--slf-accent:#4eaaa1;--slf-accent-fg:#081715}}@media(prefers-reduced-motion:reduce){.slf-dialog{scroll-behavior:auto}}\n`;\n\nexport function FeedbackDialog(props: FeedbackDialogProps) {\n const context = useFeedback();\n const titleId = useId();\n const shown = props.open ?? context.openState;\n const form = useFeedbackForm({ kind: props.defaultKind, ...props.initial });\n const [email, setEmail] = useState(props.email ?? \"\");\n const [attachment, setAttachment] = useState<AttachmentHandle | null>(null);\n const [capturing, setCapturing] = useState(false);\n const [screenshotUnavailable, setScreenshotUnavailable] = useState(false);\n const [localFailure, setLocalFailure] = useState<string | null>(null);\n const [descriptor, setDescriptor] = useState<FeedbackDescriptor | null>(null);\n const close = () => {\n if (capturing || form.submitting) return;\n form.close();\n props.onOpenChange?.(false);\n context.close();\n };\n const screenshotCapable =\n typeof XMLSerializer !== \"undefined\" && typeof HTMLCanvasElement !== \"undefined\";\n const dialog = useRef<HTMLDivElement>(null);\n const returnFocus = useRef<HTMLElement | null>(null);\n useEffect(() => {\n if (!shown || typeof document === \"undefined\") return;\n returnFocus.current =\n document.activeElement instanceof HTMLElement ? document.activeElement : null;\n dialog.current?.querySelector<HTMLElement>(\"textarea\")?.focus();\n return () => returnFocus.current?.focus();\n }, [shown]);\n useEffect(() => {\n if (!shown) return;\n form.open();\n form.setValue({ message: \"\", kind: props.defaultKind ?? \"other\", ...props.initial });\n setEmail(props.email ?? \"\");\n setAttachment(null);\n setScreenshotUnavailable(false);\n setLocalFailure(null);\n // The dialog intentionally resets only on each opening. Changes while a\n // person is composing must never erase their work.\n }, [shown]);\n useEffect(() => {\n if (!shown || !context.client) return;\n let live = true;\n void context.client\n .descriptor()\n .then((value) => {\n if (live) setDescriptor(value);\n })\n .catch(() => {\n if (live) setDescriptor(null);\n });\n return () => {\n live = false;\n };\n }, [context.client, shown]);\n useEffect(() => {\n if (!descriptor) return;\n if (form.value.kind && !descriptor.capturePolicy.kinds.includes(form.value.kind)) {\n form.setValue({ ...form.value, kind: descriptor.capturePolicy.defaultKind });\n }\n }, [descriptor, form.setValue, form.value]);\n if (!shown || typeof document === \"undefined\") return null;\n const emailMode = props.emailMode ?? descriptor?.capturePolicy.emailCollection ?? \"optional\";\n const allowedKinds = descriptor?.capturePolicy.kinds ?? [\n \"bug\",\n \"idea\",\n \"praise\",\n \"question\",\n \"other\",\n ];\n const allowScreenshot =\n (props.allowScreenshot ?? descriptor?.capturePolicy.allowAttachments ?? false) &&\n screenshotCapable &&\n !screenshotUnavailable;\n const submit = async (event: FormEvent) => {\n event.preventDefault();\n // Capturing includes the attachment upload. Do not allow a fast keyboard\n // or pointer submission to race the handle that must be included.\n if (capturing) return;\n const value = {\n ...form.value,\n attachments: attachment ? [attachment] : form.value.attachments,\n };\n try {\n setLocalFailure(null);\n await props.beforeSubmit?.(value, email.trim());\n form.setValue(value);\n const result = await form.submit(value);\n if (result.status === \"accepted\") setTimeout(close, 800);\n } catch (caught) {\n setLocalFailure(caught instanceof Error ? caught.message : \"Submission preparation failed.\");\n }\n };\n const node = (\n <div\n className=\"slf-backdrop\"\n data-slf-theme={props.scheme ?? \"auto\"}\n dir={props.direction}\n onKeyDown={(event) => {\n if (event.key === \"Escape\" && !capturing && !form.submitting) close();\n if (event.key === \"Tab\") {\n const focusable = [\n ...(dialog.current?.querySelectorAll<HTMLElement>(\n \"button:not([disabled]),input:not([disabled]),textarea:not([disabled]),select:not([disabled]),summary,[tabindex]:not([tabindex='-1'])\",\n ) ?? []),\n ].filter((item) => !item.hidden);\n const first = focusable[0];\n const last = focusable.at(-1);\n if (event.shiftKey && document.activeElement === first) {\n event.preventDefault();\n last?.focus();\n } else if (!event.shiftKey && document.activeElement === last) {\n event.preventDefault();\n first?.focus();\n }\n }\n }}\n >\n <style>{styles}</style>\n <div\n ref={dialog}\n style={props.theme}\n className={`slf-dialog ${props.placement === \"right\" ? \"slf-placement-right\" : \"\"}`}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n >\n <h2 id={titleId}>{props.title ?? \"Send feedback\"}</h2>\n {props.destination || descriptor?.publicLabel ? (\n <p className=\"slf-destination\">\n Sending to {props.destination ?? descriptor?.publicLabel}\n </p>\n ) : null}\n {props.contrast ? <p className=\"slf-contrast\">{props.contrast}</p> : null}\n <form onSubmit={(event) => void submit(event)}>\n <label className=\"slf-field\">\n <span>What would you like us to know?</span>\n <textarea\n required\n value={form.value.message}\n onChange={(event) => form.setValue({ ...form.value, message: event.target.value })}\n />\n </label>\n <label className=\"slf-field\">\n <span>Kind</span>\n <select\n value={form.value.kind}\n onChange={(event) =>\n form.setValue({ ...form.value, kind: event.target.value as FeedbackKind })\n }\n >\n {allowedKinds.map((kind) => (\n <option key={kind}>{kind}</option>\n ))}\n </select>\n </label>\n {emailMode !== \"off\" ? (\n <label className=\"slf-field\">\n <span>Email for a reply{emailMode === \"required\" ? \"\" : \" (optional)\"}</span>\n <input\n type=\"email\"\n autoComplete=\"email\"\n required={emailMode === \"required\"}\n value={email}\n onChange={(event) => setEmail(event.target.value)}\n />\n <small>Remove this address to send anonymously.</small>\n </label>\n ) : null}\n {allowScreenshot ? (\n <div className=\"slf-tools\">\n <button\n type=\"button\"\n disabled={capturing}\n onClick={() => {\n setCapturing(true);\n void context.client\n ?.captureScreenshot({\n ...props.screenshotOptions,\n direction: props.direction ?? props.screenshotOptions?.direction,\n })\n .then((next) => {\n setAttachment(next);\n if (!next) setScreenshotUnavailable(true);\n })\n .catch(() => {\n setAttachment(null);\n setScreenshotUnavailable(true);\n })\n .finally(() => setCapturing(false));\n }}\n >\n {capturing\n ? \"Opening screenshot editor…\"\n : attachment\n ? \"Screenshot attached\"\n : \"Attach screenshot\"}\n </button>\n </div>\n ) : null}\n <details className=\"slf-details\">\n <summary>Details included</summary>\n <pre>\n {JSON.stringify(\n {\n context: context.client?.peekContext() ?? {},\n diagnostics: context.client?.peekDiagnostics() ?? [],\n attachments: attachment\n ? [{ contentType: attachment.contentType, size: attachment.size }]\n : [],\n },\n null,\n 2,\n )}\n </pre>\n </details>\n <p className=\"slf-status\" role=\"status\" aria-live=\"polite\">\n {localFailure\n ? `Could not send: ${localFailure}`\n : screenshotUnavailable\n ? \"Screenshot capture is unavailable in this environment.\"\n : form.result\n ? form.result.status === \"accepted\"\n ? \"Feedback sent.\"\n : form.result.status === \"queued\"\n ? \"Saved offline for retry.\"\n : `Could not send: ${form.result.reason}`\n : \"\"}\n </p>\n <div className=\"slf-actions\">\n <button type=\"button\" onClick={close} disabled={capturing || form.submitting}>\n Cancel\n </button>\n <button\n className=\"slf-primary\"\n type=\"submit\"\n disabled={form.submitting || capturing || !context.client}\n >\n Send\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n return createPortal(node, document.body);\n}\n\nexport type { FeedbackBrowserClient, FeedbackClientConfig, FeedbackInput, FeedbackResult };\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,kHAAAE,EAAA,mBAAAC,EAAA,qBAAAC,EAAA,gBAAAC,EAAA,oBAAAC,IAAA,eAAAC,EAAAP,GAAA,IAAAQ,EAcO,iBACPC,EAA6B,qBAE7BC,EAaO,iCAmTHC,EAAA,6BAnSEC,KAAU,iBAA2C,IAAI,EAO/D,SAASC,EAAgBC,EAAkD,CACzE,OAAKA,EACE,KAAK,UAAU,CACpB,SAAUA,EAAO,SACjB,IAAKA,EAAO,IACZ,IAAKA,EAAO,IACZ,OAAQA,EAAO,OACf,OAAQA,EAAO,OACf,MAAOA,EAAO,MACd,cAAeA,EAAO,cACtB,WAAYA,EAAO,WACnB,YAAaA,EAAO,YACpB,MAAOA,EAAO,MACd,SAAUA,EAAO,SACjB,aAAcA,EAAO,YACvB,CAAC,EAdmB,EAetB,CAEO,SAASV,EAAiB,CAC/B,OAAQW,EACR,OAAAD,EACA,SAAAE,CACF,EAAqC,CACnC,GAAM,CAACC,EAAOC,CAAQ,KAAI,YAAuC,IAAI,EAC/D,CAACC,EAAWC,CAAO,KAAI,YAAS,EAAK,EACrCC,KAAW,UACf,MACF,EACMC,KAAe,UAAsB,IAAI,EACzCC,EAAYV,EAAgBC,CAAM,KACxC,aAAU,IAAM,CACd,IAAMU,EAAQ,OAAO,0BAA0B,EAE/C,GADAF,EAAa,QAAUE,EACnBT,EACF,OAAAG,EAAS,IAAI,EACN,IAAM,CACPI,EAAa,UAAYE,IAAOF,EAAa,QAAU,MACtDP,EAAS,MAAM,CACtB,EAEF,GAAI,CAACD,EAAQ,CACXI,EAAS,IAAI,EACb,MACF,CACA,IAAMO,EAAWJ,EAAS,QACpBK,EACJD,GAAU,YAAcF,EACpBE,EACA,CAAE,UAAQ,wBAAqBX,CAAM,EAAG,UAAAS,CAAU,EACxD,OAAAF,EAAS,QAAUK,EACnBR,EAASQ,EAAO,MAAM,EAClBD,GAAYA,IAAaC,GACtBD,EAAS,OAAO,MAAM,EAAE,QAAQ,IAAMA,EAAS,OAAO,QAAQ,CAAC,EAE/D,IAAM,CACPH,EAAa,UAAYE,IAAOF,EAAa,QAAU,MAItDI,EAAO,OAAO,MAAM,EAAE,QAAQ,IAAM,CACvC,eAAe,IAAM,CACfJ,EAAa,UAAY,MAAQD,EAAS,UAAYK,IACxDA,EAAO,OAAO,QAAQ,EACtBL,EAAS,QAAU,OAEvB,CAAC,CACH,CAAC,CACH,CACF,EAAG,CAACN,EAAUQ,EAAWT,GAAQ,aAAcA,GAAQ,gBAAgB,CAAC,EACxE,IAAMa,EAASZ,GAAYE,EACrBW,KAAO,eAAY,IAAMR,EAAQ,EAAI,EAAG,CAAC,CAAC,EAC1CS,KAAQ,eAAY,IAAMT,EAAQ,EAAK,EAAG,CAAC,CAAC,EAC5CU,KAAQ,WACZ,KAA6B,CAC3B,OAAAH,EACA,KAAAC,EACA,MAAAC,EACA,WAAYD,EACZ,UAAAT,EACA,OAAQ,MAAOY,GACbJ,GAAQ,OAAOI,CAAK,GAAK,CAAE,OAAQ,SAAU,OAAQ,qBAAsB,EAC7E,cAAe,MAAOA,GAAU,CAC9B,GAAI,CAACJ,EAAQ,MAAM,IAAI,MAAM,wCAAwC,EACrE,OAAO,MAAMA,EAAO,cAAcI,CAAK,CACzC,EACA,SAAU,CAACC,EAAUC,IAAiBN,GAAQ,SAASK,EAAUC,CAAY,EAC7E,cAAe,SAAY,MAAMN,GAAQ,cAAc,EACvD,YAAa,IAAMA,GAAQ,YAAY,EACvC,kBAAmB,MAAOO,GAAa,MAAMP,GAAQ,kBAAkBO,CAAO,GAAM,KACpF,WAAY,IAAMP,GAAQ,WAAW,GAAK,CAAE,QAAS,CAAE,CACzD,GACA,CAACA,EAAQE,EAAOD,EAAMT,CAAS,CACjC,EACA,SAAO,iBAAcP,EAAQ,SAAU,CAAE,MAAAkB,CAAM,EAAGd,CAAQ,CAC5D,CAEO,SAASX,GAAoC,CAClD,IAAMyB,KAAQ,cAAWlB,CAAO,EAChC,GAAI,CAACkB,EACH,MAAM,IAAI,MACR,oJACF,EACF,OAAOA,CACT,CAsBO,SAASxB,EAAgB6B,EAAkC,CAAC,EAAwB,CACzF,IAAMC,EAAU/B,EAAY,EACtB,CAAE,OAAAsB,CAAO,EAAIS,EACb,CAACN,EAAOO,CAAQ,KAAI,YAAwB,CAAE,QAAS,GAAI,KAAM,QAAS,GAAGF,CAAQ,CAAC,EACtF,CAACG,EAAQC,CAAS,KAAI,YAAgC,IAAI,EAC1D,CAACC,EAAOC,CAAQ,KAAI,YAA4B,MAAM,EACtD,CAACC,EAAYC,CAAa,KAAI,YAAoC,IAAI,EACtE,CAACC,EAAYC,CAAa,KAAI,YAAkC,IAAI,EACpEC,KAAW,UAAuC,IAAI,EACtDC,KAAa,eAAaC,GAA4B,CAC1DP,EAAUQ,GAAaA,IAAYD,MAAQ,sBAAmBC,EAASD,CAAI,EAAIA,EAAOC,CAAQ,CAChG,EAAG,CAAC,CAAC,KACL,aAAU,IAAM,CACd,GAAI,CAACtB,EAAQ,OACb,IAAIuB,EAAS,GACb,OAAKvB,EACF,WAAW,EACX,KAAMqB,GAAS,CACTE,IACLP,EAAcK,CAAI,EAClBX,EAAUY,IAAa,CACrB,GAAGA,EACH,KACEA,EAAQ,MAAQD,EAAK,cAAc,MAAM,SAASC,EAAQ,IAAI,EAC1DA,EAAQ,KACRD,EAAK,cAAc,WAC3B,EAAE,EACJ,CAAC,EACA,MAAM,IAAM,CACPE,GAAQP,EAAc,IAAI,CAChC,CAAC,EACI,IAAM,CACXO,EAAS,EACX,CACF,EAAG,CAACvB,CAAM,CAAC,EACX,IAAMwB,KAAS,eACb,MAAOC,GAA6B,CAClC,GAAI,CAACzB,EAAQ,CACX,IAAM0B,EAA8B,CAAE,OAAQ,SAAU,OAAQ,qBAAsB,EACtF,OAAAd,EAAUc,CAAW,EACdA,CACT,CACA,GAAIP,EAAS,QAAS,OAAO,MAAMA,EAAS,QAC5C,GAAIN,IAAU,YAAa,CACzB,IAAMc,EAA0B,CAAE,OAAQ,SAAU,OAAQ,qBAAsB,EAClF,OAAAf,EAAUe,CAAO,EACVA,CACT,CACAP,EAAW,YAAY,EACvB,IAAMQ,EAAaH,GAAYtB,EACzB0B,EAAiB,CACrB,GAAGD,EACH,YAAaX,EAAa,CAACA,CAAU,EAAIW,EAAW,WACtD,EACME,EAAU9B,EACb,OAAO6B,CAAc,EACrB,KAAMR,IACLT,EAAUS,CAAI,EACdP,EACEO,EAAK,SAAW,WACZ,UACAA,EAAK,SAAW,SACd,SACA,SACR,EACOA,EACR,EACA,QAAQ,IAAM,CACbF,EAAS,QAAU,IACrB,CAAC,EACH,OAAAA,EAAS,QAAUW,EACZ,MAAMA,CACf,EACA,CAACb,EAAYjB,EAAQa,EAAOO,EAAYjB,CAAK,CAC/C,EACA,MAAO,CACL,MAAAU,EACA,MAAAV,EACA,SAASkB,EAAM,CACbX,EAASW,CAAI,EACTR,IAAU,WAAWO,EAAW,WAAW,CACjD,EACA,SAASW,EAAKV,EAAM,CAClBX,EAAUY,IAAa,CAAE,GAAGA,EAAS,CAACS,CAAG,EAAGV,CAAK,EAAE,EAC/CR,IAAU,WAAWO,EAAW,WAAW,CACjD,EACA,WAAAL,EACA,WAAAE,EACA,OAAAN,EACA,WAAYE,IAAU,aACtB,OAAAW,EACA,MAAOxB,GAAQ,WAAW,GAAK,CAAE,QAAS,CAAE,EAC5C,MAAO,CACDa,IAAU,SACdO,EAAW,MAAM,EACjBX,EAAQ,KAAK,EACb,eAAe,IAAMW,EAAW,WAAW,CAAC,EAC9C,EACA,OAAQ,CACF,CAAC,OAAQ,YAAa,UAAW,QAAQ,EAAE,SAASP,CAAK,GAAGC,EAAS,MAAM,EAC/EL,EAAQ,MAAM,CAChB,EACA,MAAM,iBAAiBF,EAAS,CAC9B,GAAI,CAACP,GAAUa,IAAU,YAAa,OAAO,KAC7CO,EAAW,WAAW,EACtB,GAAI,CACF,IAAMC,EAAO,MAAMrB,EAAO,kBAAkBO,CAAO,EACnD,OAAAW,EAAcG,CAAI,EACXA,CACT,MAAQ,CACN,OAAAH,EAAc,IAAI,EACX,IACT,QAAE,CACAE,EAAW,WAAW,CACxB,CACF,EACA,kBAAmB,CACjBF,EAAc,IAAI,CACpB,EACA,OAAQ,CACNR,EAAS,CAAE,QAAS,GAAI,KAAMK,GAAY,cAAc,aAAe,QAAS,GAAGP,CAAQ,CAAC,EAC5FU,EAAc,IAAI,EAClBN,EAAU,IAAI,EACdE,EAASL,EAAQ,UAAY,YAAc,MAAM,CACnD,CACF,CACF,CAiBO,SAASuB,EAAe,CAC7B,SAAAC,EAAW,gBACX,SAAAC,EAAW,GACX,WAAAC,EAAa,GACb,YAAAC,EACA,cAAAC,EACA,MAAAC,EACA,GAAGC,CACL,EAAwB,CACtB,GAAM,CAAE,KAAAC,EAAM,UAAAC,CAAU,EAAIC,EAAY,EAClCC,EAAe,CACnB,QAASH,EACT,gBAAiB,SACjB,gBAAiBC,CACnB,EACA,SACE,oBACG,UAAAJ,EACCA,EAAcM,CAAY,KAE1B,OAAC,UACC,KAAK,SACJ,GAAGA,EACH,GAAGJ,EACJ,MAAOL,EAAW,CAAE,SAAU,QAAS,eAAgB,GAAI,OAAQ,GAAI,GAAGI,CAAM,EAAIA,EAEnF,SAAAL,EACH,EAEDE,KAAa,OAACS,EAAA,CAAgB,GAAGR,EAAa,EAAK,MACtD,CAEJ,CA0BA,IAAMS,EAAS;AAAA;AAAA,EAIR,SAASD,EAAeL,EAA4B,CACzD,IAAMO,EAAUJ,EAAY,EACtBK,KAAU,SAAM,EAChBC,EAAQT,EAAM,MAAQO,EAAQ,UAC9BG,EAAOC,EAAgB,CAAE,KAAMX,EAAM,YAAa,GAAGA,EAAM,OAAQ,CAAC,EACpE,CAACY,EAAOC,CAAQ,KAAI,YAASb,EAAM,OAAS,EAAE,EAC9C,CAACc,EAAYC,CAAa,KAAI,YAAkC,IAAI,EACpE,CAACC,EAAWC,CAAY,KAAI,YAAS,EAAK,EAC1C,CAACC,EAAuBC,CAAwB,KAAI,YAAS,EAAK,EAClE,CAACC,EAAcC,CAAe,KAAI,YAAwB,IAAI,EAC9D,CAACC,EAAYC,CAAa,KAAI,YAAoC,IAAI,EACtEC,EAAQ,IAAM,CACdR,GAAaN,EAAK,aACtBA,EAAK,MAAM,EACXV,EAAM,eAAe,EAAK,EAC1BO,EAAQ,MAAM,EAChB,EACMkB,EACJ,OAAO,cAAkB,KAAe,OAAO,kBAAsB,IACjEC,KAAS,UAAuB,IAAI,EACpCC,KAAc,UAA2B,IAAI,EAwCnD,MAvCA,aAAU,IAAM,CACd,GAAI,GAAClB,GAAS,OAAO,SAAa,KAClC,OAAAkB,EAAY,QACV,SAAS,yBAAyB,YAAc,SAAS,cAAgB,KAC3ED,EAAO,SAAS,cAA2B,UAAU,GAAG,MAAM,EACvD,IAAMC,EAAY,SAAS,MAAM,CAC1C,EAAG,CAAClB,CAAK,CAAC,KACV,aAAU,IAAM,CACTA,IACLC,EAAK,KAAK,EACVA,EAAK,SAAS,CAAE,QAAS,GAAI,KAAMV,EAAM,aAAe,QAAS,GAAGA,EAAM,OAAQ,CAAC,EACnFa,EAASb,EAAM,OAAS,EAAE,EAC1Be,EAAc,IAAI,EAClBI,EAAyB,EAAK,EAC9BE,EAAgB,IAAI,EAGtB,EAAG,CAACZ,CAAK,CAAC,KACV,aAAU,IAAM,CACd,GAAI,CAACA,GAAS,CAACF,EAAQ,OAAQ,OAC/B,IAAIqB,EAAO,GACX,OAAKrB,EAAQ,OACV,WAAW,EACX,KAAMsB,GAAU,CACXD,GAAML,EAAcM,CAAK,CAC/B,CAAC,EACA,MAAM,IAAM,CACPD,GAAML,EAAc,IAAI,CAC9B,CAAC,EACI,IAAM,CACXK,EAAO,EACT,CACF,EAAG,CAACrB,EAAQ,OAAQE,CAAK,CAAC,KAC1B,aAAU,IAAM,CACTa,GACDZ,EAAK,MAAM,MAAQ,CAACY,EAAW,cAAc,MAAM,SAASZ,EAAK,MAAM,IAAI,GAC7EA,EAAK,SAAS,CAAE,GAAGA,EAAK,MAAO,KAAMY,EAAW,cAAc,WAAY,CAAC,CAE/E,EAAG,CAACA,EAAYZ,EAAK,SAAUA,EAAK,KAAK,CAAC,EACtC,CAACD,GAAS,OAAO,SAAa,IAAa,OAAO,KACtD,IAAMqB,EAAY9B,EAAM,WAAasB,GAAY,cAAc,iBAAmB,WAC5ES,EAAeT,GAAY,cAAc,OAAS,CACtD,MACA,OACA,SACA,WACA,OACF,EACMU,GACHhC,EAAM,iBAAmBsB,GAAY,cAAc,kBAAoB,KACxEG,GACA,CAACP,EACGe,EAAS,MAAOC,GAAqB,CAIzC,GAHAA,EAAM,eAAe,EAGjBlB,EAAW,OACf,IAAMa,EAAQ,CACZ,GAAGnB,EAAK,MACR,YAAaI,EAAa,CAACA,CAAU,EAAIJ,EAAK,MAAM,WACtD,EACA,GAAI,CACFW,EAAgB,IAAI,EACpB,MAAMrB,EAAM,eAAe6B,EAAOjB,EAAM,KAAK,CAAC,EAC9CF,EAAK,SAASmB,CAAK,GACJ,MAAMnB,EAAK,OAAOmB,CAAK,GAC3B,SAAW,YAAY,WAAWL,EAAO,GAAG,CACzD,OAASW,EAAQ,CACfd,EAAgBc,aAAkB,MAAQA,EAAO,QAAU,gCAAgC,CAC7F,CACF,EACMC,KACJ,QAAC,OACC,UAAU,eACV,iBAAgBpC,EAAM,QAAU,OAChC,IAAKA,EAAM,UACX,UAAYkC,GAAU,CAEpB,GADIA,EAAM,MAAQ,UAAY,CAAClB,GAAa,CAACN,EAAK,YAAYc,EAAM,EAChEU,EAAM,MAAQ,MAAO,CACvB,IAAMG,EAAY,CAChB,GAAIX,EAAO,SAAS,iBAClB,sIACF,GAAK,CAAC,CACR,EAAE,OAAQY,GAAS,CAACA,EAAK,MAAM,EACzBC,EAAQF,EAAU,CAAC,EACnBG,EAAOH,EAAU,GAAG,EAAE,EACxBH,EAAM,UAAY,SAAS,gBAAkBK,GAC/CL,EAAM,eAAe,EACrBM,GAAM,MAAM,GACH,CAACN,EAAM,UAAY,SAAS,gBAAkBM,IACvDN,EAAM,eAAe,EACrBK,GAAO,MAAM,EAEjB,CACF,EAEA,oBAAC,SAAO,SAAAjC,EAAO,KACf,QAAC,OACC,IAAKoB,EACL,MAAO1B,EAAM,MACb,UAAW,cAAcA,EAAM,YAAc,QAAU,sBAAwB,EAAE,GACjF,KAAK,SACL,aAAW,OACX,kBAAiBQ,EAEjB,oBAAC,MAAG,GAAIA,EAAU,SAAAR,EAAM,OAAS,gBAAgB,EAChDA,EAAM,aAAesB,GAAY,eAChC,QAAC,KAAE,UAAU,kBAAkB,wBACjBtB,EAAM,aAAesB,GAAY,aAC/C,EACE,KACHtB,EAAM,YAAW,OAAC,KAAE,UAAU,eAAgB,SAAAA,EAAM,SAAS,EAAO,QACrE,QAAC,QAAK,SAAWkC,GAAU,KAAKD,EAAOC,CAAK,EAC1C,qBAAC,SAAM,UAAU,YACf,oBAAC,QAAK,2CAA+B,KACrC,OAAC,YACC,SAAQ,GACR,MAAOxB,EAAK,MAAM,QAClB,SAAWwB,GAAUxB,EAAK,SAAS,CAAE,GAAGA,EAAK,MAAO,QAASwB,EAAM,OAAO,KAAM,CAAC,EACnF,GACF,KACA,QAAC,SAAM,UAAU,YACf,oBAAC,QAAK,gBAAI,KACV,OAAC,UACC,MAAOxB,EAAK,MAAM,KAClB,SAAWwB,GACTxB,EAAK,SAAS,CAAE,GAAGA,EAAK,MAAO,KAAMwB,EAAM,OAAO,KAAsB,CAAC,EAG1E,SAAAH,EAAa,IAAKU,MACjB,OAAC,UAAmB,SAAAA,GAAPA,CAAY,CAC1B,EACH,GACF,EACCX,IAAc,SACb,QAAC,SAAM,UAAU,YACf,qBAAC,QAAK,8BAAkBA,IAAc,WAAa,GAAK,eAAc,KACtE,OAAC,SACC,KAAK,QACL,aAAa,QACb,SAAUA,IAAc,WACxB,MAAOlB,EACP,SAAWsB,GAAUrB,EAASqB,EAAM,OAAO,KAAK,EAClD,KACA,OAAC,SAAM,oDAAwC,GACjD,EACE,KACHF,KACC,OAAC,OAAI,UAAU,YACb,mBAAC,UACC,KAAK,SACL,SAAUhB,EACV,QAAS,IAAM,CACbC,EAAa,EAAI,EACZV,EAAQ,QACT,kBAAkB,CAClB,GAAGP,EAAM,kBACT,UAAWA,EAAM,WAAaA,EAAM,mBAAmB,SACzD,CAAC,EACA,KAAM0C,GAAS,CACd3B,EAAc2B,CAAI,EACbA,GAAMvB,EAAyB,EAAI,CAC1C,CAAC,EACA,MAAM,IAAM,CACXJ,EAAc,IAAI,EAClBI,EAAyB,EAAI,CAC/B,CAAC,EACA,QAAQ,IAAMF,EAAa,EAAK,CAAC,CACtC,EAEC,SAAAD,EACG,kCACAF,EACE,sBACA,oBACR,EACF,EACE,QACJ,QAAC,WAAQ,UAAU,cACjB,oBAAC,WAAQ,4BAAgB,KACzB,OAAC,OACE,cAAK,UACJ,CACE,QAASP,EAAQ,QAAQ,YAAY,GAAK,CAAC,EAC3C,YAAaA,EAAQ,QAAQ,gBAAgB,GAAK,CAAC,EACnD,YAAaO,EACT,CAAC,CAAE,YAAaA,EAAW,YAAa,KAAMA,EAAW,IAAK,CAAC,EAC/D,CAAC,CACP,EACA,KACA,CACF,EACF,GACF,KACA,OAAC,KAAE,UAAU,aAAa,KAAK,SAAS,YAAU,SAC/C,SAAAM,EACG,mBAAmBA,CAAY,GAC/BF,EACE,yDACAR,EAAK,OACHA,EAAK,OAAO,SAAW,WACrB,iBACAA,EAAK,OAAO,SAAW,SACrB,2BACA,mBAAmBA,EAAK,OAAO,MAAM,GACzC,GACV,KACA,QAAC,OAAI,UAAU,cACb,oBAAC,UAAO,KAAK,SAAS,QAASc,EAAO,SAAUR,GAAaN,EAAK,WAAY,kBAE9E,KACA,OAAC,UACC,UAAU,cACV,KAAK,SACL,SAAUA,EAAK,YAAcM,GAAa,CAACT,EAAQ,OACpD,gBAED,GACF,GACF,GACF,GACF,EAEF,SAAO,gBAAa6B,EAAM,SAAS,IAAI,CACzC","names":["index_exports","__export","FeedbackButton","FeedbackDialog","FeedbackProvider","useFeedback","useFeedbackForm","__toCommonJS","import_react","import_react_dom","import_feedback","import_jsx_runtime","Context","configSignature","config","provided","children","owned","setOwned","openState","setOpen","ownedRef","activeEffect","signature","token","previous","record","client","open","close","value","input","identity","verification","options","initial","context","setValue","result","setResult","state","setState","descriptor","setDescriptor","attachment","setAttachment","inFlight","transition","next","current","active","submit","override","unavailable","blocked","submission","withAttachment","request","key","FeedbackButton","children","floating","withDialog","dialogProps","renderTrigger","style","props","open","openState","useFeedback","triggerProps","FeedbackDialog","styles","context","titleId","shown","form","useFeedbackForm","email","setEmail","attachment","setAttachment","capturing","setCapturing","screenshotUnavailable","setScreenshotUnavailable","localFailure","setLocalFailure","descriptor","setDescriptor","close","screenshotCapable","dialog","returnFocus","live","value","emailMode","allowedKinds","allowScreenshot","submit","event","caught","node","focusable","item","first","last","kind","next"]}
@@ -0,0 +1,86 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, CSSProperties, PropsWithChildren } from 'react';
3
+ import { FeedbackInput, FeedbackKind, FeedbackBrowserClient, FeedbackResult, FeedbackFlowState, FeedbackDescriptor, AttachmentHandle, FeedbackClientConfig } from '@stella-loop/feedback';
4
+ export { FEEDBACK_FLOW_EDGES, FEEDBACK_FLOW_STATES, FeedbackBrowserClient, FeedbackClientConfig, FeedbackInput, FeedbackResult } from '@stella-loop/feedback';
5
+
6
+ type FeedbackContextValue = {
7
+ client: FeedbackBrowserClient | null;
8
+ open(): void;
9
+ close(): void;
10
+ openDialog(): void;
11
+ openState: boolean;
12
+ submit(input: FeedbackInput): Promise<FeedbackResult>;
13
+ submitOrThrow(input: FeedbackInput): Promise<{
14
+ submissionId: string;
15
+ }>;
16
+ identify: FeedbackBrowserClient["identify"];
17
+ clearIdentity(): Promise<void>;
18
+ peekContext(): ReturnType<FeedbackBrowserClient["peekContext"]> | undefined;
19
+ captureScreenshot: FeedbackBrowserClient["captureScreenshot"];
20
+ queueState(): ReturnType<FeedbackBrowserClient["queueState"]>;
21
+ };
22
+ type FeedbackProviderProps = PropsWithChildren<{
23
+ client?: FeedbackBrowserClient;
24
+ config?: FeedbackClientConfig;
25
+ }>;
26
+ declare function FeedbackProvider({ client: provided, config, children, }: FeedbackProviderProps): ReactNode;
27
+ declare function useFeedback(): FeedbackContextValue;
28
+ type FeedbackFormBinding = {
29
+ state: FeedbackFlowState;
30
+ value: FeedbackInput;
31
+ descriptor: FeedbackDescriptor | null;
32
+ attachment: AttachmentHandle | null;
33
+ result: FeedbackResult | null;
34
+ submitting: boolean;
35
+ queue: ReturnType<FeedbackBrowserClient["queueState"]>;
36
+ open(): void;
37
+ close(): void;
38
+ setField<Key extends keyof FeedbackInput>(key: Key, value: FeedbackInput[Key]): void;
39
+ setValue(value: FeedbackInput): void;
40
+ attachScreenshot(options?: Parameters<FeedbackBrowserClient["captureScreenshot"]>[0]): Promise<AttachmentHandle | null>;
41
+ removeAttachment(): void;
42
+ submit(override?: FeedbackInput): Promise<FeedbackResult>;
43
+ reset(): void;
44
+ };
45
+ declare function useFeedbackForm(initial?: Partial<FeedbackInput>): FeedbackFormBinding;
46
+
47
+ type FeedbackButtonProps = {
48
+ children?: ReactNode;
49
+ className?: string;
50
+ style?: CSSProperties;
51
+ floating?: boolean;
52
+ withDialog?: boolean;
53
+ dialogProps?: Omit<FeedbackDialogProps, "open" | "onOpenChange">;
54
+ renderTrigger?(props: {
55
+ onClick(): void;
56
+ "aria-haspopup": "dialog";
57
+ "aria-expanded": boolean;
58
+ }): ReactNode;
59
+ };
60
+ declare function FeedbackButton({ children, floating, withDialog, dialogProps, renderTrigger, style, ...props }: FeedbackButtonProps): react.JSX.Element;
61
+ type FeedbackDialogProps = {
62
+ open?: boolean;
63
+ onOpenChange?(open: boolean): void;
64
+ title?: string;
65
+ destination?: string;
66
+ placement?: "center" | "right";
67
+ initial?: Partial<FeedbackInput>;
68
+ defaultKind?: FeedbackKind;
69
+ /** Optional account address offered for this submission. It is never sent
70
+ * unless the submitter leaves it in the field and `beforeSubmit` identifies
71
+ * it on the client. */
72
+ email?: string;
73
+ emailMode?: "off" | "optional" | "required";
74
+ contrast?: string;
75
+ allowScreenshot?: boolean;
76
+ screenshotOptions?: Parameters<FeedbackBrowserClient["captureScreenshot"]>[0];
77
+ /** Public integration seam for server-minted identity verification. */
78
+ beforeSubmit?(value: FeedbackInput, email: string): void | Promise<void>;
79
+ /** Map a host design system onto the documented --slf-* custom properties. */
80
+ theme?: CSSProperties;
81
+ scheme?: "light" | "dark" | "auto";
82
+ direction?: "ltr" | "rtl";
83
+ };
84
+ declare function FeedbackDialog(props: FeedbackDialogProps): react.ReactPortal | null;
85
+
86
+ export { FeedbackButton, type FeedbackButtonProps, type FeedbackContextValue, FeedbackDialog, type FeedbackDialogProps, type FeedbackFormBinding, FeedbackProvider, type FeedbackProviderProps, useFeedback, useFeedbackForm };
@@ -0,0 +1,86 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, CSSProperties, PropsWithChildren } from 'react';
3
+ import { FeedbackInput, FeedbackKind, FeedbackBrowserClient, FeedbackResult, FeedbackFlowState, FeedbackDescriptor, AttachmentHandle, FeedbackClientConfig } from '@stella-loop/feedback';
4
+ export { FEEDBACK_FLOW_EDGES, FEEDBACK_FLOW_STATES, FeedbackBrowserClient, FeedbackClientConfig, FeedbackInput, FeedbackResult } from '@stella-loop/feedback';
5
+
6
+ type FeedbackContextValue = {
7
+ client: FeedbackBrowserClient | null;
8
+ open(): void;
9
+ close(): void;
10
+ openDialog(): void;
11
+ openState: boolean;
12
+ submit(input: FeedbackInput): Promise<FeedbackResult>;
13
+ submitOrThrow(input: FeedbackInput): Promise<{
14
+ submissionId: string;
15
+ }>;
16
+ identify: FeedbackBrowserClient["identify"];
17
+ clearIdentity(): Promise<void>;
18
+ peekContext(): ReturnType<FeedbackBrowserClient["peekContext"]> | undefined;
19
+ captureScreenshot: FeedbackBrowserClient["captureScreenshot"];
20
+ queueState(): ReturnType<FeedbackBrowserClient["queueState"]>;
21
+ };
22
+ type FeedbackProviderProps = PropsWithChildren<{
23
+ client?: FeedbackBrowserClient;
24
+ config?: FeedbackClientConfig;
25
+ }>;
26
+ declare function FeedbackProvider({ client: provided, config, children, }: FeedbackProviderProps): ReactNode;
27
+ declare function useFeedback(): FeedbackContextValue;
28
+ type FeedbackFormBinding = {
29
+ state: FeedbackFlowState;
30
+ value: FeedbackInput;
31
+ descriptor: FeedbackDescriptor | null;
32
+ attachment: AttachmentHandle | null;
33
+ result: FeedbackResult | null;
34
+ submitting: boolean;
35
+ queue: ReturnType<FeedbackBrowserClient["queueState"]>;
36
+ open(): void;
37
+ close(): void;
38
+ setField<Key extends keyof FeedbackInput>(key: Key, value: FeedbackInput[Key]): void;
39
+ setValue(value: FeedbackInput): void;
40
+ attachScreenshot(options?: Parameters<FeedbackBrowserClient["captureScreenshot"]>[0]): Promise<AttachmentHandle | null>;
41
+ removeAttachment(): void;
42
+ submit(override?: FeedbackInput): Promise<FeedbackResult>;
43
+ reset(): void;
44
+ };
45
+ declare function useFeedbackForm(initial?: Partial<FeedbackInput>): FeedbackFormBinding;
46
+
47
+ type FeedbackButtonProps = {
48
+ children?: ReactNode;
49
+ className?: string;
50
+ style?: CSSProperties;
51
+ floating?: boolean;
52
+ withDialog?: boolean;
53
+ dialogProps?: Omit<FeedbackDialogProps, "open" | "onOpenChange">;
54
+ renderTrigger?(props: {
55
+ onClick(): void;
56
+ "aria-haspopup": "dialog";
57
+ "aria-expanded": boolean;
58
+ }): ReactNode;
59
+ };
60
+ declare function FeedbackButton({ children, floating, withDialog, dialogProps, renderTrigger, style, ...props }: FeedbackButtonProps): react.JSX.Element;
61
+ type FeedbackDialogProps = {
62
+ open?: boolean;
63
+ onOpenChange?(open: boolean): void;
64
+ title?: string;
65
+ destination?: string;
66
+ placement?: "center" | "right";
67
+ initial?: Partial<FeedbackInput>;
68
+ defaultKind?: FeedbackKind;
69
+ /** Optional account address offered for this submission. It is never sent
70
+ * unless the submitter leaves it in the field and `beforeSubmit` identifies
71
+ * it on the client. */
72
+ email?: string;
73
+ emailMode?: "off" | "optional" | "required";
74
+ contrast?: string;
75
+ allowScreenshot?: boolean;
76
+ screenshotOptions?: Parameters<FeedbackBrowserClient["captureScreenshot"]>[0];
77
+ /** Public integration seam for server-minted identity verification. */
78
+ beforeSubmit?(value: FeedbackInput, email: string): void | Promise<void>;
79
+ /** Map a host design system onto the documented --slf-* custom properties. */
80
+ theme?: CSSProperties;
81
+ scheme?: "light" | "dark" | "auto";
82
+ direction?: "ltr" | "rtl";
83
+ };
84
+ declare function FeedbackDialog(props: FeedbackDialogProps): react.ReactPortal | null;
85
+
86
+ export { FeedbackButton, type FeedbackButtonProps, type FeedbackContextValue, FeedbackDialog, type FeedbackDialogProps, type FeedbackFormBinding, FeedbackProvider, type FeedbackProviderProps, useFeedback, useFeedbackForm };
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import{createContext as V,createElement as z,useCallback as D,useContext as L,useEffect as E,useId as H,useMemo as _,useRef as q,useState as k}from"react";import{createPortal as W}from"react-dom";import{createFeedbackClient as J,FEEDBACK_FLOW_EDGES as te,FEEDBACK_FLOW_STATES as ne,isFeedbackFlowEdge as $}from"@stella-loop/feedback";import{Fragment as Q,jsx as c,jsxs as v}from"react/jsx-runtime";var K=V(null);function U(e){return e?JSON.stringify({endpoint:e.endpoint,key:e.key,app:e.app,device:e.device,locale:e.locale,route:e.route,customContext:e.customContext,collectors:e.collectors,diagnostics:e.diagnostics,queue:e.queue,failFast:e.failFast,discoverable:e.discoverable}):""}function ae({client:e,config:s,children:f}){let[b,t]=k(null),[F,h]=k(!1),i=q(void 0),u=q(null),d=U(s);E(()=>{let l=Symbol("feedback-provider-effect");if(u.current=l,e)return t(null),()=>{u.current===l&&(u.current=null),e.flush()};if(!s){t(null);return}let o=i.current,a=o?.signature===d?o:{client:J(s),signature:d};return i.current=a,t(a.client),o&&o!==a&&o.client.flush().finally(()=>o.client.destroy()),()=>{u.current===l&&(u.current=null),a.client.flush().finally(()=>{queueMicrotask(()=>{u.current===null&&i.current===a&&(a.client.destroy(),i.current=void 0)})})}},[e,d,s?.scrubContext,s?.scrubDiagnostics]);let p=e??b,g=D(()=>h(!0),[]),y=D(()=>h(!1),[]),C=_(()=>({client:p,open:g,close:y,openDialog:g,openState:F,submit:async l=>p?.submit(l)??{status:"failed",reason:"client_initializing"},submitOrThrow:async l=>{if(!p)throw new Error("Feedback client is still initializing.");return await p.submitOrThrow(l)},identify:(l,o)=>p?.identify(l,o),clearIdentity:async()=>await p?.clearIdentity(),peekContext:()=>p?.peekContext(),captureScreenshot:async l=>await p?.captureScreenshot(l)??null,queueState:()=>p?.queueState()??{pending:0}}),[p,y,g,F]);return z(K.Provider,{value:C},f)}function B(){let e=L(K);if(!e)throw new Error("useFeedback must be used inside <FeedbackProvider>. See https://github.com/Jacq-OS/stella-loop/tree/main/packages/feedback-react#readme for setup.");return e}function j(e={}){let s=B(),{client:f}=s,[b,t]=k({message:"",kind:"other",...e}),[F,h]=k(null),[i,u]=k("idle"),[d,p]=k(null),[g,y]=k(null),C=q(null),l=D(a=>{u(r=>r===a||$(r,a)?a:r)},[]);E(()=>{if(!f)return;let a=!0;return f.descriptor().then(r=>{a&&(p(r),t(x=>({...x,kind:x.kind&&r.capturePolicy.kinds.includes(x.kind)?x.kind:r.capturePolicy.defaultKind})))}).catch(()=>{a&&p(null)}),()=>{a=!1}},[f]);let o=D(async a=>{if(!f){let m={status:"failed",reason:"client_initializing"};return h(m),m}if(C.current)return await C.current;if(i==="capturing"){let m={status:"failed",reason:"capture_in_progress"};return h(m),m}l("submitting");let r=a??b,x={...r,attachments:g?[g]:r.attachments},w=f.submit(x).then(m=>(h(m),u(m.status==="accepted"?"success":m.status==="queued"?"queued":"failure"),m)).finally(()=>{C.current=null});return C.current=w,await w},[g,f,i,l,b]);return{state:i,value:b,setValue(a){t(a),i==="failure"&&l("composing")},setField(a,r){t(x=>({...x,[a]:r})),i==="failure"&&l("composing")},descriptor:d,attachment:g,result:F,submitting:i==="submitting",submit:o,queue:f?.queueState()??{pending:0},open(){i==="idle"&&(l("open"),s.open(),queueMicrotask(()=>l("composing")))},close(){["open","composing","success","queued"].includes(i)&&u("idle"),s.close()},async attachScreenshot(a){if(!f||i!=="composing")return null;l("capturing");try{let r=await f.captureScreenshot(a);return y(r),r}catch{return y(null),null}finally{l("composing")}},removeAttachment(){y(null)},reset(){t({message:"",kind:d?.capturePolicy.defaultKind??"other",...e}),y(null),h(null),u(s.openState?"composing":"idle")}}}function le({children:e="Send feedback",floating:s=!1,withDialog:f=!0,dialogProps:b,renderTrigger:t,style:F,...h}){let{open:i,openState:u}=B(),d={onClick:i,"aria-haspopup":"dialog","aria-expanded":u};return v(Q,{children:[t?t(d):c("button",{type:"button",...d,...h,style:s?{position:"fixed",insetInlineEnd:20,bottom:20,...F}:F,children:e}),f?c(X,{...b}):null]})}var G=`
2
+ .slf-backdrop{position:fixed;inset:0;z-index:var(--slf-z,2147000000);background:rgb(0 0 0/.42);display:grid;place-items:center}.slf-dialog{--slf-bg:#fff;--slf-fg:#171717;--slf-muted:#666;--slf-border:#d4d4d4;--slf-accent:#12665f;--slf-accent-fg:#fff;box-sizing:border-box;width:min(92vw,500px);max-height:90vh;overflow:auto;border:1px solid var(--slf-border);border-radius:var(--slf-radius,12px);background:var(--slf-bg);color:var(--slf-fg);padding:20px;font:var(--slf-font,14px/1.45 system-ui,sans-serif)}.slf-backdrop[data-slf-theme=dark] .slf-dialog{--slf-bg:#171717;--slf-fg:#f5f5f5;--slf-muted:#aaa;--slf-border:#525252;--slf-accent:#4eaaa1;--slf-accent-fg:#081715}.slf-dialog h2{margin:0 0 4px;font-size:16px}.slf-destination,.slf-contrast,.slf-status,.slf-details{color:var(--slf-muted);font-size:12px}.slf-field{display:grid;gap:6px;margin:12px 0;text-align:start}.slf-field textarea,.slf-field select,.slf-field input{box-sizing:border-box;width:100%;border:1px solid var(--slf-border);border-radius:var(--slf-control-radius,6px);background:var(--slf-bg);color:inherit;padding:9px}.slf-field textarea{min-height:130px}.slf-actions,.slf-tools{display:flex;justify-content:flex-end;gap:8px}.slf-tools{justify-content:flex-start;margin:10px 0}.slf-actions button,.slf-tools button{border:1px solid var(--slf-border);border-radius:var(--slf-control-radius,6px);background:transparent;color:inherit;padding:8px 12px}.slf-actions .slf-primary{background:var(--slf-accent);border-color:var(--slf-accent);color:var(--slf-accent-fg)}.slf-status{min-height:20px}.slf-details pre{white-space:pre-wrap;word-break:break-word}.slf-placement-right{margin-inline-start:auto;height:100%;max-height:100vh;border-radius:0}@media(prefers-color-scheme:dark){.slf-backdrop[data-slf-theme=auto] .slf-dialog{--slf-bg:#171717;--slf-fg:#f5f5f5;--slf-muted:#aaa;--slf-border:#525252;--slf-accent:#4eaaa1;--slf-accent-fg:#081715}}@media(prefers-reduced-motion:reduce){.slf-dialog{scroll-behavior:auto}}
3
+ `;function X(e){let s=B(),f=H(),b=e.open??s.openState,t=j({kind:e.defaultKind,...e.initial}),[F,h]=k(e.email??""),[i,u]=k(null),[d,p]=k(!1),[g,y]=k(!1),[C,l]=k(null),[o,a]=k(null),r=()=>{d||t.submitting||(t.close(),e.onOpenChange?.(!1),s.close())},x=typeof XMLSerializer<"u"&&typeof HTMLCanvasElement<"u",w=q(null),m=q(null);if(E(()=>{if(!(!b||typeof document>"u"))return m.current=document.activeElement instanceof HTMLElement?document.activeElement:null,w.current?.querySelector("textarea")?.focus(),()=>m.current?.focus()},[b]),E(()=>{b&&(t.open(),t.setValue({message:"",kind:e.defaultKind??"other",...e.initial}),h(e.email??""),u(null),y(!1),l(null))},[b]),E(()=>{if(!b||!s.client)return;let n=!0;return s.client.descriptor().then(S=>{n&&a(S)}).catch(()=>{n&&a(null)}),()=>{n=!1}},[s.client,b]),E(()=>{o&&t.value.kind&&!o.capturePolicy.kinds.includes(t.value.kind)&&t.setValue({...t.value,kind:o.capturePolicy.defaultKind})},[o,t.setValue,t.value]),!b||typeof document>"u")return null;let I=e.emailMode??o?.capturePolicy.emailCollection??"optional",N=o?.capturePolicy.kinds??["bug","idea","praise","question","other"],O=(e.allowScreenshot??o?.capturePolicy.allowAttachments??!1)&&x&&!g,T=async n=>{if(n.preventDefault(),d)return;let S={...t.value,attachments:i?[i]:t.value.attachments};try{l(null),await e.beforeSubmit?.(S,F.trim()),t.setValue(S),(await t.submit(S)).status==="accepted"&&setTimeout(r,800)}catch(P){l(P instanceof Error?P.message:"Submission preparation failed.")}},A=v("div",{className:"slf-backdrop","data-slf-theme":e.scheme??"auto",dir:e.direction,onKeyDown:n=>{if(n.key==="Escape"&&!d&&!t.submitting&&r(),n.key==="Tab"){let S=[...w.current?.querySelectorAll("button:not([disabled]),input:not([disabled]),textarea:not([disabled]),select:not([disabled]),summary,[tabindex]:not([tabindex='-1'])")??[]].filter(M=>!M.hidden),P=S[0],R=S.at(-1);n.shiftKey&&document.activeElement===P?(n.preventDefault(),R?.focus()):!n.shiftKey&&document.activeElement===R&&(n.preventDefault(),P?.focus())}},children:[c("style",{children:G}),v("div",{ref:w,style:e.theme,className:`slf-dialog ${e.placement==="right"?"slf-placement-right":""}`,role:"dialog","aria-modal":"true","aria-labelledby":f,children:[c("h2",{id:f,children:e.title??"Send feedback"}),e.destination||o?.publicLabel?v("p",{className:"slf-destination",children:["Sending to ",e.destination??o?.publicLabel]}):null,e.contrast?c("p",{className:"slf-contrast",children:e.contrast}):null,v("form",{onSubmit:n=>void T(n),children:[v("label",{className:"slf-field",children:[c("span",{children:"What would you like us to know?"}),c("textarea",{required:!0,value:t.value.message,onChange:n=>t.setValue({...t.value,message:n.target.value})})]}),v("label",{className:"slf-field",children:[c("span",{children:"Kind"}),c("select",{value:t.value.kind,onChange:n=>t.setValue({...t.value,kind:n.target.value}),children:N.map(n=>c("option",{children:n},n))})]}),I!=="off"?v("label",{className:"slf-field",children:[v("span",{children:["Email for a reply",I==="required"?"":" (optional)"]}),c("input",{type:"email",autoComplete:"email",required:I==="required",value:F,onChange:n=>h(n.target.value)}),c("small",{children:"Remove this address to send anonymously."})]}):null,O?c("div",{className:"slf-tools",children:c("button",{type:"button",disabled:d,onClick:()=>{p(!0),s.client?.captureScreenshot({...e.screenshotOptions,direction:e.direction??e.screenshotOptions?.direction}).then(n=>{u(n),n||y(!0)}).catch(()=>{u(null),y(!0)}).finally(()=>p(!1))},children:d?"Opening screenshot editor\u2026":i?"Screenshot attached":"Attach screenshot"})}):null,v("details",{className:"slf-details",children:[c("summary",{children:"Details included"}),c("pre",{children:JSON.stringify({context:s.client?.peekContext()??{},diagnostics:s.client?.peekDiagnostics()??[],attachments:i?[{contentType:i.contentType,size:i.size}]:[]},null,2)})]}),c("p",{className:"slf-status",role:"status","aria-live":"polite",children:C?`Could not send: ${C}`:g?"Screenshot capture is unavailable in this environment.":t.result?t.result.status==="accepted"?"Feedback sent.":t.result.status==="queued"?"Saved offline for retry.":`Could not send: ${t.result.reason}`:""}),v("div",{className:"slf-actions",children:[c("button",{type:"button",onClick:r,disabled:d||t.submitting,children:"Cancel"}),c("button",{className:"slf-primary",type:"submit",disabled:t.submitting||d||!s.client,children:"Send"})]})]})]})]});return W(A,document.body)}export{te as FEEDBACK_FLOW_EDGES,ne as FEEDBACK_FLOW_STATES,le as FeedbackButton,X as FeedbackDialog,ae as FeedbackProvider,B as useFeedback,j as useFeedbackForm};
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type FormEvent,\n type PropsWithChildren,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport {\n createFeedbackClient,\n type FeedbackBrowserClient,\n type FeedbackClientConfig,\n type FeedbackInput,\n type FeedbackKind,\n type FeedbackResult,\n type AttachmentHandle,\n type FeedbackDescriptor,\n FEEDBACK_FLOW_EDGES,\n FEEDBACK_FLOW_STATES,\n isFeedbackFlowEdge,\n type FeedbackFlowState,\n} from \"@stella-loop/feedback\";\n\nexport type FeedbackContextValue = {\n client: FeedbackBrowserClient | null;\n open(): void;\n close(): void;\n openDialog(): void;\n openState: boolean;\n submit(input: FeedbackInput): Promise<FeedbackResult>;\n submitOrThrow(input: FeedbackInput): Promise<{ submissionId: string }>;\n identify: FeedbackBrowserClient[\"identify\"];\n clearIdentity(): Promise<void>;\n peekContext(): ReturnType<FeedbackBrowserClient[\"peekContext\"]> | undefined;\n captureScreenshot: FeedbackBrowserClient[\"captureScreenshot\"];\n queueState(): ReturnType<FeedbackBrowserClient[\"queueState\"]>;\n};\nconst Context = createContext<FeedbackContextValue | null>(null);\n\nexport type FeedbackProviderProps = PropsWithChildren<{\n client?: FeedbackBrowserClient;\n config?: FeedbackClientConfig;\n}>;\n\nfunction configSignature(config: FeedbackClientConfig | undefined): string {\n if (!config) return \"\";\n return JSON.stringify({\n endpoint: config.endpoint,\n key: config.key,\n app: config.app,\n device: config.device,\n locale: config.locale,\n route: config.route,\n customContext: config.customContext,\n collectors: config.collectors,\n diagnostics: config.diagnostics,\n queue: config.queue,\n failFast: config.failFast,\n discoverable: config.discoverable,\n });\n}\n\nexport function FeedbackProvider({\n client: provided,\n config,\n children,\n}: FeedbackProviderProps): ReactNode {\n const [owned, setOwned] = useState<FeedbackBrowserClient | null>(null);\n const [openState, setOpen] = useState(false);\n const ownedRef = useRef<{ client: FeedbackBrowserClient; signature: string } | undefined>(\n undefined,\n );\n const activeEffect = useRef<symbol | null>(null);\n const signature = configSignature(config);\n useEffect(() => {\n const token = Symbol(\"feedback-provider-effect\");\n activeEffect.current = token;\n if (provided) {\n setOwned(null);\n return () => {\n if (activeEffect.current === token) activeEffect.current = null;\n void provided.flush();\n };\n }\n if (!config) {\n setOwned(null);\n return;\n }\n const previous = ownedRef.current;\n const record =\n previous?.signature === signature\n ? previous\n : { client: createFeedbackClient(config), signature };\n ownedRef.current = record;\n setOwned(record.client);\n if (previous && previous !== record) {\n void previous.client.flush().finally(() => previous.client.destroy());\n }\n return () => {\n if (activeEffect.current === token) activeEffect.current = null;\n // React StrictMode immediately re-runs effects. The microtask check\n // preserves the same owned client across that probe while a real\n // unmount still flush-attempts before releasing instrumentation.\n void record.client.flush().finally(() => {\n queueMicrotask(() => {\n if (activeEffect.current === null && ownedRef.current === record) {\n record.client.destroy();\n ownedRef.current = undefined;\n }\n });\n });\n };\n }, [provided, signature, config?.scrubContext, config?.scrubDiagnostics]);\n const client = provided ?? owned;\n const open = useCallback(() => setOpen(true), []);\n const close = useCallback(() => setOpen(false), []);\n const value = useMemo(\n (): FeedbackContextValue => ({\n client,\n open,\n close,\n openDialog: open,\n openState,\n submit: async (input) =>\n client?.submit(input) ?? { status: \"failed\", reason: \"client_initializing\" },\n submitOrThrow: async (input) => {\n if (!client) throw new Error(\"Feedback client is still initializing.\");\n return await client.submitOrThrow(input);\n },\n identify: (identity, verification) => client?.identify(identity, verification),\n clearIdentity: async () => await client?.clearIdentity(),\n peekContext: () => client?.peekContext(),\n captureScreenshot: async (options) => (await client?.captureScreenshot(options)) ?? null,\n queueState: () => client?.queueState() ?? { pending: 0 },\n }),\n [client, close, open, openState],\n );\n return createElement(Context.Provider, { value }, children);\n}\n\nexport function useFeedback(): FeedbackContextValue {\n const value = useContext(Context);\n if (!value)\n throw new Error(\n \"useFeedback must be used inside <FeedbackProvider>. See https://github.com/Jacq-OS/stella-loop/tree/main/packages/feedback-react#readme for setup.\",\n );\n return value;\n}\n\nexport type FeedbackFormBinding = {\n state: FeedbackFlowState;\n value: FeedbackInput;\n descriptor: FeedbackDescriptor | null;\n attachment: AttachmentHandle | null;\n result: FeedbackResult | null;\n submitting: boolean;\n queue: ReturnType<FeedbackBrowserClient[\"queueState\"]>;\n open(): void;\n close(): void;\n setField<Key extends keyof FeedbackInput>(key: Key, value: FeedbackInput[Key]): void;\n setValue(value: FeedbackInput): void;\n attachScreenshot(\n options?: Parameters<FeedbackBrowserClient[\"captureScreenshot\"]>[0],\n ): Promise<AttachmentHandle | null>;\n removeAttachment(): void;\n submit(override?: FeedbackInput): Promise<FeedbackResult>;\n reset(): void;\n};\n\nexport function useFeedbackForm(initial: Partial<FeedbackInput> = {}): FeedbackFormBinding {\n const context = useFeedback();\n const { client } = context;\n const [value, setValue] = useState<FeedbackInput>({ message: \"\", kind: \"other\", ...initial });\n const [result, setResult] = useState<FeedbackResult | null>(null);\n const [state, setState] = useState<FeedbackFlowState>(\"idle\");\n const [descriptor, setDescriptor] = useState<FeedbackDescriptor | null>(null);\n const [attachment, setAttachment] = useState<AttachmentHandle | null>(null);\n const inFlight = useRef<Promise<FeedbackResult> | null>(null);\n const transition = useCallback((next: FeedbackFlowState) => {\n setState((current) => (current === next || isFeedbackFlowEdge(current, next) ? next : current));\n }, []);\n useEffect(() => {\n if (!client) return;\n let active = true;\n void client\n .descriptor()\n .then((next) => {\n if (!active) return;\n setDescriptor(next);\n setValue((current) => ({\n ...current,\n kind:\n current.kind && next.capturePolicy.kinds.includes(current.kind)\n ? current.kind\n : next.capturePolicy.defaultKind,\n }));\n })\n .catch(() => {\n if (active) setDescriptor(null);\n });\n return () => {\n active = false;\n };\n }, [client]);\n const submit = useCallback(\n async (override?: FeedbackInput) => {\n if (!client) {\n const unavailable: FeedbackResult = { status: \"failed\", reason: \"client_initializing\" };\n setResult(unavailable);\n return unavailable;\n }\n if (inFlight.current) return await inFlight.current;\n if (state === \"capturing\") {\n const blocked: FeedbackResult = { status: \"failed\", reason: \"capture_in_progress\" };\n setResult(blocked);\n return blocked;\n }\n transition(\"submitting\");\n const submission = override ?? value;\n const withAttachment = {\n ...submission,\n attachments: attachment ? [attachment] : submission.attachments,\n };\n const request = client\n .submit(withAttachment)\n .then((next) => {\n setResult(next);\n setState(\n next.status === \"accepted\"\n ? \"success\"\n : next.status === \"queued\"\n ? \"queued\"\n : \"failure\",\n );\n return next;\n })\n .finally(() => {\n inFlight.current = null;\n });\n inFlight.current = request;\n return await request;\n },\n [attachment, client, state, transition, value],\n );\n return {\n state,\n value,\n setValue(next) {\n setValue(next);\n if (state === \"failure\") transition(\"composing\");\n },\n setField(key, next) {\n setValue((current) => ({ ...current, [key]: next }));\n if (state === \"failure\") transition(\"composing\");\n },\n descriptor,\n attachment,\n result,\n submitting: state === \"submitting\",\n submit,\n queue: client?.queueState() ?? { pending: 0 },\n open() {\n if (state !== \"idle\") return;\n transition(\"open\");\n context.open();\n queueMicrotask(() => transition(\"composing\"));\n },\n close() {\n if ([\"open\", \"composing\", \"success\", \"queued\"].includes(state)) setState(\"idle\");\n context.close();\n },\n async attachScreenshot(options) {\n if (!client || state !== \"composing\") return null;\n transition(\"capturing\");\n try {\n const next = await client.captureScreenshot(options);\n setAttachment(next);\n return next;\n } catch {\n setAttachment(null);\n return null;\n } finally {\n transition(\"composing\");\n }\n },\n removeAttachment() {\n setAttachment(null);\n },\n reset() {\n setValue({ message: \"\", kind: descriptor?.capturePolicy.defaultKind ?? \"other\", ...initial });\n setAttachment(null);\n setResult(null);\n setState(context.openState ? \"composing\" : \"idle\");\n },\n };\n}\n\nexport { FEEDBACK_FLOW_EDGES, FEEDBACK_FLOW_STATES };\n\nexport type FeedbackButtonProps = {\n children?: ReactNode;\n className?: string;\n style?: CSSProperties;\n floating?: boolean;\n withDialog?: boolean;\n dialogProps?: Omit<FeedbackDialogProps, \"open\" | \"onOpenChange\">;\n renderTrigger?(props: {\n onClick(): void;\n \"aria-haspopup\": \"dialog\";\n \"aria-expanded\": boolean;\n }): ReactNode;\n};\nexport function FeedbackButton({\n children = \"Send feedback\",\n floating = false,\n withDialog = true,\n dialogProps,\n renderTrigger,\n style,\n ...props\n}: FeedbackButtonProps) {\n const { open, openState } = useFeedback();\n const triggerProps = {\n onClick: open,\n \"aria-haspopup\": \"dialog\" as const,\n \"aria-expanded\": openState,\n };\n return (\n <>\n {renderTrigger ? (\n renderTrigger(triggerProps)\n ) : (\n <button\n type=\"button\"\n {...triggerProps}\n {...props}\n style={floating ? { position: \"fixed\", insetInlineEnd: 20, bottom: 20, ...style } : style}\n >\n {children}\n </button>\n )}\n {withDialog ? <FeedbackDialog {...dialogProps} /> : null}\n </>\n );\n}\n\nexport type FeedbackDialogProps = {\n open?: boolean;\n onOpenChange?(open: boolean): void;\n title?: string;\n destination?: string;\n placement?: \"center\" | \"right\";\n initial?: Partial<FeedbackInput>;\n defaultKind?: FeedbackKind;\n /** Optional account address offered for this submission. It is never sent\n * unless the submitter leaves it in the field and `beforeSubmit` identifies\n * it on the client. */\n email?: string;\n emailMode?: \"off\" | \"optional\" | \"required\";\n contrast?: string;\n allowScreenshot?: boolean;\n screenshotOptions?: Parameters<FeedbackBrowserClient[\"captureScreenshot\"]>[0];\n /** Public integration seam for server-minted identity verification. */\n beforeSubmit?(value: FeedbackInput, email: string): void | Promise<void>;\n /** Map a host design system onto the documented --slf-* custom properties. */\n theme?: CSSProperties;\n scheme?: \"light\" | \"dark\" | \"auto\";\n direction?: \"ltr\" | \"rtl\";\n};\n\nconst styles = `\n.slf-backdrop{position:fixed;inset:0;z-index:var(--slf-z,2147000000);background:rgb(0 0 0/.42);display:grid;place-items:center}.slf-dialog{--slf-bg:#fff;--slf-fg:#171717;--slf-muted:#666;--slf-border:#d4d4d4;--slf-accent:#12665f;--slf-accent-fg:#fff;box-sizing:border-box;width:min(92vw,500px);max-height:90vh;overflow:auto;border:1px solid var(--slf-border);border-radius:var(--slf-radius,12px);background:var(--slf-bg);color:var(--slf-fg);padding:20px;font:var(--slf-font,14px/1.45 system-ui,sans-serif)}.slf-backdrop[data-slf-theme=dark] .slf-dialog{--slf-bg:#171717;--slf-fg:#f5f5f5;--slf-muted:#aaa;--slf-border:#525252;--slf-accent:#4eaaa1;--slf-accent-fg:#081715}.slf-dialog h2{margin:0 0 4px;font-size:16px}.slf-destination,.slf-contrast,.slf-status,.slf-details{color:var(--slf-muted);font-size:12px}.slf-field{display:grid;gap:6px;margin:12px 0;text-align:start}.slf-field textarea,.slf-field select,.slf-field input{box-sizing:border-box;width:100%;border:1px solid var(--slf-border);border-radius:var(--slf-control-radius,6px);background:var(--slf-bg);color:inherit;padding:9px}.slf-field textarea{min-height:130px}.slf-actions,.slf-tools{display:flex;justify-content:flex-end;gap:8px}.slf-tools{justify-content:flex-start;margin:10px 0}.slf-actions button,.slf-tools button{border:1px solid var(--slf-border);border-radius:var(--slf-control-radius,6px);background:transparent;color:inherit;padding:8px 12px}.slf-actions .slf-primary{background:var(--slf-accent);border-color:var(--slf-accent);color:var(--slf-accent-fg)}.slf-status{min-height:20px}.slf-details pre{white-space:pre-wrap;word-break:break-word}.slf-placement-right{margin-inline-start:auto;height:100%;max-height:100vh;border-radius:0}@media(prefers-color-scheme:dark){.slf-backdrop[data-slf-theme=auto] .slf-dialog{--slf-bg:#171717;--slf-fg:#f5f5f5;--slf-muted:#aaa;--slf-border:#525252;--slf-accent:#4eaaa1;--slf-accent-fg:#081715}}@media(prefers-reduced-motion:reduce){.slf-dialog{scroll-behavior:auto}}\n`;\n\nexport function FeedbackDialog(props: FeedbackDialogProps) {\n const context = useFeedback();\n const titleId = useId();\n const shown = props.open ?? context.openState;\n const form = useFeedbackForm({ kind: props.defaultKind, ...props.initial });\n const [email, setEmail] = useState(props.email ?? \"\");\n const [attachment, setAttachment] = useState<AttachmentHandle | null>(null);\n const [capturing, setCapturing] = useState(false);\n const [screenshotUnavailable, setScreenshotUnavailable] = useState(false);\n const [localFailure, setLocalFailure] = useState<string | null>(null);\n const [descriptor, setDescriptor] = useState<FeedbackDescriptor | null>(null);\n const close = () => {\n if (capturing || form.submitting) return;\n form.close();\n props.onOpenChange?.(false);\n context.close();\n };\n const screenshotCapable =\n typeof XMLSerializer !== \"undefined\" && typeof HTMLCanvasElement !== \"undefined\";\n const dialog = useRef<HTMLDivElement>(null);\n const returnFocus = useRef<HTMLElement | null>(null);\n useEffect(() => {\n if (!shown || typeof document === \"undefined\") return;\n returnFocus.current =\n document.activeElement instanceof HTMLElement ? document.activeElement : null;\n dialog.current?.querySelector<HTMLElement>(\"textarea\")?.focus();\n return () => returnFocus.current?.focus();\n }, [shown]);\n useEffect(() => {\n if (!shown) return;\n form.open();\n form.setValue({ message: \"\", kind: props.defaultKind ?? \"other\", ...props.initial });\n setEmail(props.email ?? \"\");\n setAttachment(null);\n setScreenshotUnavailable(false);\n setLocalFailure(null);\n // The dialog intentionally resets only on each opening. Changes while a\n // person is composing must never erase their work.\n }, [shown]);\n useEffect(() => {\n if (!shown || !context.client) return;\n let live = true;\n void context.client\n .descriptor()\n .then((value) => {\n if (live) setDescriptor(value);\n })\n .catch(() => {\n if (live) setDescriptor(null);\n });\n return () => {\n live = false;\n };\n }, [context.client, shown]);\n useEffect(() => {\n if (!descriptor) return;\n if (form.value.kind && !descriptor.capturePolicy.kinds.includes(form.value.kind)) {\n form.setValue({ ...form.value, kind: descriptor.capturePolicy.defaultKind });\n }\n }, [descriptor, form.setValue, form.value]);\n if (!shown || typeof document === \"undefined\") return null;\n const emailMode = props.emailMode ?? descriptor?.capturePolicy.emailCollection ?? \"optional\";\n const allowedKinds = descriptor?.capturePolicy.kinds ?? [\n \"bug\",\n \"idea\",\n \"praise\",\n \"question\",\n \"other\",\n ];\n const allowScreenshot =\n (props.allowScreenshot ?? descriptor?.capturePolicy.allowAttachments ?? false) &&\n screenshotCapable &&\n !screenshotUnavailable;\n const submit = async (event: FormEvent) => {\n event.preventDefault();\n // Capturing includes the attachment upload. Do not allow a fast keyboard\n // or pointer submission to race the handle that must be included.\n if (capturing) return;\n const value = {\n ...form.value,\n attachments: attachment ? [attachment] : form.value.attachments,\n };\n try {\n setLocalFailure(null);\n await props.beforeSubmit?.(value, email.trim());\n form.setValue(value);\n const result = await form.submit(value);\n if (result.status === \"accepted\") setTimeout(close, 800);\n } catch (caught) {\n setLocalFailure(caught instanceof Error ? caught.message : \"Submission preparation failed.\");\n }\n };\n const node = (\n <div\n className=\"slf-backdrop\"\n data-slf-theme={props.scheme ?? \"auto\"}\n dir={props.direction}\n onKeyDown={(event) => {\n if (event.key === \"Escape\" && !capturing && !form.submitting) close();\n if (event.key === \"Tab\") {\n const focusable = [\n ...(dialog.current?.querySelectorAll<HTMLElement>(\n \"button:not([disabled]),input:not([disabled]),textarea:not([disabled]),select:not([disabled]),summary,[tabindex]:not([tabindex='-1'])\",\n ) ?? []),\n ].filter((item) => !item.hidden);\n const first = focusable[0];\n const last = focusable.at(-1);\n if (event.shiftKey && document.activeElement === first) {\n event.preventDefault();\n last?.focus();\n } else if (!event.shiftKey && document.activeElement === last) {\n event.preventDefault();\n first?.focus();\n }\n }\n }}\n >\n <style>{styles}</style>\n <div\n ref={dialog}\n style={props.theme}\n className={`slf-dialog ${props.placement === \"right\" ? \"slf-placement-right\" : \"\"}`}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n >\n <h2 id={titleId}>{props.title ?? \"Send feedback\"}</h2>\n {props.destination || descriptor?.publicLabel ? (\n <p className=\"slf-destination\">\n Sending to {props.destination ?? descriptor?.publicLabel}\n </p>\n ) : null}\n {props.contrast ? <p className=\"slf-contrast\">{props.contrast}</p> : null}\n <form onSubmit={(event) => void submit(event)}>\n <label className=\"slf-field\">\n <span>What would you like us to know?</span>\n <textarea\n required\n value={form.value.message}\n onChange={(event) => form.setValue({ ...form.value, message: event.target.value })}\n />\n </label>\n <label className=\"slf-field\">\n <span>Kind</span>\n <select\n value={form.value.kind}\n onChange={(event) =>\n form.setValue({ ...form.value, kind: event.target.value as FeedbackKind })\n }\n >\n {allowedKinds.map((kind) => (\n <option key={kind}>{kind}</option>\n ))}\n </select>\n </label>\n {emailMode !== \"off\" ? (\n <label className=\"slf-field\">\n <span>Email for a reply{emailMode === \"required\" ? \"\" : \" (optional)\"}</span>\n <input\n type=\"email\"\n autoComplete=\"email\"\n required={emailMode === \"required\"}\n value={email}\n onChange={(event) => setEmail(event.target.value)}\n />\n <small>Remove this address to send anonymously.</small>\n </label>\n ) : null}\n {allowScreenshot ? (\n <div className=\"slf-tools\">\n <button\n type=\"button\"\n disabled={capturing}\n onClick={() => {\n setCapturing(true);\n void context.client\n ?.captureScreenshot({\n ...props.screenshotOptions,\n direction: props.direction ?? props.screenshotOptions?.direction,\n })\n .then((next) => {\n setAttachment(next);\n if (!next) setScreenshotUnavailable(true);\n })\n .catch(() => {\n setAttachment(null);\n setScreenshotUnavailable(true);\n })\n .finally(() => setCapturing(false));\n }}\n >\n {capturing\n ? \"Opening screenshot editor…\"\n : attachment\n ? \"Screenshot attached\"\n : \"Attach screenshot\"}\n </button>\n </div>\n ) : null}\n <details className=\"slf-details\">\n <summary>Details included</summary>\n <pre>\n {JSON.stringify(\n {\n context: context.client?.peekContext() ?? {},\n diagnostics: context.client?.peekDiagnostics() ?? [],\n attachments: attachment\n ? [{ contentType: attachment.contentType, size: attachment.size }]\n : [],\n },\n null,\n 2,\n )}\n </pre>\n </details>\n <p className=\"slf-status\" role=\"status\" aria-live=\"polite\">\n {localFailure\n ? `Could not send: ${localFailure}`\n : screenshotUnavailable\n ? \"Screenshot capture is unavailable in this environment.\"\n : form.result\n ? form.result.status === \"accepted\"\n ? \"Feedback sent.\"\n : form.result.status === \"queued\"\n ? \"Saved offline for retry.\"\n : `Could not send: ${form.result.reason}`\n : \"\"}\n </p>\n <div className=\"slf-actions\">\n <button type=\"button\" onClick={close} disabled={capturing || form.submitting}>\n Cancel\n </button>\n <button\n className=\"slf-primary\"\n type=\"submit\"\n disabled={form.submitting || capturing || !context.client}\n >\n Send\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n return createPortal(node, document.body);\n}\n\nexport type { FeedbackBrowserClient, FeedbackClientConfig, FeedbackInput, FeedbackResult };\n"],"mappings":"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","names":["createContext","createElement","useCallback","useContext","useEffect","useId","useMemo","useRef","useState","createPortal","createFeedbackClient","FEEDBACK_FLOW_EDGES","FEEDBACK_FLOW_STATES","isFeedbackFlowEdge","Fragment","jsx","jsxs","Context","configSignature","config","FeedbackProvider","provided","children","owned","setOwned","openState","setOpen","ownedRef","activeEffect","signature","token","previous","record","client","open","close","value","input","identity","verification","options","useFeedback","useFeedbackForm","initial","context","setValue","result","setResult","state","setState","descriptor","setDescriptor","attachment","setAttachment","inFlight","transition","next","current","active","submit","override","unavailable","blocked","submission","withAttachment","request","key","FeedbackButton","children","floating","withDialog","dialogProps","renderTrigger","style","props","open","openState","useFeedback","triggerProps","jsxs","Fragment","jsx","FeedbackDialog","styles","context","titleId","useId","shown","form","useFeedbackForm","email","setEmail","useState","attachment","setAttachment","capturing","setCapturing","screenshotUnavailable","setScreenshotUnavailable","localFailure","setLocalFailure","descriptor","setDescriptor","close","screenshotCapable","dialog","useRef","returnFocus","useEffect","live","value","emailMode","allowedKinds","allowScreenshot","submit","event","caught","node","focusable","item","first","last","kind","next","createPortal"]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@stella-loop/feedback-react",
3
+ "version": "0.2.16",
4
+ "description": "React bindings and accessible UI for Stella Feedback.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ },
18
+ "./package.json": "./package.json"
19
+ },
20
+ "main": "./dist/index.cjs",
21
+ "module": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "dependencies": {
27
+ "@stella-loop/feedback": "0.2.16"
28
+ },
29
+ "peerDependencies": {
30
+ "react": "^18.2.0 || ^19.0.0",
31
+ "react-dom": "^18.2.0 || ^19.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@testing-library/react": "^16.3.2",
35
+ "@types/react": "^19.0.0",
36
+ "@types/react-dom": "^19.0.0",
37
+ "axe-core": "^4.12.1",
38
+ "eslint": "^9.20.0",
39
+ "happy-dom": "^20.10.6",
40
+ "react": "^19.0.0",
41
+ "react-dom": "^19.0.0",
42
+ "tsup": "^8.5.0",
43
+ "tsx": "^4.23.0",
44
+ "typescript": "^5.8.0",
45
+ "vitest": "^3.2.7",
46
+ "@stella-loop/eslint-config": "0.0.0",
47
+ "@stella-loop/typescript-config": "0.0.0"
48
+ },
49
+ "peerContract": "ingest/v1",
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "lint": "eslint src",
53
+ "typecheck": "tsc --noEmit",
54
+ "test": "vitest run",
55
+ "size": "tsx scripts/check-size.ts"
56
+ }
57
+ }