blocfeed 0.1.0 → 0.2.0
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/CHANGELOG.md +16 -0
- package/README.md +17 -47
- package/dist/chunk-H36VLJXX.js +2 -0
- package/dist/chunk-NMFAZKHE.cjs +2 -0
- package/dist/{controller-5O3OHUFm.d.cts → controller-DmWFVHoj.d.cts} +10 -45
- package/dist/{controller-5O3OHUFm.d.ts → controller-DmWFVHoj.d.ts} +10 -45
- package/dist/engine.cjs +1 -1
- package/dist/engine.d.cts +2 -2
- package/dist/engine.d.ts +2 -2
- package/dist/engine.js +1 -1
- package/dist/main.cjs +2 -2
- package/dist/main.d.cts +6 -4
- package/dist/main.d.ts +6 -4
- package/dist/main.js +2 -2
- package/package.json +8 -2
- package/dist/chunk-4EGJPVEG.js +0 -1
- package/dist/chunk-52FWOTRN.cjs +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- (none)
|
|
6
|
+
|
|
7
|
+
## 0.2.0 — 2026-02-17
|
|
8
|
+
|
|
9
|
+
- **Breaking**: `blocfeed_id` is now required for `BlocFeedWidget`, `BlocFeedProvider`, and all submissions.
|
|
10
|
+
- **Breaking**: removed `submit.onSubmit` / `submit.webhook`; the SDK always submits to the BlocFeed ingestion API (`POST https://www.blocfeed.com/api/feedback`).
|
|
11
|
+
|
|
12
|
+
## 0.1.1 — 2026-02-16
|
|
13
|
+
|
|
14
|
+
- Next.js App Router: clarified Server Component constraints (serializable props) and added webhook-first examples.
|
|
15
|
+
- Build: published `dist/main.*` now includes a `"use client";` banner so `BlocFeedWidget` can be mounted directly from `app/layout.tsx` when using serializable config.
|
|
16
|
+
- Docs: synced `docs/modules.md`, `docs/lifecycle.md`, and `docs/api.md` with the implementation.
|
package/README.md
CHANGED
|
@@ -5,9 +5,11 @@ Drop-in in-app feedback widget for **Next.js** and **React**:
|
|
|
5
5
|
- Safe “feedback mode” with **DOM element picking** (blocks host app clicks while active)
|
|
6
6
|
- Optional **element** + **full page** screenshots (capture code is lazy-loaded)
|
|
7
7
|
- Typed, JSON-serializable `FeedbackPayload` with contextual metadata
|
|
8
|
-
-
|
|
8
|
+
- Submits directly to the BlocFeed platform via `blocfeed_id`
|
|
9
9
|
- Optional `selection.componentName` + `selection.testId` for faster triage
|
|
10
10
|
|
|
11
|
+
Docs live in `docs/` (start at `docs/index.md`). Architecture pointers are in `ARCHITECTURE.md`.
|
|
12
|
+
|
|
11
13
|
## Install
|
|
12
14
|
|
|
13
15
|
```bash
|
|
@@ -16,6 +18,14 @@ npm install blocfeed
|
|
|
16
18
|
|
|
17
19
|
Peer deps: `react`, `react-dom`.
|
|
18
20
|
|
|
21
|
+
## Get your `blocfeed_id`
|
|
22
|
+
|
|
23
|
+
Create a project in the BlocFeed dashboard (`https://www.blocfeed.com`). Each project has a unique `blocfeed_id` that the SDK uses to link incoming feedback to the correct project.
|
|
24
|
+
|
|
25
|
+
## Submission endpoint
|
|
26
|
+
|
|
27
|
+
Submissions are sent to the BlocFeed platform ingestion API at `https://www.blocfeed.com/api/feedback`. Custom/external endpoints are intentionally not supported.
|
|
28
|
+
|
|
19
29
|
## Next.js (App Router) — copy/paste
|
|
20
30
|
|
|
21
31
|
`app/layout.tsx`
|
|
@@ -23,44 +33,19 @@ Peer deps: `react`, `react-dom`.
|
|
|
23
33
|
```tsx
|
|
24
34
|
import type { ReactNode } from "react";
|
|
25
35
|
import { BlocFeedWidget } from "blocfeed";
|
|
26
|
-
import type { FeedbackPayload } from "blocfeed";
|
|
27
36
|
|
|
28
37
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
29
38
|
return (
|
|
30
39
|
<html lang="en">
|
|
31
40
|
<body>
|
|
32
41
|
{children}
|
|
33
|
-
<BlocFeedWidget
|
|
34
|
-
config={{
|
|
35
|
-
submit: {
|
|
36
|
-
onSubmit: async (payload: FeedbackPayload) => {
|
|
37
|
-
await fetch("/api/feedback", {
|
|
38
|
-
method: "POST",
|
|
39
|
-
headers: { "content-type": "application/json" },
|
|
40
|
-
body: JSON.stringify(payload)
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}}
|
|
45
|
-
/>
|
|
42
|
+
<BlocFeedWidget blocfeed_id="bf_your_project_blocfeed_id" />
|
|
46
43
|
</body>
|
|
47
44
|
</html>
|
|
48
45
|
);
|
|
49
46
|
}
|
|
50
47
|
```
|
|
51
48
|
|
|
52
|
-
`app/api/feedback/route.ts`
|
|
53
|
-
|
|
54
|
-
```ts
|
|
55
|
-
import { NextResponse } from "next/server";
|
|
56
|
-
|
|
57
|
-
export async function POST(req: Request) {
|
|
58
|
-
const payload = await req.json();
|
|
59
|
-
// Persist, forward to your tracker, etc.
|
|
60
|
-
return NextResponse.json({ ok: true });
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
49
|
## Next.js (Pages Router)
|
|
65
50
|
|
|
66
51
|
Use a client-only dynamic import:
|
|
@@ -77,7 +62,7 @@ export default function App({ Component, pageProps }) {
|
|
|
77
62
|
return (
|
|
78
63
|
<>
|
|
79
64
|
<Component {...pageProps} />
|
|
80
|
-
<BlocFeedWidget
|
|
65
|
+
<BlocFeedWidget blocfeed_id="bf_your_project_blocfeed_id" />
|
|
81
66
|
</>
|
|
82
67
|
);
|
|
83
68
|
}
|
|
@@ -92,29 +77,12 @@ export function App() {
|
|
|
92
77
|
return (
|
|
93
78
|
<>
|
|
94
79
|
{/* your app */}
|
|
95
|
-
<BlocFeedWidget
|
|
80
|
+
<BlocFeedWidget blocfeed_id="bf_your_project_blocfeed_id" />
|
|
96
81
|
</>
|
|
97
82
|
);
|
|
98
83
|
}
|
|
99
84
|
```
|
|
100
85
|
|
|
101
|
-
## Webhook mode
|
|
102
|
-
|
|
103
|
-
```tsx
|
|
104
|
-
<BlocFeedWidget
|
|
105
|
-
config={{
|
|
106
|
-
submit: {
|
|
107
|
-
webhook: {
|
|
108
|
-
url: "https://example.com/api/feedback",
|
|
109
|
-
headers: async () => ({ Authorization: `Bearer ${token}` }),
|
|
110
|
-
timeoutMs: 12_000,
|
|
111
|
-
retry: { attempts: 2, backoffMs: 500 }
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}}
|
|
115
|
-
/>
|
|
116
|
-
```
|
|
117
|
-
|
|
118
86
|
## Picking rules (ignore / filter)
|
|
119
87
|
|
|
120
88
|
### Ignore a subtree (recommended)
|
|
@@ -181,7 +149,7 @@ function FeedbackButton() {
|
|
|
181
149
|
|
|
182
150
|
export function App() {
|
|
183
151
|
return (
|
|
184
|
-
<BlocFeedProvider
|
|
152
|
+
<BlocFeedProvider blocfeed_id="bf_your_project_blocfeed_id">
|
|
185
153
|
{/* your app */}
|
|
186
154
|
<FeedbackButton />
|
|
187
155
|
</BlocFeedProvider>
|
|
@@ -193,6 +161,8 @@ export function App() {
|
|
|
193
161
|
|
|
194
162
|
```ts
|
|
195
163
|
import { createBlocFeedController } from "blocfeed/engine";
|
|
164
|
+
|
|
165
|
+
const controller = createBlocFeedController({ blocfeed_id: "bf_your_project_blocfeed_id" });
|
|
196
166
|
```
|
|
197
167
|
|
|
198
168
|
## Local development
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function b(){return typeof window<"u"&&typeof document<"u"}function W(e){let n=globalThis.CSS;return typeof n?.escape=="function"?n.escape(e):e.replace(/[^a-zA-Z0-9_-]/g,t=>{let r=t.codePointAt(0);return r===void 0?"":`\\${r.toString(16)} `})}function H(e){return {x:e.x,y:e.y,width:e.width,height:e.height}}function de(e){return {x:e.x+window.scrollX,y:e.y+window.scrollY,width:e.width,height:e.height}}function fe(e){return e.replace(/\s+/g," ").trim()}function me(e,n=140){let t=e.textContent;if(!t)return;let r=fe(t);if(r)return r.length<=n?r:`${r.slice(0,n-1)}\u2026`}function pe(e){let n=1;for(let t=e.previousElementSibling;t;t=t.previousElementSibling)t.tagName===e.tagName&&(n+=1);return n}var K=["data-testid","data-test-id","data-test","data-qa","data-cy"],z="data-blocfeed-component";function ge(e){let n=e.closest(`[${z}]`);if(!n)return;let r=n.getAttribute(z)?.trim();return r||void 0}function we(e){for(let n of K){let t=e.closest(`[${n}]`);if(!t)continue;let o=t.getAttribute(n)?.trim();if(o)return o}}function he(e){try{let n=e,t=Object.getOwnPropertyNames(n);for(let r of t)if(r.startsWith("__reactFiber$")||r.startsWith("__reactInternalInstance$")){let o=n[r];if(o&&typeof o=="object")return o}}catch{}return null}function C(e){if(e&&typeof e!="string"){if(typeof e=="function"){let n=e;return typeof n.displayName=="string"&&n.displayName?n.displayName:typeof n.name=="string"&&n.name?n.name:void 0}if(typeof e=="object"){let n=e,t=n.displayName;if(typeof t=="string"&&t)return t;let r=n.render;if(typeof r=="function"){let i=r;if(typeof i.displayName=="string"&&i.displayName)return i.displayName;if(typeof i.name=="string"&&i.name)return i.name}let o=n.type;return C(o)}}}function be(e){let n=he(e);if(!n)return;let t=n._debugOwner;for(let o=0;t&&o<50;o+=1){let i=C(t.type)??C(t.elementType);if(i)return i;t=t._debugOwner;}let r=n;for(let o=0;r&&o<80;o+=1){let i=C(r.type)??C(r.elementType);if(i)return i;r=r.return;}}function ye(e){let n=e.tagName.toLowerCase(),t=e.getAttribute("id");if(t)return `#${W(t)}`;for(let r of K){let o=e.getAttribute(r);if(o)return `${n}[${r}="${W(o)}"]`}return `${n}:nth-of-type(${pe(e)})`}function Ee(e,n=10){let t=[],r=e;for(;r&&t.length<n;){let o=ye(r);if(t.unshift(o),o.startsWith("#"))break;r=r.parentElement;}return t.join(" > ")}function Y(e,n){if(!n||n.length===0)return false;for(let t of n)if(e.closest(t))return true;return false}function N(e,n){if(!e||Y(e,n.ignoreSelectors))return null;let t=e;for(;t;){if(Y(t,n.ignoreSelectors))return null;if(n.isSelectable?.(t)??ve(t))return t;t=t.parentElement;}return null}function ve(e){let n=e.tagName;return !(n==="HTML"||n==="BODY")}function Q(e){let n=e.getBoundingClientRect(),t={selector:Ee(e),tagName:e.tagName.toLowerCase(),rect:H(n),pageRect:de(n)},r=e.getAttribute("id");r&&(t.id=r);let o=e.className;typeof o=="string"&&o.trim()&&(t.className=o);let i=me(e);i&&(t.textSnippet=i);let l=ge(e)??be(e);l&&(t.componentName=l);let s=we(e);return s&&(t.testId=s),t}var O=null;async function X(){return O||(O=import('html-to-image')),O}async function Se(e){return await new Promise((n,t)=>{let r=new Image;r.onload=()=>n({width:r.naturalWidth,height:r.naturalHeight}),r.onerror=()=>t(new Error("Failed to load generated screenshot")),r.src=e;})}async function J(e,n){let{width:t,height:r}=await Se(e);return {dataUrl:e,mime:n,width:t,height:r}}function T(e){if(e?.aborted)throw new Error("Aborted")}function G(){return {async captureElement(e,n){if(!b())throw new Error("captureElement can only run in the browser");T(n.signal);let t=await X();T(n.signal);let r=n.mime==="image/jpeg"?await t.toJpeg(e,{quality:n.quality??.92,pixelRatio:n.pixelRatio}):await t.toPng(e,{pixelRatio:n.pixelRatio});return T(n.signal),await J(r,n.mime)},async captureFullPage(e){if(!b())throw new Error("captureFullPage can only run in the browser");T(e.signal);let n=document.documentElement,t=Math.max(n.scrollWidth,n.clientWidth),r=Math.max(n.scrollHeight,n.clientHeight),o=Math.min(1,e.maxDimension/Math.max(t,r)),i=Math.max(1,Math.round(t*o)),l=Math.max(1,Math.round(r*o)),s=await X();T(e.signal);let u=e.mime==="image/jpeg"?await s.toJpeg(n,{width:i,height:l,quality:e.quality??.92,pixelRatio:e.pixelRatio}):await s.toPng(n,{width:i,height:l,pixelRatio:e.pixelRatio});return T(e.signal),await J(u,e.mime)}}}function ke(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return "Unknown error"}}function m(e,n,t){let r={kind:e,message:ke(n)};return t&&(r.detail=t),r}var Fe=12e3,xe=2048,Pe=.92;function Z(){return Date.now()}function Te(e){return new Promise((n,t)=>{let r=()=>t(new Error("Aborted"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:true});})}async function V(e,n,t){let r=new Promise((i,l)=>{let s=setTimeout(()=>l(new Error("Timeout")),n);typeof s.unref=="function"&&s.unref();}),o=[e,r];return t&&o.push(Te(t)),await Promise.race(o)}function Ae(e){if(!b())return 1;let n=window.devicePixelRatio||1,t=e?.pixelRatio??Math.min(n,2);return Math.max(.1,t)}function Ce(e){return !!(e?.element||e?.fullPage)}function ee(e){let n={mime:e.mime,pixelRatio:e.pixelRatio,maxDimension:e.maxDimension};return e.includeQuality&&(n.quality=e.quality),e.signal&&(n.signal=e.signal),n}async function te(e){let{selectionElement:n,capture:t,signal:r}=e;if(!b()||!Ce(t))return;let o=Z(),i=[],l=t?.timeoutMs??Fe,s=t?.maxDimension??xe,u=t?.mime??"image/png",d=t?.quality??Pe,v=t?.adapter??G(),y={},p=Ae(t);if(t?.element&&n)try{let f=n.getBoundingClientRect(),g=Math.min(1,s/Math.max(f.width,f.height)),c=Math.min(p,p*g),E=await V(Promise.resolve(v.captureElement(n,{...ee({mime:u,quality:d,pixelRatio:c,maxDimension:s,includeQuality:u==="image/jpeg",...r?{signal:r}:{}})})),l,r);y.element=E;}catch(f){if(r?.aborted)throw f;i.push(m("capture_failed",f,{target:"element"}));}if(t?.fullPage)try{let f=await V(Promise.resolve(v.captureFullPage(ee({mime:u,quality:d,pixelRatio:p,maxDimension:s,includeQuality:u==="image/jpeg",...r?{signal:r}:{}}))),l,r);y.fullPage=f;}catch(f){if(r?.aborted)throw f;i.push(m("capture_failed",f,{target:"fullPage"}));}let S=Z(),a={startedAt:o,finishedAt:S,durationMs:Math.max(0,S-o)};return i.length>0&&(a.errors=i),{...y,diagnostics:a}}function Re(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return}}function Me(){return b()?{url:window.location.href,title:document.title,referrer:document.referrer||void 0,userAgent:navigator.userAgent,language:navigator.language,platform:navigator.platform,viewport:{width:window.innerWidth,height:window.innerHeight},screen:{width:window.screen.width,height:window.screen.height},scroll:{x:window.scrollX,y:window.scrollY},devicePixelRatio:window.devicePixelRatio||1,timezone:Re()}:{}}async function ne(e){let{config:n,context:t}=e;if(n?.enabled===false)return {};let r=Me(),o=n?.enrich;if(!o)return r;try{let i=await o(t);return {...r,...i}}catch(i){let l=m("unknown",i);return {...r,blocfeedMetadataError:l.message}}}function U(e){let n=null,t=null,r=(...o)=>{t=o,n===null&&(n=requestAnimationFrame(()=>{if(n=null,!t)return;let i=t;t=null,e(...i);}));};return r.cancel=()=>{n!==null&&cancelAnimationFrame(n),n=null,t=null;},r}function M(e){return e instanceof Element?!!e.closest("[data-blocfeed-ui]"):false}function B(e){e.stopPropagation(),e.stopImmediatePropagation?.();}function re(e,n){if(!b())throw new Error("BlocFeed picker can only run in a browser environment.");let t=e.ignoreSelectors,r=e.isSelectable,o={};t&&t.length>0&&(o.ignoreSelectors=t),r&&(o.isSelectable=r);let i=null,l=null,s=(a,f=false)=>{if(!a){i=null,l=null,n.onHover(null);return}let g=H(a.getBoundingClientRect()),c=`${Math.round(g.x)}:${Math.round(g.y)}:${Math.round(g.width)}:${Math.round(g.height)}`;!f&&a===i&&c===l||(i=a,l=c,n.onHover({element:a,rect:g}));},u=U(a=>{if(M(a.target))return;let f=document.elementFromPoint(a.clientX,a.clientY),g=N(f,o);s(g);}),d=U(()=>{i&&s(i,true);}),v=a=>{M(a.target)||(B(a),a.pointerType==="mouse"&&a.preventDefault());},y=a=>{M(a.target)||(B(a),a.pointerType==="mouse"&&a.preventDefault());},p=a=>{if(M(a.target))return;B(a),a.preventDefault();let f=document.elementFromPoint(a.clientX,a.clientY),g=N(f,o);g&&n.onSelect({element:g,descriptor:Q(g)});},S=a=>{a.key==="Escape"&&(B(a),a.preventDefault(),n.onCancel());};return window.addEventListener("pointermove",u,{capture:true,passive:true}),window.addEventListener("pointerdown",v,{capture:true}),window.addEventListener("pointerup",y,{capture:true}),window.addEventListener("click",p,{capture:true}),window.addEventListener("keydown",S,{capture:true}),window.addEventListener("scroll",d,{capture:true,passive:true}),window.addEventListener("resize",d,{passive:true}),{stop(){window.removeEventListener("pointermove",u,{capture:true}),window.removeEventListener("pointerdown",v,{capture:true}),window.removeEventListener("pointerup",y,{capture:true}),window.removeEventListener("click",p,{capture:true}),window.removeEventListener("keydown",S,{capture:true}),window.removeEventListener("scroll",d,{capture:true}),window.removeEventListener("resize",d),u.cancel(),d.cancel(),n.onHover(null);}}}var Be=12e3,$=2,oe=500,_e="https://www.blocfeed.com/api/feedback";function ie(e,n){return new Promise((t,r)=>{if(n?.aborted){r(new Error("Aborted"));return}let o=setTimeout(t,e),i=()=>{clearTimeout(o),r(new Error("Aborted"));};n?.addEventListener("abort",i,{once:true});})}function Le(e){return e>=500&&e<=599}async function ae(e){let{payload:n,signal:t}=e;for(let r=1;r<=$;r+=1){let o=new AbortController,i=setTimeout(()=>o.abort(),Be),l=()=>o.abort();t&&t.addEventListener("abort",l,{once:true});try{let s=await fetch(_e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n),signal:o.signal});if(s.ok)return {ok:!0,status:s.status};if(r<$&&Le(s.status)){let u=.85+Math.random()*.3,d=Math.round(oe*2**(r-1)*u);await ie(d,t);continue}return {ok:!1,status:s.status,error:m("api_failed",new Error(`HTTP ${s.status}`))}}catch(s){if(o.signal.aborted||t?.aborted)return {ok:false,error:m("aborted",s)};if(r<$){let u=.85+Math.random()*.3,d=Math.round(oe*2**(r-1)*u);await ie(d,t);continue}return {ok:false,error:m("api_failed",s)}}finally{clearTimeout(i),t&&t.removeEventListener("abort",l);}}return {ok:false,error:m("api_failed",new Error("Failed"))}}async function se(e){let{signal:n}=e,t={ok:false};try{let r=n?{payload:e.payload,signal:n}:{payload:e.payload};t.api=await ae(r),t.ok=!!t.api?.ok;}catch(r){t.api={ok:false,error:m("api_failed",r)},t.ok=false;}return {payload:e.payload,result:t}}var Ie=["[data-blocfeed-ui]","[data-blocfeed-ignore]"];function De(e){let n=[...Ie,...e?.ignoreSelectors??[]],t=Array.from(new Set(n));return {...e,ignoreSelectors:t}}function le(){return {phase:"idle"}}function ut(e){let n=e,t=le(),r=new Set,o=new Set,i=null,l=null,s=null,u=null,d=0,v=()=>{for(let c of r)c(t);},y=c=>{for(let E of o)E(c);},p=c=>{t=c,v();},S=()=>{d+=1,u?.abort(),u=null;},a=()=>{i?.stop(),i=null,y(null),s!==null&&b()&&(document.documentElement.style.cursor=s,s=null);},f=()=>{S(),a(),l=null,p(le());},g=()=>{if(!b())return;a(),l=null;let c=De(n.picker);s=document.documentElement.style.cursor,document.documentElement.style.cursor="crosshair",p({phase:"picking"}),i=re(c,{onHover:y,onSelect:({element:E,descriptor:R})=>{l=E,a(),p({phase:"review",selection:R});},onCancel:()=>{f();}});};return {getState:()=>t,subscribe(c){return r.add(c),()=>r.delete(c)},subscribeHover(c){return o.add(c),()=>o.delete(c)},start(){t.phase==="capturing"||t.phase==="submitting"||t.phase!=="picking"&&g();},stop(){f();},clearSelection(){t.phase==="capturing"||t.phase==="submitting"||g();},setConfig(c){n=c;},async submit(c,E){if(!b()){let w=m("configuration",new Error("BlocFeed submit can only run in the browser"));return p({phase:"error",lastError:w}),{ok:false}}let R=n.blocfeed_id?.trim?.()??"";if(!R){let k={phase:"error",lastError:m("configuration",new Error("Missing blocfeed_id. Create a project in BlocFeed and pass its blocfeed_id."))};return t.selection&&(k.selection=t.selection),p(k),{ok:false}}if(t.phase==="capturing"||t.phase==="submitting")return {ok:false};let A=d+1;d=A,u?.abort(),u=new AbortController;let x=u.signal,h=t.selection,_=E?.capture?{...n.capture,...E.capture}:n.capture,q=!!(_?.element||_?.fullPage),j={phase:q?"capturing":"submitting"};h&&(j.selection=h),p(j);try{let w=q?await te({selectionElement:l,capture:_,signal:x}):void 0;if(x.aborted||d!==A)return {ok:!1};let k={phase:"submitting"};h&&(k.selection=h),w&&(k.capture=w),p(k);let P={};h&&(P.selection=h),w&&(P.capture=w);let ce=await ne({config:n.metadata,context:P}),L={version:1,createdAt:new Date().toISOString(),blocfeed_id:R,message:c,metadata:ce};h&&(L.selection=h),w&&(L.screenshots=w);let{result:F}=await se({payload:L,signal:x});if(x.aborted||d!==A)return F;if(F.ok){let D={phase:"success",lastSubmit:F};return h&&(D.selection=h),w&&(D.capture=w),p(D),F}let ue=F.api?.error??m("unknown",new Error("Submission failed")),I={phase:"error",lastSubmit:F,lastError:ue};return h&&(I.selection=h),w&&(I.capture=w),p(I),F}catch(w){if(x.aborted||d!==A)return {ok:false};let P={phase:"error",lastError:x.aborted?m("aborted",w):m("unknown",w)};return h&&(P.selection=h),p(P),{ok:false}}finally{d===A&&(u=null);}},__unsafeGetSelectedElement(){return l},destroy(){f(),r.clear(),o.clear();}}}
|
|
2
|
+
export{b as a,H as b,G as c,te as d,ne as e,ut as f};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';function b(){return typeof window<"u"&&typeof document<"u"}function W(e){let n=globalThis.CSS;return typeof n?.escape=="function"?n.escape(e):e.replace(/[^a-zA-Z0-9_-]/g,t=>{let r=t.codePointAt(0);return r===void 0?"":`\\${r.toString(16)} `})}function H(e){return {x:e.x,y:e.y,width:e.width,height:e.height}}function de(e){return {x:e.x+window.scrollX,y:e.y+window.scrollY,width:e.width,height:e.height}}function fe(e){return e.replace(/\s+/g," ").trim()}function me(e,n=140){let t=e.textContent;if(!t)return;let r=fe(t);if(r)return r.length<=n?r:`${r.slice(0,n-1)}\u2026`}function pe(e){let n=1;for(let t=e.previousElementSibling;t;t=t.previousElementSibling)t.tagName===e.tagName&&(n+=1);return n}var K=["data-testid","data-test-id","data-test","data-qa","data-cy"],z="data-blocfeed-component";function ge(e){let n=e.closest(`[${z}]`);if(!n)return;let r=n.getAttribute(z)?.trim();return r||void 0}function we(e){for(let n of K){let t=e.closest(`[${n}]`);if(!t)continue;let o=t.getAttribute(n)?.trim();if(o)return o}}function he(e){try{let n=e,t=Object.getOwnPropertyNames(n);for(let r of t)if(r.startsWith("__reactFiber$")||r.startsWith("__reactInternalInstance$")){let o=n[r];if(o&&typeof o=="object")return o}}catch{}return null}function C(e){if(e&&typeof e!="string"){if(typeof e=="function"){let n=e;return typeof n.displayName=="string"&&n.displayName?n.displayName:typeof n.name=="string"&&n.name?n.name:void 0}if(typeof e=="object"){let n=e,t=n.displayName;if(typeof t=="string"&&t)return t;let r=n.render;if(typeof r=="function"){let i=r;if(typeof i.displayName=="string"&&i.displayName)return i.displayName;if(typeof i.name=="string"&&i.name)return i.name}let o=n.type;return C(o)}}}function be(e){let n=he(e);if(!n)return;let t=n._debugOwner;for(let o=0;t&&o<50;o+=1){let i=C(t.type)??C(t.elementType);if(i)return i;t=t._debugOwner;}let r=n;for(let o=0;r&&o<80;o+=1){let i=C(r.type)??C(r.elementType);if(i)return i;r=r.return;}}function ye(e){let n=e.tagName.toLowerCase(),t=e.getAttribute("id");if(t)return `#${W(t)}`;for(let r of K){let o=e.getAttribute(r);if(o)return `${n}[${r}="${W(o)}"]`}return `${n}:nth-of-type(${pe(e)})`}function Ee(e,n=10){let t=[],r=e;for(;r&&t.length<n;){let o=ye(r);if(t.unshift(o),o.startsWith("#"))break;r=r.parentElement;}return t.join(" > ")}function Y(e,n){if(!n||n.length===0)return false;for(let t of n)if(e.closest(t))return true;return false}function N(e,n){if(!e||Y(e,n.ignoreSelectors))return null;let t=e;for(;t;){if(Y(t,n.ignoreSelectors))return null;if(n.isSelectable?.(t)??ve(t))return t;t=t.parentElement;}return null}function ve(e){let n=e.tagName;return !(n==="HTML"||n==="BODY")}function Q(e){let n=e.getBoundingClientRect(),t={selector:Ee(e),tagName:e.tagName.toLowerCase(),rect:H(n),pageRect:de(n)},r=e.getAttribute("id");r&&(t.id=r);let o=e.className;typeof o=="string"&&o.trim()&&(t.className=o);let i=me(e);i&&(t.textSnippet=i);let l=ge(e)??be(e);l&&(t.componentName=l);let s=we(e);return s&&(t.testId=s),t}var O=null;async function X(){return O||(O=import('html-to-image')),O}async function Se(e){return await new Promise((n,t)=>{let r=new Image;r.onload=()=>n({width:r.naturalWidth,height:r.naturalHeight}),r.onerror=()=>t(new Error("Failed to load generated screenshot")),r.src=e;})}async function J(e,n){let{width:t,height:r}=await Se(e);return {dataUrl:e,mime:n,width:t,height:r}}function T(e){if(e?.aborted)throw new Error("Aborted")}function G(){return {async captureElement(e,n){if(!b())throw new Error("captureElement can only run in the browser");T(n.signal);let t=await X();T(n.signal);let r=n.mime==="image/jpeg"?await t.toJpeg(e,{quality:n.quality??.92,pixelRatio:n.pixelRatio}):await t.toPng(e,{pixelRatio:n.pixelRatio});return T(n.signal),await J(r,n.mime)},async captureFullPage(e){if(!b())throw new Error("captureFullPage can only run in the browser");T(e.signal);let n=document.documentElement,t=Math.max(n.scrollWidth,n.clientWidth),r=Math.max(n.scrollHeight,n.clientHeight),o=Math.min(1,e.maxDimension/Math.max(t,r)),i=Math.max(1,Math.round(t*o)),l=Math.max(1,Math.round(r*o)),s=await X();T(e.signal);let u=e.mime==="image/jpeg"?await s.toJpeg(n,{width:i,height:l,quality:e.quality??.92,pixelRatio:e.pixelRatio}):await s.toPng(n,{width:i,height:l,pixelRatio:e.pixelRatio});return T(e.signal),await J(u,e.mime)}}}function ke(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return "Unknown error"}}function m(e,n,t){let r={kind:e,message:ke(n)};return t&&(r.detail=t),r}var Fe=12e3,xe=2048,Pe=.92;function Z(){return Date.now()}function Te(e){return new Promise((n,t)=>{let r=()=>t(new Error("Aborted"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:true});})}async function V(e,n,t){let r=new Promise((i,l)=>{let s=setTimeout(()=>l(new Error("Timeout")),n);typeof s.unref=="function"&&s.unref();}),o=[e,r];return t&&o.push(Te(t)),await Promise.race(o)}function Ae(e){if(!b())return 1;let n=window.devicePixelRatio||1,t=e?.pixelRatio??Math.min(n,2);return Math.max(.1,t)}function Ce(e){return !!(e?.element||e?.fullPage)}function ee(e){let n={mime:e.mime,pixelRatio:e.pixelRatio,maxDimension:e.maxDimension};return e.includeQuality&&(n.quality=e.quality),e.signal&&(n.signal=e.signal),n}async function te(e){let{selectionElement:n,capture:t,signal:r}=e;if(!b()||!Ce(t))return;let o=Z(),i=[],l=t?.timeoutMs??Fe,s=t?.maxDimension??xe,u=t?.mime??"image/png",d=t?.quality??Pe,v=t?.adapter??G(),y={},p=Ae(t);if(t?.element&&n)try{let f=n.getBoundingClientRect(),g=Math.min(1,s/Math.max(f.width,f.height)),c=Math.min(p,p*g),E=await V(Promise.resolve(v.captureElement(n,{...ee({mime:u,quality:d,pixelRatio:c,maxDimension:s,includeQuality:u==="image/jpeg",...r?{signal:r}:{}})})),l,r);y.element=E;}catch(f){if(r?.aborted)throw f;i.push(m("capture_failed",f,{target:"element"}));}if(t?.fullPage)try{let f=await V(Promise.resolve(v.captureFullPage(ee({mime:u,quality:d,pixelRatio:p,maxDimension:s,includeQuality:u==="image/jpeg",...r?{signal:r}:{}}))),l,r);y.fullPage=f;}catch(f){if(r?.aborted)throw f;i.push(m("capture_failed",f,{target:"fullPage"}));}let S=Z(),a={startedAt:o,finishedAt:S,durationMs:Math.max(0,S-o)};return i.length>0&&(a.errors=i),{...y,diagnostics:a}}function Re(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return}}function Me(){return b()?{url:window.location.href,title:document.title,referrer:document.referrer||void 0,userAgent:navigator.userAgent,language:navigator.language,platform:navigator.platform,viewport:{width:window.innerWidth,height:window.innerHeight},screen:{width:window.screen.width,height:window.screen.height},scroll:{x:window.scrollX,y:window.scrollY},devicePixelRatio:window.devicePixelRatio||1,timezone:Re()}:{}}async function ne(e){let{config:n,context:t}=e;if(n?.enabled===false)return {};let r=Me(),o=n?.enrich;if(!o)return r;try{let i=await o(t);return {...r,...i}}catch(i){let l=m("unknown",i);return {...r,blocfeedMetadataError:l.message}}}function U(e){let n=null,t=null,r=(...o)=>{t=o,n===null&&(n=requestAnimationFrame(()=>{if(n=null,!t)return;let i=t;t=null,e(...i);}));};return r.cancel=()=>{n!==null&&cancelAnimationFrame(n),n=null,t=null;},r}function M(e){return e instanceof Element?!!e.closest("[data-blocfeed-ui]"):false}function B(e){e.stopPropagation(),e.stopImmediatePropagation?.();}function re(e,n){if(!b())throw new Error("BlocFeed picker can only run in a browser environment.");let t=e.ignoreSelectors,r=e.isSelectable,o={};t&&t.length>0&&(o.ignoreSelectors=t),r&&(o.isSelectable=r);let i=null,l=null,s=(a,f=false)=>{if(!a){i=null,l=null,n.onHover(null);return}let g=H(a.getBoundingClientRect()),c=`${Math.round(g.x)}:${Math.round(g.y)}:${Math.round(g.width)}:${Math.round(g.height)}`;!f&&a===i&&c===l||(i=a,l=c,n.onHover({element:a,rect:g}));},u=U(a=>{if(M(a.target))return;let f=document.elementFromPoint(a.clientX,a.clientY),g=N(f,o);s(g);}),d=U(()=>{i&&s(i,true);}),v=a=>{M(a.target)||(B(a),a.pointerType==="mouse"&&a.preventDefault());},y=a=>{M(a.target)||(B(a),a.pointerType==="mouse"&&a.preventDefault());},p=a=>{if(M(a.target))return;B(a),a.preventDefault();let f=document.elementFromPoint(a.clientX,a.clientY),g=N(f,o);g&&n.onSelect({element:g,descriptor:Q(g)});},S=a=>{a.key==="Escape"&&(B(a),a.preventDefault(),n.onCancel());};return window.addEventListener("pointermove",u,{capture:true,passive:true}),window.addEventListener("pointerdown",v,{capture:true}),window.addEventListener("pointerup",y,{capture:true}),window.addEventListener("click",p,{capture:true}),window.addEventListener("keydown",S,{capture:true}),window.addEventListener("scroll",d,{capture:true,passive:true}),window.addEventListener("resize",d,{passive:true}),{stop(){window.removeEventListener("pointermove",u,{capture:true}),window.removeEventListener("pointerdown",v,{capture:true}),window.removeEventListener("pointerup",y,{capture:true}),window.removeEventListener("click",p,{capture:true}),window.removeEventListener("keydown",S,{capture:true}),window.removeEventListener("scroll",d,{capture:true}),window.removeEventListener("resize",d),u.cancel(),d.cancel(),n.onHover(null);}}}var Be=12e3,$=2,oe=500,_e="https://www.blocfeed.com/api/feedback";function ie(e,n){return new Promise((t,r)=>{if(n?.aborted){r(new Error("Aborted"));return}let o=setTimeout(t,e),i=()=>{clearTimeout(o),r(new Error("Aborted"));};n?.addEventListener("abort",i,{once:true});})}function Le(e){return e>=500&&e<=599}async function ae(e){let{payload:n,signal:t}=e;for(let r=1;r<=$;r+=1){let o=new AbortController,i=setTimeout(()=>o.abort(),Be),l=()=>o.abort();t&&t.addEventListener("abort",l,{once:true});try{let s=await fetch(_e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n),signal:o.signal});if(s.ok)return {ok:!0,status:s.status};if(r<$&&Le(s.status)){let u=.85+Math.random()*.3,d=Math.round(oe*2**(r-1)*u);await ie(d,t);continue}return {ok:!1,status:s.status,error:m("api_failed",new Error(`HTTP ${s.status}`))}}catch(s){if(o.signal.aborted||t?.aborted)return {ok:false,error:m("aborted",s)};if(r<$){let u=.85+Math.random()*.3,d=Math.round(oe*2**(r-1)*u);await ie(d,t);continue}return {ok:false,error:m("api_failed",s)}}finally{clearTimeout(i),t&&t.removeEventListener("abort",l);}}return {ok:false,error:m("api_failed",new Error("Failed"))}}async function se(e){let{signal:n}=e,t={ok:false};try{let r=n?{payload:e.payload,signal:n}:{payload:e.payload};t.api=await ae(r),t.ok=!!t.api?.ok;}catch(r){t.api={ok:false,error:m("api_failed",r)},t.ok=false;}return {payload:e.payload,result:t}}var Ie=["[data-blocfeed-ui]","[data-blocfeed-ignore]"];function De(e){let n=[...Ie,...e?.ignoreSelectors??[]],t=Array.from(new Set(n));return {...e,ignoreSelectors:t}}function le(){return {phase:"idle"}}function ut(e){let n=e,t=le(),r=new Set,o=new Set,i=null,l=null,s=null,u=null,d=0,v=()=>{for(let c of r)c(t);},y=c=>{for(let E of o)E(c);},p=c=>{t=c,v();},S=()=>{d+=1,u?.abort(),u=null;},a=()=>{i?.stop(),i=null,y(null),s!==null&&b()&&(document.documentElement.style.cursor=s,s=null);},f=()=>{S(),a(),l=null,p(le());},g=()=>{if(!b())return;a(),l=null;let c=De(n.picker);s=document.documentElement.style.cursor,document.documentElement.style.cursor="crosshair",p({phase:"picking"}),i=re(c,{onHover:y,onSelect:({element:E,descriptor:R})=>{l=E,a(),p({phase:"review",selection:R});},onCancel:()=>{f();}});};return {getState:()=>t,subscribe(c){return r.add(c),()=>r.delete(c)},subscribeHover(c){return o.add(c),()=>o.delete(c)},start(){t.phase==="capturing"||t.phase==="submitting"||t.phase!=="picking"&&g();},stop(){f();},clearSelection(){t.phase==="capturing"||t.phase==="submitting"||g();},setConfig(c){n=c;},async submit(c,E){if(!b()){let w=m("configuration",new Error("BlocFeed submit can only run in the browser"));return p({phase:"error",lastError:w}),{ok:false}}let R=n.blocfeed_id?.trim?.()??"";if(!R){let k={phase:"error",lastError:m("configuration",new Error("Missing blocfeed_id. Create a project in BlocFeed and pass its blocfeed_id."))};return t.selection&&(k.selection=t.selection),p(k),{ok:false}}if(t.phase==="capturing"||t.phase==="submitting")return {ok:false};let A=d+1;d=A,u?.abort(),u=new AbortController;let x=u.signal,h=t.selection,_=E?.capture?{...n.capture,...E.capture}:n.capture,q=!!(_?.element||_?.fullPage),j={phase:q?"capturing":"submitting"};h&&(j.selection=h),p(j);try{let w=q?await te({selectionElement:l,capture:_,signal:x}):void 0;if(x.aborted||d!==A)return {ok:!1};let k={phase:"submitting"};h&&(k.selection=h),w&&(k.capture=w),p(k);let P={};h&&(P.selection=h),w&&(P.capture=w);let ce=await ne({config:n.metadata,context:P}),L={version:1,createdAt:new Date().toISOString(),blocfeed_id:R,message:c,metadata:ce};h&&(L.selection=h),w&&(L.screenshots=w);let{result:F}=await se({payload:L,signal:x});if(x.aborted||d!==A)return F;if(F.ok){let D={phase:"success",lastSubmit:F};return h&&(D.selection=h),w&&(D.capture=w),p(D),F}let ue=F.api?.error??m("unknown",new Error("Submission failed")),I={phase:"error",lastSubmit:F,lastError:ue};return h&&(I.selection=h),w&&(I.capture=w),p(I),F}catch(w){if(x.aborted||d!==A)return {ok:false};let P={phase:"error",lastError:x.aborted?m("aborted",w):m("unknown",w)};return h&&(P.selection=h),p(P),{ok:false}}finally{d===A&&(u=null);}},__unsafeGetSelectedElement(){return l},destroy(){f(),r.clear(),o.clear();}}}
|
|
2
|
+
exports.a=b;exports.b=H;exports.c=G;exports.d=te;exports.e=ne;exports.f=ut;
|
|
@@ -49,7 +49,7 @@ interface CaptureResult {
|
|
|
49
49
|
diagnostics: CaptureDiagnostics;
|
|
50
50
|
}
|
|
51
51
|
interface BlocFeedError {
|
|
52
|
-
kind: "unknown" | "configuration" | "capture_failed" | "
|
|
52
|
+
kind: "unknown" | "configuration" | "capture_failed" | "api_failed" | "aborted";
|
|
53
53
|
message: string;
|
|
54
54
|
detail?: Record<string, unknown>;
|
|
55
55
|
}
|
|
@@ -110,52 +110,17 @@ interface MetadataConfig {
|
|
|
110
110
|
*/
|
|
111
111
|
enrich?: (context: MetadataContext) => MaybePromise<Record<string, unknown>>;
|
|
112
112
|
}
|
|
113
|
-
interface
|
|
114
|
-
url: string;
|
|
115
|
-
headers?: Record<string, string> | (() => MaybePromise<Record<string, string>>);
|
|
116
|
-
timeoutMs?: number;
|
|
117
|
-
/**
|
|
118
|
-
* Optional: set fetch credentials mode for the request.
|
|
119
|
-
*/
|
|
120
|
-
credentials?: RequestCredentials;
|
|
121
|
-
/**
|
|
122
|
-
* Basic retry behavior for transient failures (network errors, 5xx).
|
|
123
|
-
*/
|
|
124
|
-
retry?: {
|
|
125
|
-
/**
|
|
126
|
-
* Total attempts including the initial request. Default: 2.
|
|
127
|
-
*/
|
|
128
|
-
attempts?: number;
|
|
129
|
-
/**
|
|
130
|
-
* Base delay used for exponential backoff. Default: 500ms.
|
|
131
|
-
*/
|
|
132
|
-
backoffMs?: number;
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
interface SubmitConfig {
|
|
136
|
-
/**
|
|
137
|
-
* Local delivery mode. If provided, called with the final payload.
|
|
138
|
-
*/
|
|
139
|
-
onSubmit?: (payload: FeedbackPayload) => MaybePromise<void>;
|
|
140
|
-
/**
|
|
141
|
-
* Remote delivery mode. If provided, BlocFeed will POST the payload to the webhook.
|
|
142
|
-
*/
|
|
143
|
-
webhook?: WebhookConfig;
|
|
144
|
-
/**
|
|
145
|
-
* If both `onSubmit` and `webhook` are present, require both to succeed.
|
|
146
|
-
* Default: false (success if at least one succeeds).
|
|
147
|
-
*/
|
|
148
|
-
requireAll?: boolean;
|
|
113
|
+
interface BlocFeedConfig {
|
|
149
114
|
/**
|
|
150
|
-
*
|
|
115
|
+
* Unique identifier for the project in BlocFeed.
|
|
116
|
+
*
|
|
117
|
+
* This value is issued by the BlocFeed platform when you create a project.
|
|
118
|
+
* It is required for all feedback submissions.
|
|
151
119
|
*/
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
interface BlocFeedConfig {
|
|
120
|
+
blocfeed_id: string;
|
|
155
121
|
capture?: CaptureConfig;
|
|
156
122
|
picker?: PickerConfig;
|
|
157
123
|
metadata?: MetadataConfig;
|
|
158
|
-
submit?: SubmitConfig;
|
|
159
124
|
ui?: {
|
|
160
125
|
/**
|
|
161
126
|
* z-index for the widget overlay/panel.
|
|
@@ -166,6 +131,7 @@ interface BlocFeedConfig {
|
|
|
166
131
|
interface FeedbackPayload {
|
|
167
132
|
version: 1;
|
|
168
133
|
createdAt: string;
|
|
134
|
+
blocfeed_id: string;
|
|
169
135
|
message: string;
|
|
170
136
|
selection?: ElementDescriptor;
|
|
171
137
|
screenshots?: CaptureResult;
|
|
@@ -178,8 +144,7 @@ interface TransportResult {
|
|
|
178
144
|
}
|
|
179
145
|
interface SubmitResult {
|
|
180
146
|
ok: boolean;
|
|
181
|
-
|
|
182
|
-
webhook?: TransportResult;
|
|
147
|
+
api?: TransportResult;
|
|
183
148
|
}
|
|
184
149
|
type SessionPhase = "idle" | "picking" | "review" | "capturing" | "submitting" | "success" | "error";
|
|
185
150
|
interface BlocFeedState {
|
|
@@ -214,4 +179,4 @@ interface BlocFeedController {
|
|
|
214
179
|
}
|
|
215
180
|
declare function createBlocFeedController(config: BlocFeedConfig): BlocFeedController;
|
|
216
181
|
|
|
217
|
-
export { type BlocFeedConfig as B, type CaptureConfig as C, type ElementDescriptor as E, type FeedbackPayload as F, type HoverListener as H, type ImageAsset as I, type MaybePromise as M, type PickerConfig as P, type Rect as R, type SubmitResult as S, type TransportResult as T, type
|
|
182
|
+
export { type BlocFeedConfig as B, type CaptureConfig as C, type ElementDescriptor as E, type FeedbackPayload as F, type HoverListener as H, type ImageAsset as I, type MaybePromise as M, type PickerConfig as P, type Rect as R, type SubmitResult as S, type TransportResult as T, type BlocFeedState as a, type BlocFeedController as b, type BlocFeedError as c, type CaptureDiagnostics as d, type CaptureResult as e, type MetadataConfig as f, type MetadataContext as g, type ScreenshotAdapter as h, type ScreenshotAdapterOptions as i, type ScreenshotMime as j, type SessionPhase as k, type StateListener as l, createBlocFeedController as m };
|
|
@@ -49,7 +49,7 @@ interface CaptureResult {
|
|
|
49
49
|
diagnostics: CaptureDiagnostics;
|
|
50
50
|
}
|
|
51
51
|
interface BlocFeedError {
|
|
52
|
-
kind: "unknown" | "configuration" | "capture_failed" | "
|
|
52
|
+
kind: "unknown" | "configuration" | "capture_failed" | "api_failed" | "aborted";
|
|
53
53
|
message: string;
|
|
54
54
|
detail?: Record<string, unknown>;
|
|
55
55
|
}
|
|
@@ -110,52 +110,17 @@ interface MetadataConfig {
|
|
|
110
110
|
*/
|
|
111
111
|
enrich?: (context: MetadataContext) => MaybePromise<Record<string, unknown>>;
|
|
112
112
|
}
|
|
113
|
-
interface
|
|
114
|
-
url: string;
|
|
115
|
-
headers?: Record<string, string> | (() => MaybePromise<Record<string, string>>);
|
|
116
|
-
timeoutMs?: number;
|
|
117
|
-
/**
|
|
118
|
-
* Optional: set fetch credentials mode for the request.
|
|
119
|
-
*/
|
|
120
|
-
credentials?: RequestCredentials;
|
|
121
|
-
/**
|
|
122
|
-
* Basic retry behavior for transient failures (network errors, 5xx).
|
|
123
|
-
*/
|
|
124
|
-
retry?: {
|
|
125
|
-
/**
|
|
126
|
-
* Total attempts including the initial request. Default: 2.
|
|
127
|
-
*/
|
|
128
|
-
attempts?: number;
|
|
129
|
-
/**
|
|
130
|
-
* Base delay used for exponential backoff. Default: 500ms.
|
|
131
|
-
*/
|
|
132
|
-
backoffMs?: number;
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
interface SubmitConfig {
|
|
136
|
-
/**
|
|
137
|
-
* Local delivery mode. If provided, called with the final payload.
|
|
138
|
-
*/
|
|
139
|
-
onSubmit?: (payload: FeedbackPayload) => MaybePromise<void>;
|
|
140
|
-
/**
|
|
141
|
-
* Remote delivery mode. If provided, BlocFeed will POST the payload to the webhook.
|
|
142
|
-
*/
|
|
143
|
-
webhook?: WebhookConfig;
|
|
144
|
-
/**
|
|
145
|
-
* If both `onSubmit` and `webhook` are present, require both to succeed.
|
|
146
|
-
* Default: false (success if at least one succeeds).
|
|
147
|
-
*/
|
|
148
|
-
requireAll?: boolean;
|
|
113
|
+
interface BlocFeedConfig {
|
|
149
114
|
/**
|
|
150
|
-
*
|
|
115
|
+
* Unique identifier for the project in BlocFeed.
|
|
116
|
+
*
|
|
117
|
+
* This value is issued by the BlocFeed platform when you create a project.
|
|
118
|
+
* It is required for all feedback submissions.
|
|
151
119
|
*/
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
interface BlocFeedConfig {
|
|
120
|
+
blocfeed_id: string;
|
|
155
121
|
capture?: CaptureConfig;
|
|
156
122
|
picker?: PickerConfig;
|
|
157
123
|
metadata?: MetadataConfig;
|
|
158
|
-
submit?: SubmitConfig;
|
|
159
124
|
ui?: {
|
|
160
125
|
/**
|
|
161
126
|
* z-index for the widget overlay/panel.
|
|
@@ -166,6 +131,7 @@ interface BlocFeedConfig {
|
|
|
166
131
|
interface FeedbackPayload {
|
|
167
132
|
version: 1;
|
|
168
133
|
createdAt: string;
|
|
134
|
+
blocfeed_id: string;
|
|
169
135
|
message: string;
|
|
170
136
|
selection?: ElementDescriptor;
|
|
171
137
|
screenshots?: CaptureResult;
|
|
@@ -178,8 +144,7 @@ interface TransportResult {
|
|
|
178
144
|
}
|
|
179
145
|
interface SubmitResult {
|
|
180
146
|
ok: boolean;
|
|
181
|
-
|
|
182
|
-
webhook?: TransportResult;
|
|
147
|
+
api?: TransportResult;
|
|
183
148
|
}
|
|
184
149
|
type SessionPhase = "idle" | "picking" | "review" | "capturing" | "submitting" | "success" | "error";
|
|
185
150
|
interface BlocFeedState {
|
|
@@ -214,4 +179,4 @@ interface BlocFeedController {
|
|
|
214
179
|
}
|
|
215
180
|
declare function createBlocFeedController(config: BlocFeedConfig): BlocFeedController;
|
|
216
181
|
|
|
217
|
-
export { type BlocFeedConfig as B, type CaptureConfig as C, type ElementDescriptor as E, type FeedbackPayload as F, type HoverListener as H, type ImageAsset as I, type MaybePromise as M, type PickerConfig as P, type Rect as R, type SubmitResult as S, type TransportResult as T, type
|
|
182
|
+
export { type BlocFeedConfig as B, type CaptureConfig as C, type ElementDescriptor as E, type FeedbackPayload as F, type HoverListener as H, type ImageAsset as I, type MaybePromise as M, type PickerConfig as P, type Rect as R, type SubmitResult as S, type TransportResult as T, type BlocFeedState as a, type BlocFeedController as b, type BlocFeedError as c, type CaptureDiagnostics as d, type CaptureResult as e, type MetadataConfig as f, type MetadataContext as g, type ScreenshotAdapter as h, type ScreenshotAdapterOptions as i, type ScreenshotMime as j, type SessionPhase as k, type StateListener as l, createBlocFeedController as m };
|
package/dist/engine.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var
|
|
1
|
+
'use strict';var chunkNMFAZKHE_cjs=require('./chunk-NMFAZKHE.cjs');function d(n){let[r,o]=n.split(",",2);if(!r||!o)throw new Error("Invalid data URL");let c=/data:(.*?);base64/.exec(r)?.[1]||"application/octet-stream",t=atob(o),a=new Uint8Array(t.length);for(let e=0;e<t.length;e+=1)a[e]=t.charCodeAt(e);return new Blob([a],{type:c})}Object.defineProperty(exports,"collectMetadata",{enumerable:true,get:function(){return chunkNMFAZKHE_cjs.e}});Object.defineProperty(exports,"createBlocFeedController",{enumerable:true,get:function(){return chunkNMFAZKHE_cjs.f}});Object.defineProperty(exports,"createHtmlToImageAdapter",{enumerable:true,get:function(){return chunkNMFAZKHE_cjs.c}});Object.defineProperty(exports,"runCapture",{enumerable:true,get:function(){return chunkNMFAZKHE_cjs.d}});exports.dataUrlToBlob=d;
|
package/dist/engine.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { h as ScreenshotAdapter, C as CaptureConfig, e as CaptureResult, f as MetadataConfig, g as MetadataContext } from './controller-
|
|
2
|
-
export { B as BlocFeedConfig, b as BlocFeedController, c as BlocFeedError, a as BlocFeedState, E as ElementDescriptor, F as FeedbackPayload, H as HoverListener, I as ImageAsset, R as Rect, i as ScreenshotAdapterOptions, j as ScreenshotMime, k as SessionPhase,
|
|
1
|
+
import { h as ScreenshotAdapter, C as CaptureConfig, e as CaptureResult, f as MetadataConfig, g as MetadataContext } from './controller-DmWFVHoj.cjs';
|
|
2
|
+
export { B as BlocFeedConfig, b as BlocFeedController, c as BlocFeedError, a as BlocFeedState, E as ElementDescriptor, F as FeedbackPayload, H as HoverListener, I as ImageAsset, R as Rect, i as ScreenshotAdapterOptions, j as ScreenshotMime, k as SessionPhase, l as StateListener, S as SubmitResult, m as createBlocFeedController } from './controller-DmWFVHoj.cjs';
|
|
3
3
|
|
|
4
4
|
declare function createHtmlToImageAdapter(): ScreenshotAdapter;
|
|
5
5
|
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { h as ScreenshotAdapter, C as CaptureConfig, e as CaptureResult, f as MetadataConfig, g as MetadataContext } from './controller-
|
|
2
|
-
export { B as BlocFeedConfig, b as BlocFeedController, c as BlocFeedError, a as BlocFeedState, E as ElementDescriptor, F as FeedbackPayload, H as HoverListener, I as ImageAsset, R as Rect, i as ScreenshotAdapterOptions, j as ScreenshotMime, k as SessionPhase,
|
|
1
|
+
import { h as ScreenshotAdapter, C as CaptureConfig, e as CaptureResult, f as MetadataConfig, g as MetadataContext } from './controller-DmWFVHoj.js';
|
|
2
|
+
export { B as BlocFeedConfig, b as BlocFeedController, c as BlocFeedError, a as BlocFeedState, E as ElementDescriptor, F as FeedbackPayload, H as HoverListener, I as ImageAsset, R as Rect, i as ScreenshotAdapterOptions, j as ScreenshotMime, k as SessionPhase, l as StateListener, S as SubmitResult, m as createBlocFeedController } from './controller-DmWFVHoj.js';
|
|
3
3
|
|
|
4
4
|
declare function createHtmlToImageAdapter(): ScreenshotAdapter;
|
|
5
5
|
|
package/dist/engine.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{e as collectMetadata,f as createBlocFeedController,c as createHtmlToImageAdapter,d as runCapture}from'./chunk-
|
|
1
|
+
export{e as collectMetadata,f as createBlocFeedController,c as createHtmlToImageAdapter,d as runCapture}from'./chunk-H36VLJXX.js';function d(n){let[r,o]=n.split(",",2);if(!r||!o)throw new Error("Invalid data URL");let c=/data:(.*?);base64/.exec(r)?.[1]||"application/octet-stream",t=atob(o),a=new Uint8Array(t.length);for(let e=0;e<t.length;e+=1)a[e]=t.charCodeAt(e);return new Blob([a],{type:c})}export{d as dataUrlToBlob};
|
package/dist/main.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
'use strict';var
|
|
2
|
+
'use strict';var chunkNMFAZKHE_cjs=require('./chunk-NMFAZKHE.cjs'),react=require('react'),jsxRuntime=require('react/jsx-runtime'),reactDom=require('react-dom');var g=react.createContext(null);function w(t){let e=react.useMemo(()=>chunkNMFAZKHE_cjs.f({...t.config??{},blocfeed_id:t.blocfeed_id}),[]),[a,l]=react.useState(()=>e.getState());return react.useEffect(()=>e.subscribe(l),[e]),react.useEffect(()=>e.setConfig({...t.config??{},blocfeed_id:t.blocfeed_id}),[e,t.config,t.blocfeed_id]),react.useEffect(()=>()=>e.destroy(),[e]),jsxRuntime.jsx(g.Provider,{value:{controller:e,state:a},children:t.children})}var H="blocfeed-styles-v1",D=`
|
|
3
3
|
:where([data-blocfeed-ui-root]),
|
|
4
4
|
:where([data-blocfeed-ui-root]) * {
|
|
5
5
|
box-sizing: border-box;
|
|
@@ -220,4 +220,4 @@
|
|
|
220
220
|
font-size: 13px;
|
|
221
221
|
color: rgba(243, 244, 246, 0.9);
|
|
222
222
|
}
|
|
223
|
-
`;function
|
|
223
|
+
`;function T(){if(!chunkNMFAZKHE_cjs.a()||document.getElementById(H))return;let t=document.createElement("style");t.id=H,t.textContent=D,document.head.appendChild(t);}function y(){let t=react.useContext(g);if(!t)throw new Error("useBlocFeed must be used within a <BlocFeedProvider />");return {state:t.state,controller:t.controller,start:t.controller.start,stop:t.controller.stop,clearSelection:t.controller.clearSelection,submit:t.controller.submit}}function X(t,e,a){return Math.max(e,Math.min(a,t))}function G(t,e){let l=window.innerWidth,r=window.innerHeight,s=X(t.x,12,Math.max(12,l-e-12)),p=t.y+t.height+12,m=Math.max(12,t.y-240);return {top:p+240<=r?p:m,left:s}}function V(t){let{rect:e}=t,a={left:`${e.x}px`,top:`${e.y}px`,width:`${Math.max(0,e.width)}px`,height:`${Math.max(0,e.height)}px`};return jsxRuntime.jsx("div",{className:"bf-highlight",style:a})}function Y(t){let{state:e,controller:a,start:l,stop:r,clearSelection:s,submit:p}=y(),[m,C]=react.useState(null),[h,F]=react.useState(""),[B,k]=react.useState(t.config.capture?.element??true),[S,P]=react.useState(t.config.capture?.fullPage??false),[A,N]=react.useState(null);react.useEffect(()=>a.subscribeHover(C),[a]),react.useEffect(()=>{let n=a.__unsafeGetSelectedElement();if(!n||e.phase==="idle"||e.phase==="picking"){N(null);return}let z=()=>{N(chunkNMFAZKHE_cjs.b(n.getBoundingClientRect()));};z();let u=()=>z();return window.addEventListener("scroll",u,{capture:true,passive:true}),window.addEventListener("resize",u,{passive:true}),()=>{window.removeEventListener("scroll",u,{capture:true}),window.removeEventListener("resize",u);}},[a,e.phase,e.selection?.selector]),react.useEffect(()=>{e.phase==="review"&&(F(""),k(t.config.capture?.element??true),P(t.config.capture?.fullPage??false));},[e.phase,e.selection?.selector,t.config.capture?.element,t.config.capture?.fullPage]),react.useEffect(()=>{if(e.phase!=="success")return;let n=window.setTimeout(()=>r(),1200);return ()=>window.clearTimeout(n)},[e.phase,r]);let d=e.phase==="capturing"||e.phase==="submitting",c=e.phase==="picking"?m?.rect??null:A??e.selection?.rect??null,x=react.useMemo(()=>c?G(c,360):null,[c?.x,c?.y,c?.width,c?.height]),R=e.lastError?.message;return jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[e.phase==="idle"&&jsxRuntime.jsxs("button",{className:"bf-trigger",type:"button",onClick:()=>l(),"aria-label":"Give feedback",children:[jsxRuntime.jsx("span",{className:"bf-dot","aria-hidden":"true"}),"Feedback"]}),e.phase!=="idle"&&jsxRuntime.jsxs("div",{className:"bf-overlay",children:[e.phase!=="picking"&&jsxRuntime.jsx("div",{className:"bf-blocker",role:"presentation",onClick:()=>r()}),c&&jsxRuntime.jsx(V,{rect:c}),e.phase==="picking"&&jsxRuntime.jsxs("div",{className:"bf-hint",children:[jsxRuntime.jsxs("p",{children:["Click an element to attach your feedback. Press ",jsxRuntime.jsx("strong",{children:"Esc"})," to cancel."]}),jsxRuntime.jsx("button",{type:"button",className:"bf-btn",onClick:()=>r(),children:"Cancel"})]}),(e.phase==="review"||e.phase==="capturing"||e.phase==="submitting"||e.phase==="error"||e.phase==="success")&&x&&jsxRuntime.jsxs("div",{className:"bf-panel",style:{left:x.left,top:x.top},children:[jsxRuntime.jsxs("div",{className:"bf-panelHeader",children:[jsxRuntime.jsx("div",{className:"bf-title",children:"Feedback"}),jsxRuntime.jsxs("div",{className:"bf-row",style:{justifyContent:"flex-end"},children:[jsxRuntime.jsx("button",{type:"button",className:"bf-btn",onClick:()=>s(),disabled:d,children:"Re-pick"}),jsxRuntime.jsx("button",{type:"button",className:"bf-btn",onClick:()=>r(),disabled:d,children:"Close"})]})]}),jsxRuntime.jsxs("div",{className:"bf-panelBody",children:[jsxRuntime.jsx("textarea",{className:"bf-textarea",placeholder:"What\u2019s happening? What did you expect?",value:h,onChange:n=>F(n.target.value),disabled:d}),jsxRuntime.jsxs("div",{className:"bf-row",children:[jsxRuntime.jsxs("label",{children:[jsxRuntime.jsx("input",{type:"checkbox",checked:B,onChange:n=>k(n.target.checked),disabled:d}),"Screenshot element"]}),jsxRuntime.jsxs("label",{children:[jsxRuntime.jsx("input",{type:"checkbox",checked:S,onChange:n=>P(n.target.checked),disabled:d}),"Full page"]})]}),e.phase==="capturing"&&jsxRuntime.jsx("div",{className:"bf-status",children:"Capturing screenshots\u2026"}),e.phase==="submitting"&&jsxRuntime.jsx("div",{className:"bf-status",children:"Submitting\u2026"}),e.phase==="success"&&jsxRuntime.jsx("div",{className:"bf-status",children:"Sent. Thank you!"}),e.phase==="error"&&R&&jsxRuntime.jsx("div",{className:"bf-error",children:R}),jsxRuntime.jsxs("div",{className:"bf-actions",children:[jsxRuntime.jsx("button",{type:"button",className:"bf-btn",onClick:()=>r(),disabled:d,children:"Cancel"}),jsxRuntime.jsx("button",{type:"button",className:"bf-btn bf-btnPrimary",onClick:()=>p(h,{capture:{element:B,fullPage:S}}),disabled:d||h.trim().length===0,children:"Send"})]})]})]})]}),e.phase==="success"&&jsxRuntime.jsx("div",{className:"bf-toast","aria-live":"polite",children:"Feedback sent"})]})}function q(t){let e={...t.config??{},blocfeed_id:t.blocfeed_id},[a,l]=react.useState(null);return react.useEffect(()=>{T();let r=document.createElement("div");r.setAttribute("data-blocfeed-ui-root","true"),r.setAttribute("data-blocfeed-ui","true");let s=e.ui?.zIndex;return typeof s=="number"&&r.style.setProperty("--bf-z",String(s)),document.body.appendChild(r),l(r),()=>{r.remove(),l(null);}},[e.ui?.zIndex]),a?reactDom.createPortal(jsxRuntime.jsx(w,{blocfeed_id:e.blocfeed_id,...t.config?{config:t.config}:{},children:jsxRuntime.jsx(Y,{config:e})}),a):null}exports.BlocFeedProvider=w;exports.BlocFeedWidget=q;exports.useBlocFeed=y;
|
package/dist/main.d.cts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { B as BlocFeedConfig, a as BlocFeedState, b as BlocFeedController, C as CaptureConfig, S as SubmitResult } from './controller-
|
|
2
|
-
export { c as BlocFeedError, d as CaptureDiagnostics, e as CaptureResult, E as ElementDescriptor, F as FeedbackPayload, I as ImageAsset, M as MaybePromise, f as MetadataConfig, g as MetadataContext, P as PickerConfig, R as Rect, h as ScreenshotAdapter, i as ScreenshotAdapterOptions, j as ScreenshotMime, k as SessionPhase,
|
|
1
|
+
import { B as BlocFeedConfig, a as BlocFeedState, b as BlocFeedController, C as CaptureConfig, S as SubmitResult } from './controller-DmWFVHoj.cjs';
|
|
2
|
+
export { c as BlocFeedError, d as CaptureDiagnostics, e as CaptureResult, E as ElementDescriptor, F as FeedbackPayload, I as ImageAsset, M as MaybePromise, f as MetadataConfig, g as MetadataContext, P as PickerConfig, R as Rect, h as ScreenshotAdapter, i as ScreenshotAdapterOptions, j as ScreenshotMime, k as SessionPhase, T as TransportResult } from './controller-DmWFVHoj.cjs';
|
|
3
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
import * as react from 'react';
|
|
5
5
|
import { ReactNode } from 'react';
|
|
6
6
|
|
|
7
7
|
type BlocFeedProviderProps = {
|
|
8
|
-
|
|
8
|
+
blocfeed_id: string;
|
|
9
|
+
config?: Omit<BlocFeedConfig, "blocfeed_id">;
|
|
9
10
|
children: ReactNode;
|
|
10
11
|
};
|
|
11
12
|
declare function BlocFeedProvider(props: BlocFeedProviderProps): react_jsx_runtime.JSX.Element;
|
|
12
13
|
|
|
13
14
|
type BlocFeedWidgetProps = {
|
|
14
|
-
|
|
15
|
+
blocfeed_id: string;
|
|
16
|
+
config?: Omit<BlocFeedConfig, "blocfeed_id">;
|
|
15
17
|
};
|
|
16
18
|
declare function BlocFeedWidget(props: BlocFeedWidgetProps): react.ReactPortal | null;
|
|
17
19
|
|
package/dist/main.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { B as BlocFeedConfig, a as BlocFeedState, b as BlocFeedController, C as CaptureConfig, S as SubmitResult } from './controller-
|
|
2
|
-
export { c as BlocFeedError, d as CaptureDiagnostics, e as CaptureResult, E as ElementDescriptor, F as FeedbackPayload, I as ImageAsset, M as MaybePromise, f as MetadataConfig, g as MetadataContext, P as PickerConfig, R as Rect, h as ScreenshotAdapter, i as ScreenshotAdapterOptions, j as ScreenshotMime, k as SessionPhase,
|
|
1
|
+
import { B as BlocFeedConfig, a as BlocFeedState, b as BlocFeedController, C as CaptureConfig, S as SubmitResult } from './controller-DmWFVHoj.js';
|
|
2
|
+
export { c as BlocFeedError, d as CaptureDiagnostics, e as CaptureResult, E as ElementDescriptor, F as FeedbackPayload, I as ImageAsset, M as MaybePromise, f as MetadataConfig, g as MetadataContext, P as PickerConfig, R as Rect, h as ScreenshotAdapter, i as ScreenshotAdapterOptions, j as ScreenshotMime, k as SessionPhase, T as TransportResult } from './controller-DmWFVHoj.js';
|
|
3
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
import * as react from 'react';
|
|
5
5
|
import { ReactNode } from 'react';
|
|
6
6
|
|
|
7
7
|
type BlocFeedProviderProps = {
|
|
8
|
-
|
|
8
|
+
blocfeed_id: string;
|
|
9
|
+
config?: Omit<BlocFeedConfig, "blocfeed_id">;
|
|
9
10
|
children: ReactNode;
|
|
10
11
|
};
|
|
11
12
|
declare function BlocFeedProvider(props: BlocFeedProviderProps): react_jsx_runtime.JSX.Element;
|
|
12
13
|
|
|
13
14
|
type BlocFeedWidgetProps = {
|
|
14
|
-
|
|
15
|
+
blocfeed_id: string;
|
|
16
|
+
config?: Omit<BlocFeedConfig, "blocfeed_id">;
|
|
15
17
|
};
|
|
16
18
|
declare function BlocFeedWidget(props: BlocFeedWidgetProps): react.ReactPortal | null;
|
|
17
19
|
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {f,a,b}from'./chunk-
|
|
2
|
+
import {f,a,b}from'./chunk-H36VLJXX.js';import {createContext,useMemo,useState,useEffect,useContext}from'react';import {jsx,jsxs,Fragment}from'react/jsx-runtime';import {createPortal}from'react-dom';var g=createContext(null);function w(t){let e=useMemo(()=>f({...t.config??{},blocfeed_id:t.blocfeed_id}),[]),[a,l]=useState(()=>e.getState());return useEffect(()=>e.subscribe(l),[e]),useEffect(()=>e.setConfig({...t.config??{},blocfeed_id:t.blocfeed_id}),[e,t.config,t.blocfeed_id]),useEffect(()=>()=>e.destroy(),[e]),jsx(g.Provider,{value:{controller:e,state:a},children:t.children})}var H="blocfeed-styles-v1",D=`
|
|
3
3
|
:where([data-blocfeed-ui-root]),
|
|
4
4
|
:where([data-blocfeed-ui-root]) * {
|
|
5
5
|
box-sizing: border-box;
|
|
@@ -220,4 +220,4 @@ import {f,a,b}from'./chunk-4EGJPVEG.js';import {createContext,useMemo,useState,u
|
|
|
220
220
|
font-size: 13px;
|
|
221
221
|
color: rgba(243, 244, 246, 0.9);
|
|
222
222
|
}
|
|
223
|
-
`;function
|
|
223
|
+
`;function T(){if(!a()||document.getElementById(H))return;let t=document.createElement("style");t.id=H,t.textContent=D,document.head.appendChild(t);}function y(){let t=useContext(g);if(!t)throw new Error("useBlocFeed must be used within a <BlocFeedProvider />");return {state:t.state,controller:t.controller,start:t.controller.start,stop:t.controller.stop,clearSelection:t.controller.clearSelection,submit:t.controller.submit}}function X(t,e,a){return Math.max(e,Math.min(a,t))}function G(t,e){let l=window.innerWidth,r=window.innerHeight,s=X(t.x,12,Math.max(12,l-e-12)),p=t.y+t.height+12,m=Math.max(12,t.y-240);return {top:p+240<=r?p:m,left:s}}function V(t){let{rect:e}=t,a={left:`${e.x}px`,top:`${e.y}px`,width:`${Math.max(0,e.width)}px`,height:`${Math.max(0,e.height)}px`};return jsx("div",{className:"bf-highlight",style:a})}function Y(t){let{state:e,controller:a,start:l,stop:r,clearSelection:s,submit:p}=y(),[m,C]=useState(null),[h,F]=useState(""),[B,k]=useState(t.config.capture?.element??true),[S,P]=useState(t.config.capture?.fullPage??false),[A,N]=useState(null);useEffect(()=>a.subscribeHover(C),[a]),useEffect(()=>{let n=a.__unsafeGetSelectedElement();if(!n||e.phase==="idle"||e.phase==="picking"){N(null);return}let z=()=>{N(b(n.getBoundingClientRect()));};z();let u=()=>z();return window.addEventListener("scroll",u,{capture:true,passive:true}),window.addEventListener("resize",u,{passive:true}),()=>{window.removeEventListener("scroll",u,{capture:true}),window.removeEventListener("resize",u);}},[a,e.phase,e.selection?.selector]),useEffect(()=>{e.phase==="review"&&(F(""),k(t.config.capture?.element??true),P(t.config.capture?.fullPage??false));},[e.phase,e.selection?.selector,t.config.capture?.element,t.config.capture?.fullPage]),useEffect(()=>{if(e.phase!=="success")return;let n=window.setTimeout(()=>r(),1200);return ()=>window.clearTimeout(n)},[e.phase,r]);let d=e.phase==="capturing"||e.phase==="submitting",c=e.phase==="picking"?m?.rect??null:A??e.selection?.rect??null,x=useMemo(()=>c?G(c,360):null,[c?.x,c?.y,c?.width,c?.height]),R=e.lastError?.message;return jsxs(Fragment,{children:[e.phase==="idle"&&jsxs("button",{className:"bf-trigger",type:"button",onClick:()=>l(),"aria-label":"Give feedback",children:[jsx("span",{className:"bf-dot","aria-hidden":"true"}),"Feedback"]}),e.phase!=="idle"&&jsxs("div",{className:"bf-overlay",children:[e.phase!=="picking"&&jsx("div",{className:"bf-blocker",role:"presentation",onClick:()=>r()}),c&&jsx(V,{rect:c}),e.phase==="picking"&&jsxs("div",{className:"bf-hint",children:[jsxs("p",{children:["Click an element to attach your feedback. Press ",jsx("strong",{children:"Esc"})," to cancel."]}),jsx("button",{type:"button",className:"bf-btn",onClick:()=>r(),children:"Cancel"})]}),(e.phase==="review"||e.phase==="capturing"||e.phase==="submitting"||e.phase==="error"||e.phase==="success")&&x&&jsxs("div",{className:"bf-panel",style:{left:x.left,top:x.top},children:[jsxs("div",{className:"bf-panelHeader",children:[jsx("div",{className:"bf-title",children:"Feedback"}),jsxs("div",{className:"bf-row",style:{justifyContent:"flex-end"},children:[jsx("button",{type:"button",className:"bf-btn",onClick:()=>s(),disabled:d,children:"Re-pick"}),jsx("button",{type:"button",className:"bf-btn",onClick:()=>r(),disabled:d,children:"Close"})]})]}),jsxs("div",{className:"bf-panelBody",children:[jsx("textarea",{className:"bf-textarea",placeholder:"What\u2019s happening? What did you expect?",value:h,onChange:n=>F(n.target.value),disabled:d}),jsxs("div",{className:"bf-row",children:[jsxs("label",{children:[jsx("input",{type:"checkbox",checked:B,onChange:n=>k(n.target.checked),disabled:d}),"Screenshot element"]}),jsxs("label",{children:[jsx("input",{type:"checkbox",checked:S,onChange:n=>P(n.target.checked),disabled:d}),"Full page"]})]}),e.phase==="capturing"&&jsx("div",{className:"bf-status",children:"Capturing screenshots\u2026"}),e.phase==="submitting"&&jsx("div",{className:"bf-status",children:"Submitting\u2026"}),e.phase==="success"&&jsx("div",{className:"bf-status",children:"Sent. Thank you!"}),e.phase==="error"&&R&&jsx("div",{className:"bf-error",children:R}),jsxs("div",{className:"bf-actions",children:[jsx("button",{type:"button",className:"bf-btn",onClick:()=>r(),disabled:d,children:"Cancel"}),jsx("button",{type:"button",className:"bf-btn bf-btnPrimary",onClick:()=>p(h,{capture:{element:B,fullPage:S}}),disabled:d||h.trim().length===0,children:"Send"})]})]})]})]}),e.phase==="success"&&jsx("div",{className:"bf-toast","aria-live":"polite",children:"Feedback sent"})]})}function q(t){let e={...t.config??{},blocfeed_id:t.blocfeed_id},[a,l]=useState(null);return useEffect(()=>{T();let r=document.createElement("div");r.setAttribute("data-blocfeed-ui-root","true"),r.setAttribute("data-blocfeed-ui","true");let s=e.ui?.zIndex;return typeof s=="number"&&r.style.setProperty("--bf-z",String(s)),document.body.appendChild(r),l(r),()=>{r.remove(),l(null);}},[e.ui?.zIndex]),a?createPortal(jsx(w,{blocfeed_id:e.blocfeed_id,...t.config?{config:t.config}:{},children:jsx(Y,{config:e})}),a):null}export{w as BlocFeedProvider,q as BlocFeedWidget,y as useBlocFeed};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blocfeed",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Drop-in feedback + screenshot widget for React.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"dist/**/*.cjs",
|
|
24
24
|
"dist/**/*.d.ts",
|
|
25
25
|
"dist/**/*.d.cts",
|
|
26
|
+
"CHANGELOG.md",
|
|
26
27
|
"README.md",
|
|
27
28
|
"LICENSE"
|
|
28
29
|
],
|
|
@@ -67,5 +68,10 @@
|
|
|
67
68
|
"tsup": "^8.0.2",
|
|
68
69
|
"typescript": "^5.5.4",
|
|
69
70
|
"vitest": "^1.6.0"
|
|
70
|
-
}
|
|
71
|
+
},
|
|
72
|
+
"directories": {
|
|
73
|
+
"doc": "docs",
|
|
74
|
+
"test": "tests"
|
|
75
|
+
},
|
|
76
|
+
"author": ""
|
|
71
77
|
}
|
package/dist/chunk-4EGJPVEG.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function y(){return typeof window<"u"&&typeof document<"u"}function W(e){let t=globalThis.CSS;return typeof t?.escape=="function"?t.escape(e):e.replace(/[^a-zA-Z0-9_-]/g,n=>{let r=n.codePointAt(0);return r===void 0?"":`\\${r.toString(16)} `})}function O(e){return {x:e.x,y:e.y,width:e.width,height:e.height}}function ce(e){return {x:e.x+window.scrollX,y:e.y+window.scrollY,width:e.width,height:e.height}}function ue(e){return e.replace(/\s+/g," ").trim()}function de(e,t=140){let n=e.textContent;if(!n)return;let r=ue(n);if(r)return r.length<=t?r:`${r.slice(0,t-1)}\u2026`}function fe(e){let t=1;for(let n=e.previousElementSibling;n;n=n.previousElementSibling)n.tagName===e.tagName&&(t+=1);return t}var Y=["data-testid","data-test-id","data-test","data-qa","data-cy"],j="data-blocfeed-component";function me(e){let t=e.closest(`[${j}]`);if(!t)return;let r=t.getAttribute(j)?.trim();return r||void 0}function pe(e){for(let t of Y){let n=e.closest(`[${t}]`);if(!n)continue;let o=n.getAttribute(t)?.trim();if(o)return o}}function ge(e){try{let t=e,n=Object.getOwnPropertyNames(t);for(let r of n)if(r.startsWith("__reactFiber$")||r.startsWith("__reactInternalInstance$")){let o=t[r];if(o&&typeof o=="object")return o}}catch{}return null}function R(e){if(e&&typeof e!="string"){if(typeof e=="function"){let t=e;return typeof t.displayName=="string"&&t.displayName?t.displayName:typeof t.name=="string"&&t.name?t.name:void 0}if(typeof e=="object"){let t=e,n=t.displayName;if(typeof n=="string"&&n)return n;let r=t.render;if(typeof r=="function"){let i=r;if(typeof i.displayName=="string"&&i.displayName)return i.displayName;if(typeof i.name=="string"&&i.name)return i.name}let o=t.type;return R(o)}}}function we(e){let t=ge(e);if(!t)return;let n=t._debugOwner;for(let o=0;n&&o<50;o+=1){let i=R(n.type)??R(n.elementType);if(i)return i;n=n._debugOwner;}let r=t;for(let o=0;r&&o<80;o+=1){let i=R(r.type)??R(r.elementType);if(i)return i;r=r.return;}}function be(e){let t=e.tagName.toLowerCase(),n=e.getAttribute("id");if(n)return `#${W(n)}`;for(let r of Y){let o=e.getAttribute(r);if(o)return `${t}[${r}="${W(o)}"]`}return `${t}:nth-of-type(${fe(e)})`}function he(e,t=10){let n=[],r=e;for(;r&&n.length<t;){let o=be(r);if(n.unshift(o),o.startsWith("#"))break;r=r.parentElement;}return n.join(" > ")}function z(e,t){if(!t||t.length===0)return false;for(let n of t)if(e.closest(n))return true;return false}function _(e,t){if(!e||z(e,t.ignoreSelectors))return null;let n=e;for(;n;){if(z(n,t.ignoreSelectors))return null;if(t.isSelectable?.(n)??ye(n))return n;n=n.parentElement;}return null}function ye(e){let t=e.tagName;return !(t==="HTML"||t==="BODY")}function Q(e){let t=e.getBoundingClientRect(),n={selector:he(e),tagName:e.tagName.toLowerCase(),rect:O(t),pageRect:ce(t)},r=e.getAttribute("id");r&&(n.id=r);let o=e.className;typeof o=="string"&&o.trim()&&(n.className=o);let i=de(e);i&&(n.textSnippet=i);let s=me(e)??we(e);s&&(n.componentName=s);let l=pe(e);return l&&(n.testId=l),n}var D=null;async function X(){return D||(D=import('html-to-image')),D}async function Ee(e){return await new Promise((t,n)=>{let r=new Image;r.onload=()=>t({width:r.naturalWidth,height:r.naturalHeight}),r.onerror=()=>n(new Error("Failed to load generated screenshot")),r.src=e;})}async function J(e,t){let{width:n,height:r}=await Ee(e);return {dataUrl:e,mime:t,width:n,height:r}}function F(e){if(e?.aborted)throw new Error("Aborted")}function G(){return {async captureElement(e,t){if(!y())throw new Error("captureElement can only run in the browser");F(t.signal);let n=await X();F(t.signal);let r=t.mime==="image/jpeg"?await n.toJpeg(e,{quality:t.quality??.92,pixelRatio:t.pixelRatio}):await n.toPng(e,{pixelRatio:t.pixelRatio});return F(t.signal),await J(r,t.mime)},async captureFullPage(e){if(!y())throw new Error("captureFullPage can only run in the browser");F(e.signal);let t=document.documentElement,n=Math.max(t.scrollWidth,t.clientWidth),r=Math.max(t.scrollHeight,t.clientHeight),o=Math.min(1,e.maxDimension/Math.max(n,r)),i=Math.max(1,Math.round(n*o)),s=Math.max(1,Math.round(r*o)),l=await X();F(e.signal);let c=e.mime==="image/jpeg"?await l.toJpeg(t,{width:i,height:s,quality:e.quality??.92,pixelRatio:e.pixelRatio}):await l.toPng(t,{width:i,height:s,pixelRatio:e.pixelRatio});return F(e.signal),await J(c,e.mime)}}}function ke(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return "Unknown error"}}function p(e,t,n){let r={kind:e,message:ke(t)};return n&&(r.detail=n),r}var Se=12e3,ve=2048,xe=.92;function K(){return Date.now()}function Pe(e){return new Promise((t,n)=>{let r=()=>n(new Error("Aborted"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:true});})}async function Z(e,t,n){let r=new Promise((i,s)=>{let l=setTimeout(()=>s(new Error("Timeout")),t);typeof l.unref=="function"&&l.unref();}),o=[e,r];return n&&o.push(Pe(n)),await Promise.race(o)}function Ce(e){if(!y())return 1;let t=window.devicePixelRatio||1,n=e?.pixelRatio??Math.min(t,2);return Math.max(.1,n)}function Fe(e){return !!(e?.element||e?.fullPage)}function V(e){let t={mime:e.mime,pixelRatio:e.pixelRatio,maxDimension:e.maxDimension};return e.includeQuality&&(t.quality=e.quality),e.signal&&(t.signal=e.signal),t}async function ee(e){let{selectionElement:t,capture:n,signal:r}=e;if(!y()||!Fe(n))return;let o=K(),i=[],s=n?.timeoutMs??Se,l=n?.maxDimension??ve,c=n?.mime??"image/png",m=n?.quality??xe,k=n?.adapter??G(),E={},u=Ce(n);if(n?.element&&t)try{let d=t.getBoundingClientRect(),g=Math.min(1,l/Math.max(d.width,d.height)),f=Math.min(u,u*g),S=await Z(Promise.resolve(k.captureElement(t,{...V({mime:c,quality:m,pixelRatio:f,maxDimension:l,includeQuality:c==="image/jpeg",...r?{signal:r}:{}})})),s,r);E.element=S;}catch(d){if(r?.aborted)throw d;i.push(p("capture_failed",d,{target:"element"}));}if(n?.fullPage)try{let d=await Z(Promise.resolve(k.captureFullPage(V({mime:c,quality:m,pixelRatio:u,maxDimension:l,includeQuality:c==="image/jpeg",...r?{signal:r}:{}}))),s,r);E.fullPage=d;}catch(d){if(r?.aborted)throw d;i.push(p("capture_failed",d,{target:"fullPage"}));}let b=K(),a={startedAt:o,finishedAt:b,durationMs:Math.max(0,b-o)};return i.length>0&&(a.errors=i),{...E,diagnostics:a}}function Te(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return}}function Ae(){return y()?{url:window.location.href,title:document.title,referrer:document.referrer||void 0,userAgent:navigator.userAgent,language:navigator.language,platform:navigator.platform,viewport:{width:window.innerWidth,height:window.innerHeight},screen:{width:window.screen.width,height:window.screen.height},scroll:{x:window.scrollX,y:window.scrollY},devicePixelRatio:window.devicePixelRatio||1,timezone:Te()}:{}}async function te(e){let{config:t,context:n}=e;if(t?.enabled===false)return {};let r=Ae(),o=t?.enrich;if(!o)return r;try{let i=await o(n);return {...r,...i}}catch(i){let s=p("unknown",i);return {...r,blocfeedMetadataError:s.message}}}function $(e){let t=null,n=null,r=(...o)=>{n=o,t===null&&(t=requestAnimationFrame(()=>{if(t=null,!n)return;let i=n;n=null,e(...i);}));};return r.cancel=()=>{t!==null&&cancelAnimationFrame(t),t=null,n=null;},r}function M(e){return e instanceof Element?!!e.closest("[data-blocfeed-ui]"):false}function B(e){e.stopPropagation(),e.stopImmediatePropagation?.();}function ne(e,t){if(!y())throw new Error("BlocFeed picker can only run in a browser environment.");let n=e.ignoreSelectors,r=e.isSelectable,o={};n&&n.length>0&&(o.ignoreSelectors=n),r&&(o.isSelectable=r);let i=null,s=null,l=(a,d=false)=>{if(!a){i=null,s=null,t.onHover(null);return}let g=O(a.getBoundingClientRect()),f=`${Math.round(g.x)}:${Math.round(g.y)}:${Math.round(g.width)}:${Math.round(g.height)}`;!d&&a===i&&f===s||(i=a,s=f,t.onHover({element:a,rect:g}));},c=$(a=>{if(M(a.target))return;let d=document.elementFromPoint(a.clientX,a.clientY),g=_(d,o);l(g);}),m=$(()=>{i&&l(i,true);}),k=a=>{M(a.target)||(B(a),a.pointerType==="mouse"&&a.preventDefault());},E=a=>{M(a.target)||(B(a),a.pointerType==="mouse"&&a.preventDefault());},u=a=>{if(M(a.target))return;B(a),a.preventDefault();let d=document.elementFromPoint(a.clientX,a.clientY),g=_(d,o);g&&t.onSelect({element:g,descriptor:Q(g)});},b=a=>{a.key==="Escape"&&(B(a),a.preventDefault(),t.onCancel());};return window.addEventListener("pointermove",c,{capture:true,passive:true}),window.addEventListener("pointerdown",k,{capture:true}),window.addEventListener("pointerup",E,{capture:true}),window.addEventListener("click",u,{capture:true}),window.addEventListener("keydown",b,{capture:true}),window.addEventListener("scroll",m,{capture:true,passive:true}),window.addEventListener("resize",m,{passive:true}),{stop(){window.removeEventListener("pointermove",c,{capture:true}),window.removeEventListener("pointerdown",k,{capture:true}),window.removeEventListener("pointerup",E,{capture:true}),window.removeEventListener("click",u,{capture:true}),window.removeEventListener("keydown",b,{capture:true}),window.removeEventListener("scroll",m,{capture:true}),window.removeEventListener("resize",m),c.cancel(),m.cancel(),t.onHover(null);}}}var Re=12e3;function re(e,t){return new Promise((n,r)=>{if(t?.aborted){r(new Error("Aborted"));return}let o=setTimeout(n,e),i=()=>{clearTimeout(o),r(new Error("Aborted"));};t?.addEventListener("abort",i,{once:true});})}async function Me(e){return e?typeof e=="function"?await e():e:{}}function Be(e){return e>=500&&e<=599}async function oe(e){let{payload:t,webhook:n,signal:r}=e,o=Math.max(1,n.retry?.attempts??2),i=Math.max(0,n.retry?.backoffMs??500);for(let s=1;s<=o;s+=1){let l=n.timeoutMs??Re,c=new AbortController,m=setTimeout(()=>c.abort(),l),k=()=>c.abort();r&&r.addEventListener("abort",k,{once:true});try{let u={method:"POST",headers:{"content-type":"application/json",...await Me(n.headers)},body:JSON.stringify(t),signal:c.signal};n.credentials!==void 0&&(u.credentials=n.credentials);let b=await fetch(n.url,u);if(b.ok)return {ok:!0,status:b.status};if(s<o&&Be(b.status)){let a=.85+Math.random()*.3,d=Math.round(i*2**(s-1)*a);await re(d,r);continue}return {ok:!1,status:b.status,error:p("webhook_failed",new Error(`HTTP ${b.status}`))}}catch(E){if(c.signal.aborted||r?.aborted)return {ok:false,error:p("aborted",E)};if(s<o){let u=.85+Math.random()*.3,b=Math.round(i*2**(s-1)*u);await re(b,r);continue}return {ok:false,error:p("webhook_failed",E)}}finally{clearTimeout(m),r&&r.removeEventListener("abort",k);}}return {ok:false,error:p("webhook_failed",new Error("Failed"))}}function Ie(e,t){let n=e.callback?.ok,r=e.webhook?.ok;return n===void 0&&r===void 0?false:n===void 0?!!r:r===void 0?!!n:t?n&&r:n||r}async function ie(e){let{submit:t,signal:n}=e,r=t.beforeSubmit?await t.beforeSubmit(e.payload):e.payload,o={ok:false};if(t.onSubmit)try{await t.onSubmit(r),o.callback={ok:!0};}catch(i){o.callback={ok:false,error:p("callback_failed",i)};}if(t.webhook){let i={payload:r,webhook:t.webhook};o.webhook=await oe(n?{...i,signal:n}:i);}return o.ok=Ie(o,t.requireAll??false),{payload:r,result:o}}var Le=["[data-blocfeed-ui]","[data-blocfeed-ignore]"];function He(e){let t=[...Le,...e?.ignoreSelectors??[]],n=Array.from(new Set(t));return {...e,ignoreSelectors:n}}function ae(){return {phase:"idle"}}function ct(e){let t=e,n=ae(),r=new Set,o=new Set,i=null,s=null,l=null,c=null,m=0,k=()=>{for(let f of r)f(n);},E=f=>{for(let S of o)S(f);},u=f=>{n=f,k();},b=()=>{m+=1,c?.abort(),c=null;},a=()=>{i?.stop(),i=null,E(null),l!==null&&y()&&(document.documentElement.style.cursor=l,l=null);},d=()=>{b(),a(),s=null,u(ae());},g=()=>{if(!y())return;a(),s=null;let f=He(t.picker);l=document.documentElement.style.cursor,document.documentElement.style.cursor="crosshair",u({phase:"picking"}),i=ne(f,{onHover:E,onSelect:({element:S,descriptor:T})=>{s=S,a(),u({phase:"review",selection:T});},onCancel:()=>{d();}});};return {getState:()=>n,subscribe(f){return r.add(f),()=>r.delete(f)},subscribeHover(f){return o.add(f),()=>o.delete(f)},start(){n.phase==="capturing"||n.phase==="submitting"||n.phase!=="picking"&&g();},stop(){d();},clearSelection(){n.phase==="capturing"||n.phase==="submitting"||g();},setConfig(f){t=f;},async submit(f,S){if(!y()){let w=p("configuration",new Error("BlocFeed submit can only run in the browser"));return u({phase:"error",lastError:w}),{ok:false}}let T=t.submit;if(!T?.onSubmit&&!T?.webhook){let x={phase:"error",lastError:p("configuration",new Error("BlocFeed requires submit.onSubmit and/or submit.webhook"))};return n.selection&&(x.selection=n.selection),u(x),{ok:false}}if(n.phase==="capturing"||n.phase==="submitting")return {ok:false};let A=m+1;m=A,c?.abort(),c=new AbortController;let P=c.signal,h=n.selection,I=S?.capture?{...t.capture,...S.capture}:t.capture,q=!!(I?.element||I?.fullPage),U={phase:q?"capturing":"submitting"};h&&(U.selection=h),u(U);try{let w=q?await ee({selectionElement:s,capture:I,signal:P}):void 0;if(P.aborted||m!==A)return {ok:!1};let x={phase:"submitting"};h&&(x.selection=h),w&&(x.capture=w),u(x);let C={};h&&(C.selection=h),w&&(C.capture=w);let se=await te({config:t.metadata,context:C}),L={version:1,createdAt:new Date().toISOString(),message:f,metadata:se};h&&(L.selection=h),w&&(L.screenshots=w);let{result:v}=await ie({payload:L,submit:T,signal:P});if(P.aborted||m!==A)return v;if(v.ok){let N={phase:"success",lastSubmit:v};return h&&(N.selection=h),w&&(N.capture=w),u(N),v}let le=v.webhook?.error??v.callback?.error??p("unknown",new Error("Submission failed")),H={phase:"error",lastSubmit:v,lastError:le};return h&&(H.selection=h),w&&(H.capture=w),u(H),v}catch(w){if(P.aborted||m!==A)return {ok:false};let C={phase:"error",lastError:P.aborted?p("aborted",w):p("unknown",w)};return h&&(C.selection=h),u(C),{ok:false}}finally{m===A&&(c=null);}},__unsafeGetSelectedElement(){return s},destroy(){d(),r.clear(),o.clear();}}}export{y as a,O as b,G as c,ee as d,te as e,ct as f};
|
package/dist/chunk-52FWOTRN.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
'use strict';function y(){return typeof window<"u"&&typeof document<"u"}function W(e){let t=globalThis.CSS;return typeof t?.escape=="function"?t.escape(e):e.replace(/[^a-zA-Z0-9_-]/g,n=>{let r=n.codePointAt(0);return r===void 0?"":`\\${r.toString(16)} `})}function O(e){return {x:e.x,y:e.y,width:e.width,height:e.height}}function ce(e){return {x:e.x+window.scrollX,y:e.y+window.scrollY,width:e.width,height:e.height}}function ue(e){return e.replace(/\s+/g," ").trim()}function de(e,t=140){let n=e.textContent;if(!n)return;let r=ue(n);if(r)return r.length<=t?r:`${r.slice(0,t-1)}\u2026`}function fe(e){let t=1;for(let n=e.previousElementSibling;n;n=n.previousElementSibling)n.tagName===e.tagName&&(t+=1);return t}var Y=["data-testid","data-test-id","data-test","data-qa","data-cy"],j="data-blocfeed-component";function me(e){let t=e.closest(`[${j}]`);if(!t)return;let r=t.getAttribute(j)?.trim();return r||void 0}function pe(e){for(let t of Y){let n=e.closest(`[${t}]`);if(!n)continue;let o=n.getAttribute(t)?.trim();if(o)return o}}function ge(e){try{let t=e,n=Object.getOwnPropertyNames(t);for(let r of n)if(r.startsWith("__reactFiber$")||r.startsWith("__reactInternalInstance$")){let o=t[r];if(o&&typeof o=="object")return o}}catch{}return null}function R(e){if(e&&typeof e!="string"){if(typeof e=="function"){let t=e;return typeof t.displayName=="string"&&t.displayName?t.displayName:typeof t.name=="string"&&t.name?t.name:void 0}if(typeof e=="object"){let t=e,n=t.displayName;if(typeof n=="string"&&n)return n;let r=t.render;if(typeof r=="function"){let i=r;if(typeof i.displayName=="string"&&i.displayName)return i.displayName;if(typeof i.name=="string"&&i.name)return i.name}let o=t.type;return R(o)}}}function we(e){let t=ge(e);if(!t)return;let n=t._debugOwner;for(let o=0;n&&o<50;o+=1){let i=R(n.type)??R(n.elementType);if(i)return i;n=n._debugOwner;}let r=t;for(let o=0;r&&o<80;o+=1){let i=R(r.type)??R(r.elementType);if(i)return i;r=r.return;}}function be(e){let t=e.tagName.toLowerCase(),n=e.getAttribute("id");if(n)return `#${W(n)}`;for(let r of Y){let o=e.getAttribute(r);if(o)return `${t}[${r}="${W(o)}"]`}return `${t}:nth-of-type(${fe(e)})`}function he(e,t=10){let n=[],r=e;for(;r&&n.length<t;){let o=be(r);if(n.unshift(o),o.startsWith("#"))break;r=r.parentElement;}return n.join(" > ")}function z(e,t){if(!t||t.length===0)return false;for(let n of t)if(e.closest(n))return true;return false}function _(e,t){if(!e||z(e,t.ignoreSelectors))return null;let n=e;for(;n;){if(z(n,t.ignoreSelectors))return null;if(t.isSelectable?.(n)??ye(n))return n;n=n.parentElement;}return null}function ye(e){let t=e.tagName;return !(t==="HTML"||t==="BODY")}function Q(e){let t=e.getBoundingClientRect(),n={selector:he(e),tagName:e.tagName.toLowerCase(),rect:O(t),pageRect:ce(t)},r=e.getAttribute("id");r&&(n.id=r);let o=e.className;typeof o=="string"&&o.trim()&&(n.className=o);let i=de(e);i&&(n.textSnippet=i);let s=me(e)??we(e);s&&(n.componentName=s);let l=pe(e);return l&&(n.testId=l),n}var D=null;async function X(){return D||(D=import('html-to-image')),D}async function Ee(e){return await new Promise((t,n)=>{let r=new Image;r.onload=()=>t({width:r.naturalWidth,height:r.naturalHeight}),r.onerror=()=>n(new Error("Failed to load generated screenshot")),r.src=e;})}async function J(e,t){let{width:n,height:r}=await Ee(e);return {dataUrl:e,mime:t,width:n,height:r}}function F(e){if(e?.aborted)throw new Error("Aborted")}function G(){return {async captureElement(e,t){if(!y())throw new Error("captureElement can only run in the browser");F(t.signal);let n=await X();F(t.signal);let r=t.mime==="image/jpeg"?await n.toJpeg(e,{quality:t.quality??.92,pixelRatio:t.pixelRatio}):await n.toPng(e,{pixelRatio:t.pixelRatio});return F(t.signal),await J(r,t.mime)},async captureFullPage(e){if(!y())throw new Error("captureFullPage can only run in the browser");F(e.signal);let t=document.documentElement,n=Math.max(t.scrollWidth,t.clientWidth),r=Math.max(t.scrollHeight,t.clientHeight),o=Math.min(1,e.maxDimension/Math.max(n,r)),i=Math.max(1,Math.round(n*o)),s=Math.max(1,Math.round(r*o)),l=await X();F(e.signal);let c=e.mime==="image/jpeg"?await l.toJpeg(t,{width:i,height:s,quality:e.quality??.92,pixelRatio:e.pixelRatio}):await l.toPng(t,{width:i,height:s,pixelRatio:e.pixelRatio});return F(e.signal),await J(c,e.mime)}}}function ke(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return "Unknown error"}}function p(e,t,n){let r={kind:e,message:ke(t)};return n&&(r.detail=n),r}var Se=12e3,ve=2048,xe=.92;function K(){return Date.now()}function Pe(e){return new Promise((t,n)=>{let r=()=>n(new Error("Aborted"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:true});})}async function Z(e,t,n){let r=new Promise((i,s)=>{let l=setTimeout(()=>s(new Error("Timeout")),t);typeof l.unref=="function"&&l.unref();}),o=[e,r];return n&&o.push(Pe(n)),await Promise.race(o)}function Ce(e){if(!y())return 1;let t=window.devicePixelRatio||1,n=e?.pixelRatio??Math.min(t,2);return Math.max(.1,n)}function Fe(e){return !!(e?.element||e?.fullPage)}function V(e){let t={mime:e.mime,pixelRatio:e.pixelRatio,maxDimension:e.maxDimension};return e.includeQuality&&(t.quality=e.quality),e.signal&&(t.signal=e.signal),t}async function ee(e){let{selectionElement:t,capture:n,signal:r}=e;if(!y()||!Fe(n))return;let o=K(),i=[],s=n?.timeoutMs??Se,l=n?.maxDimension??ve,c=n?.mime??"image/png",m=n?.quality??xe,k=n?.adapter??G(),E={},u=Ce(n);if(n?.element&&t)try{let d=t.getBoundingClientRect(),g=Math.min(1,l/Math.max(d.width,d.height)),f=Math.min(u,u*g),S=await Z(Promise.resolve(k.captureElement(t,{...V({mime:c,quality:m,pixelRatio:f,maxDimension:l,includeQuality:c==="image/jpeg",...r?{signal:r}:{}})})),s,r);E.element=S;}catch(d){if(r?.aborted)throw d;i.push(p("capture_failed",d,{target:"element"}));}if(n?.fullPage)try{let d=await Z(Promise.resolve(k.captureFullPage(V({mime:c,quality:m,pixelRatio:u,maxDimension:l,includeQuality:c==="image/jpeg",...r?{signal:r}:{}}))),s,r);E.fullPage=d;}catch(d){if(r?.aborted)throw d;i.push(p("capture_failed",d,{target:"fullPage"}));}let b=K(),a={startedAt:o,finishedAt:b,durationMs:Math.max(0,b-o)};return i.length>0&&(a.errors=i),{...E,diagnostics:a}}function Te(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return}}function Ae(){return y()?{url:window.location.href,title:document.title,referrer:document.referrer||void 0,userAgent:navigator.userAgent,language:navigator.language,platform:navigator.platform,viewport:{width:window.innerWidth,height:window.innerHeight},screen:{width:window.screen.width,height:window.screen.height},scroll:{x:window.scrollX,y:window.scrollY},devicePixelRatio:window.devicePixelRatio||1,timezone:Te()}:{}}async function te(e){let{config:t,context:n}=e;if(t?.enabled===false)return {};let r=Ae(),o=t?.enrich;if(!o)return r;try{let i=await o(n);return {...r,...i}}catch(i){let s=p("unknown",i);return {...r,blocfeedMetadataError:s.message}}}function $(e){let t=null,n=null,r=(...o)=>{n=o,t===null&&(t=requestAnimationFrame(()=>{if(t=null,!n)return;let i=n;n=null,e(...i);}));};return r.cancel=()=>{t!==null&&cancelAnimationFrame(t),t=null,n=null;},r}function M(e){return e instanceof Element?!!e.closest("[data-blocfeed-ui]"):false}function B(e){e.stopPropagation(),e.stopImmediatePropagation?.();}function ne(e,t){if(!y())throw new Error("BlocFeed picker can only run in a browser environment.");let n=e.ignoreSelectors,r=e.isSelectable,o={};n&&n.length>0&&(o.ignoreSelectors=n),r&&(o.isSelectable=r);let i=null,s=null,l=(a,d=false)=>{if(!a){i=null,s=null,t.onHover(null);return}let g=O(a.getBoundingClientRect()),f=`${Math.round(g.x)}:${Math.round(g.y)}:${Math.round(g.width)}:${Math.round(g.height)}`;!d&&a===i&&f===s||(i=a,s=f,t.onHover({element:a,rect:g}));},c=$(a=>{if(M(a.target))return;let d=document.elementFromPoint(a.clientX,a.clientY),g=_(d,o);l(g);}),m=$(()=>{i&&l(i,true);}),k=a=>{M(a.target)||(B(a),a.pointerType==="mouse"&&a.preventDefault());},E=a=>{M(a.target)||(B(a),a.pointerType==="mouse"&&a.preventDefault());},u=a=>{if(M(a.target))return;B(a),a.preventDefault();let d=document.elementFromPoint(a.clientX,a.clientY),g=_(d,o);g&&t.onSelect({element:g,descriptor:Q(g)});},b=a=>{a.key==="Escape"&&(B(a),a.preventDefault(),t.onCancel());};return window.addEventListener("pointermove",c,{capture:true,passive:true}),window.addEventListener("pointerdown",k,{capture:true}),window.addEventListener("pointerup",E,{capture:true}),window.addEventListener("click",u,{capture:true}),window.addEventListener("keydown",b,{capture:true}),window.addEventListener("scroll",m,{capture:true,passive:true}),window.addEventListener("resize",m,{passive:true}),{stop(){window.removeEventListener("pointermove",c,{capture:true}),window.removeEventListener("pointerdown",k,{capture:true}),window.removeEventListener("pointerup",E,{capture:true}),window.removeEventListener("click",u,{capture:true}),window.removeEventListener("keydown",b,{capture:true}),window.removeEventListener("scroll",m,{capture:true}),window.removeEventListener("resize",m),c.cancel(),m.cancel(),t.onHover(null);}}}var Re=12e3;function re(e,t){return new Promise((n,r)=>{if(t?.aborted){r(new Error("Aborted"));return}let o=setTimeout(n,e),i=()=>{clearTimeout(o),r(new Error("Aborted"));};t?.addEventListener("abort",i,{once:true});})}async function Me(e){return e?typeof e=="function"?await e():e:{}}function Be(e){return e>=500&&e<=599}async function oe(e){let{payload:t,webhook:n,signal:r}=e,o=Math.max(1,n.retry?.attempts??2),i=Math.max(0,n.retry?.backoffMs??500);for(let s=1;s<=o;s+=1){let l=n.timeoutMs??Re,c=new AbortController,m=setTimeout(()=>c.abort(),l),k=()=>c.abort();r&&r.addEventListener("abort",k,{once:true});try{let u={method:"POST",headers:{"content-type":"application/json",...await Me(n.headers)},body:JSON.stringify(t),signal:c.signal};n.credentials!==void 0&&(u.credentials=n.credentials);let b=await fetch(n.url,u);if(b.ok)return {ok:!0,status:b.status};if(s<o&&Be(b.status)){let a=.85+Math.random()*.3,d=Math.round(i*2**(s-1)*a);await re(d,r);continue}return {ok:!1,status:b.status,error:p("webhook_failed",new Error(`HTTP ${b.status}`))}}catch(E){if(c.signal.aborted||r?.aborted)return {ok:false,error:p("aborted",E)};if(s<o){let u=.85+Math.random()*.3,b=Math.round(i*2**(s-1)*u);await re(b,r);continue}return {ok:false,error:p("webhook_failed",E)}}finally{clearTimeout(m),r&&r.removeEventListener("abort",k);}}return {ok:false,error:p("webhook_failed",new Error("Failed"))}}function Ie(e,t){let n=e.callback?.ok,r=e.webhook?.ok;return n===void 0&&r===void 0?false:n===void 0?!!r:r===void 0?!!n:t?n&&r:n||r}async function ie(e){let{submit:t,signal:n}=e,r=t.beforeSubmit?await t.beforeSubmit(e.payload):e.payload,o={ok:false};if(t.onSubmit)try{await t.onSubmit(r),o.callback={ok:!0};}catch(i){o.callback={ok:false,error:p("callback_failed",i)};}if(t.webhook){let i={payload:r,webhook:t.webhook};o.webhook=await oe(n?{...i,signal:n}:i);}return o.ok=Ie(o,t.requireAll??false),{payload:r,result:o}}var Le=["[data-blocfeed-ui]","[data-blocfeed-ignore]"];function He(e){let t=[...Le,...e?.ignoreSelectors??[]],n=Array.from(new Set(t));return {...e,ignoreSelectors:n}}function ae(){return {phase:"idle"}}function ct(e){let t=e,n=ae(),r=new Set,o=new Set,i=null,s=null,l=null,c=null,m=0,k=()=>{for(let f of r)f(n);},E=f=>{for(let S of o)S(f);},u=f=>{n=f,k();},b=()=>{m+=1,c?.abort(),c=null;},a=()=>{i?.stop(),i=null,E(null),l!==null&&y()&&(document.documentElement.style.cursor=l,l=null);},d=()=>{b(),a(),s=null,u(ae());},g=()=>{if(!y())return;a(),s=null;let f=He(t.picker);l=document.documentElement.style.cursor,document.documentElement.style.cursor="crosshair",u({phase:"picking"}),i=ne(f,{onHover:E,onSelect:({element:S,descriptor:T})=>{s=S,a(),u({phase:"review",selection:T});},onCancel:()=>{d();}});};return {getState:()=>n,subscribe(f){return r.add(f),()=>r.delete(f)},subscribeHover(f){return o.add(f),()=>o.delete(f)},start(){n.phase==="capturing"||n.phase==="submitting"||n.phase!=="picking"&&g();},stop(){d();},clearSelection(){n.phase==="capturing"||n.phase==="submitting"||g();},setConfig(f){t=f;},async submit(f,S){if(!y()){let w=p("configuration",new Error("BlocFeed submit can only run in the browser"));return u({phase:"error",lastError:w}),{ok:false}}let T=t.submit;if(!T?.onSubmit&&!T?.webhook){let x={phase:"error",lastError:p("configuration",new Error("BlocFeed requires submit.onSubmit and/or submit.webhook"))};return n.selection&&(x.selection=n.selection),u(x),{ok:false}}if(n.phase==="capturing"||n.phase==="submitting")return {ok:false};let A=m+1;m=A,c?.abort(),c=new AbortController;let P=c.signal,h=n.selection,I=S?.capture?{...t.capture,...S.capture}:t.capture,q=!!(I?.element||I?.fullPage),U={phase:q?"capturing":"submitting"};h&&(U.selection=h),u(U);try{let w=q?await ee({selectionElement:s,capture:I,signal:P}):void 0;if(P.aborted||m!==A)return {ok:!1};let x={phase:"submitting"};h&&(x.selection=h),w&&(x.capture=w),u(x);let C={};h&&(C.selection=h),w&&(C.capture=w);let se=await te({config:t.metadata,context:C}),L={version:1,createdAt:new Date().toISOString(),message:f,metadata:se};h&&(L.selection=h),w&&(L.screenshots=w);let{result:v}=await ie({payload:L,submit:T,signal:P});if(P.aborted||m!==A)return v;if(v.ok){let N={phase:"success",lastSubmit:v};return h&&(N.selection=h),w&&(N.capture=w),u(N),v}let le=v.webhook?.error??v.callback?.error??p("unknown",new Error("Submission failed")),H={phase:"error",lastSubmit:v,lastError:le};return h&&(H.selection=h),w&&(H.capture=w),u(H),v}catch(w){if(P.aborted||m!==A)return {ok:false};let C={phase:"error",lastError:P.aborted?p("aborted",w):p("unknown",w)};return h&&(C.selection=h),u(C),{ok:false}}finally{m===A&&(c=null);}},__unsafeGetSelectedElement(){return s},destroy(){d(),r.clear(),o.clear();}}}exports.a=y;exports.b=O;exports.c=G;exports.d=ee;exports.e=te;exports.f=ct;
|