blocfeed 0.1.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/dist/main.cjs ADDED
@@ -0,0 +1,223 @@
1
+ "use client";
2
+ 'use strict';var chunk52FWOTRN_cjs=require('./chunk-52FWOTRN.cjs'),react=require('react'),jsxRuntime=require('react/jsx-runtime'),reactDom=require('react-dom');var m=react.createContext(null);function w(t){let e=react.useMemo(()=>chunk52FWOTRN_cjs.f(t.config??{}),[]),[a,l]=react.useState(()=>e.getState());return react.useEffect(()=>e.subscribe(l),[e]),react.useEffect(()=>e.setConfig(t.config??{}),[e,t.config]),react.useEffect(()=>()=>e.destroy(),[e]),jsxRuntime.jsx(m.Provider,{value:{controller:e,state:a},children:t.children})}var T="blocfeed-styles-v1",_=`
3
+ :where([data-blocfeed-ui-root]),
4
+ :where([data-blocfeed-ui-root]) * {
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ :where([data-blocfeed-ui-root]) {
9
+ --bf-z: 2147483646;
10
+ --bf-font: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
11
+ --bf-panel-bg: rgba(17, 24, 39, 0.98);
12
+ --bf-panel-fg: rgb(243, 244, 246);
13
+ --bf-muted: rgba(243, 244, 246, 0.72);
14
+ --bf-border: rgba(255, 255, 255, 0.14);
15
+ --bf-accent: rgb(99, 102, 241);
16
+ --bf-danger: rgb(239, 68, 68);
17
+ --bf-shadow: 0 18px 55px rgba(0, 0, 0, 0.45);
18
+
19
+ font-family: var(--bf-font);
20
+ color: var(--bf-panel-fg);
21
+ }
22
+
23
+ :where([data-blocfeed-ui-root]) .bf-trigger {
24
+ position: fixed;
25
+ right: 18px;
26
+ bottom: 18px;
27
+ z-index: var(--bf-z);
28
+
29
+ display: inline-flex;
30
+ align-items: center;
31
+ gap: 10px;
32
+ padding: 10px 12px;
33
+ border-radius: 999px;
34
+ border: 1px solid var(--bf-border);
35
+ background: var(--bf-panel-bg);
36
+ color: var(--bf-panel-fg);
37
+ box-shadow: var(--bf-shadow);
38
+ cursor: pointer;
39
+ user-select: none;
40
+ -webkit-tap-highlight-color: transparent;
41
+ }
42
+
43
+ :where([data-blocfeed-ui-root]) .bf-trigger:focus-visible {
44
+ outline: 2px solid var(--bf-accent);
45
+ outline-offset: 2px;
46
+ }
47
+
48
+ :where([data-blocfeed-ui-root]) .bf-dot {
49
+ width: 10px;
50
+ height: 10px;
51
+ border-radius: 999px;
52
+ background: var(--bf-accent);
53
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.18);
54
+ }
55
+
56
+ :where([data-blocfeed-ui-root]) .bf-overlay {
57
+ position: fixed;
58
+ inset: 0;
59
+ z-index: var(--bf-z);
60
+ pointer-events: none;
61
+ }
62
+
63
+ :where([data-blocfeed-ui-root]) .bf-blocker {
64
+ position: fixed;
65
+ inset: 0;
66
+ background: rgba(0, 0, 0, 0.35);
67
+ pointer-events: auto;
68
+ }
69
+
70
+ :where([data-blocfeed-ui-root]) .bf-highlight {
71
+ position: fixed;
72
+ border: 2px solid var(--bf-accent);
73
+ border-radius: 10px;
74
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.42);
75
+ pointer-events: none;
76
+ }
77
+
78
+ :where([data-blocfeed-ui-root]) .bf-hint {
79
+ position: fixed;
80
+ top: 16px;
81
+ left: 50%;
82
+ transform: translateX(-50%);
83
+ max-width: min(640px, calc(100vw - 24px));
84
+ padding: 10px 12px;
85
+ border-radius: 12px;
86
+ border: 1px solid var(--bf-border);
87
+ background: var(--bf-panel-bg);
88
+ box-shadow: var(--bf-shadow);
89
+ pointer-events: auto;
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: space-between;
93
+ gap: 12px;
94
+ }
95
+
96
+ :where([data-blocfeed-ui-root]) .bf-hint p {
97
+ margin: 0;
98
+ font-size: 13px;
99
+ color: var(--bf-muted);
100
+ }
101
+
102
+ :where([data-blocfeed-ui-root]) .bf-btn {
103
+ border: 1px solid var(--bf-border);
104
+ background: transparent;
105
+ color: var(--bf-panel-fg);
106
+ padding: 8px 10px;
107
+ border-radius: 10px;
108
+ cursor: pointer;
109
+ user-select: none;
110
+ }
111
+
112
+ :where([data-blocfeed-ui-root]) .bf-btn[disabled] {
113
+ opacity: 0.6;
114
+ cursor: not-allowed;
115
+ }
116
+
117
+ :where([data-blocfeed-ui-root]) .bf-btnPrimary {
118
+ background: var(--bf-accent);
119
+ border-color: rgba(99, 102, 241, 0.6);
120
+ color: white;
121
+ }
122
+
123
+ :where([data-blocfeed-ui-root]) .bf-panel {
124
+ position: fixed;
125
+ width: 360px;
126
+ max-width: calc(100vw - 24px);
127
+ border-radius: 14px;
128
+ border: 1px solid var(--bf-border);
129
+ background: var(--bf-panel-bg);
130
+ box-shadow: var(--bf-shadow);
131
+ pointer-events: auto;
132
+ overflow: hidden;
133
+ }
134
+
135
+ :where([data-blocfeed-ui-root]) .bf-panelHeader {
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: space-between;
139
+ gap: 8px;
140
+ padding: 12px 12px 10px;
141
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
142
+ }
143
+
144
+ :where([data-blocfeed-ui-root]) .bf-title {
145
+ font-size: 13px;
146
+ letter-spacing: 0.3px;
147
+ text-transform: uppercase;
148
+ color: rgba(243, 244, 246, 0.85);
149
+ }
150
+
151
+ :where([data-blocfeed-ui-root]) .bf-panelBody {
152
+ padding: 12px;
153
+ display: grid;
154
+ gap: 10px;
155
+ }
156
+
157
+ :where([data-blocfeed-ui-root]) .bf-textarea {
158
+ width: 100%;
159
+ min-height: 96px;
160
+ resize: vertical;
161
+ padding: 10px 10px;
162
+ border-radius: 12px;
163
+ border: 1px solid rgba(255, 255, 255, 0.14);
164
+ background: rgba(0, 0, 0, 0.18);
165
+ color: var(--bf-panel-fg);
166
+ font-size: 14px;
167
+ line-height: 1.4;
168
+ }
169
+
170
+ :where([data-blocfeed-ui-root]) .bf-textarea:focus-visible {
171
+ outline: 2px solid rgba(99, 102, 241, 0.9);
172
+ outline-offset: 2px;
173
+ }
174
+
175
+ :where([data-blocfeed-ui-root]) .bf-row {
176
+ display: flex;
177
+ align-items: center;
178
+ justify-content: space-between;
179
+ gap: 10px;
180
+ }
181
+
182
+ :where([data-blocfeed-ui-root]) .bf-row label {
183
+ font-size: 13px;
184
+ color: var(--bf-muted);
185
+ display: inline-flex;
186
+ align-items: center;
187
+ gap: 8px;
188
+ user-select: none;
189
+ }
190
+
191
+ :where([data-blocfeed-ui-root]) .bf-actions {
192
+ display: flex;
193
+ gap: 10px;
194
+ justify-content: flex-end;
195
+ padding-top: 4px;
196
+ }
197
+
198
+ :where([data-blocfeed-ui-root]) .bf-status {
199
+ font-size: 12px;
200
+ color: var(--bf-muted);
201
+ }
202
+
203
+ :where([data-blocfeed-ui-root]) .bf-error {
204
+ font-size: 12px;
205
+ color: rgb(254, 202, 202);
206
+ }
207
+
208
+ :where([data-blocfeed-ui-root]) .bf-toast {
209
+ position: fixed;
210
+ left: 50%;
211
+ bottom: 18px;
212
+ transform: translateX(-50%);
213
+ z-index: var(--bf-z);
214
+ padding: 10px 12px;
215
+ border-radius: 12px;
216
+ border: 1px solid var(--bf-border);
217
+ background: var(--bf-panel-bg);
218
+ box-shadow: var(--bf-shadow);
219
+ pointer-events: none;
220
+ font-size: 13px;
221
+ color: rgba(243, 244, 246, 0.9);
222
+ }
223
+ `;function A(){if(!chunk52FWOTRN_cjs.a()||document.getElementById(T))return;let t=document.createElement("style");t.id=T,t.textContent=_,document.head.appendChild(t);}function y(){let t=react.useContext(m);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 G(t,e,a){return Math.max(e,Math.min(a,t))}function O(t,e){let l=window.innerWidth,r=window.innerHeight,d=G(t.x,12,Math.max(12,l-e-12)),f=t.y+t.height+12,g=Math.max(12,t.y-240);return {top:f+240<=r?f:g,left:d}}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:d,submit:f}=y(),[g,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),[I,N]=react.useState(null);react.useEffect(()=>a.subscribeHover(C),[a]),react.useEffect(()=>{let i=a.__unsafeGetSelectedElement();if(!i||e.phase==="idle"||e.phase==="picking"){N(null);return}let z=()=>{N(chunk52FWOTRN_cjs.b(i.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 i=window.setTimeout(()=>r(),1200);return ()=>window.clearTimeout(i)},[e.phase,r]);let c=e.phase==="capturing"||e.phase==="submitting",s=e.phase==="picking"?g?.rect??null:I??e.selection?.rect??null,x=react.useMemo(()=>s?O(s,360):null,[s?.x,s?.y,s?.width,s?.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()}),s&&jsxRuntime.jsx(V,{rect:s}),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:()=>d(),disabled:c,children:"Re-pick"}),jsxRuntime.jsx("button",{type:"button",className:"bf-btn",onClick:()=>r(),disabled:c,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:i=>F(i.target.value),disabled:c}),jsxRuntime.jsxs("div",{className:"bf-row",children:[jsxRuntime.jsxs("label",{children:[jsxRuntime.jsx("input",{type:"checkbox",checked:B,onChange:i=>k(i.target.checked),disabled:c}),"Screenshot element"]}),jsxRuntime.jsxs("label",{children:[jsxRuntime.jsx("input",{type:"checkbox",checked:S,onChange:i=>P(i.target.checked),disabled:c}),"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:c,children:"Cancel"}),jsxRuntime.jsx("button",{type:"button",className:"bf-btn bf-btnPrimary",onClick:()=>f(h,{capture:{element:B,fullPage:S}}),disabled:c||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??{},[a,l]=react.useState(null);return react.useEffect(()=>{A();let r=document.createElement("div");r.setAttribute("data-blocfeed-ui-root","true"),r.setAttribute("data-blocfeed-ui","true");let d=e.ui?.zIndex;return typeof d=="number"&&r.style.setProperty("--bf-z",String(d)),document.body.appendChild(r),l(r),()=>{r.remove(),l(null);}},[e.ui?.zIndex]),a?reactDom.createPortal(jsxRuntime.jsx(w,{config:e,children:jsxRuntime.jsx(Y,{config:e})}),a):null}exports.BlocFeedProvider=w;exports.BlocFeedWidget=q;exports.useBlocFeed=y;
@@ -0,0 +1,30 @@
1
+ import { B as BlocFeedConfig, a as BlocFeedState, b as BlocFeedController, C as CaptureConfig, S as SubmitResult } from './controller-5O3OHUFm.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, l as SubmitConfig, T as TransportResult, W as WebhookConfig } from './controller-5O3OHUFm.cjs';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import * as react from 'react';
5
+ import { ReactNode } from 'react';
6
+
7
+ type BlocFeedProviderProps = {
8
+ config?: BlocFeedConfig;
9
+ children: ReactNode;
10
+ };
11
+ declare function BlocFeedProvider(props: BlocFeedProviderProps): react_jsx_runtime.JSX.Element;
12
+
13
+ type BlocFeedWidgetProps = {
14
+ config?: BlocFeedConfig;
15
+ };
16
+ declare function BlocFeedWidget(props: BlocFeedWidgetProps): react.ReactPortal | null;
17
+
18
+ type BlocFeedApi = {
19
+ state: BlocFeedState;
20
+ controller: BlocFeedController;
21
+ start: () => void;
22
+ stop: () => void;
23
+ clearSelection: () => void;
24
+ submit: (message: string, options?: {
25
+ capture?: CaptureConfig;
26
+ }) => Promise<SubmitResult>;
27
+ };
28
+ declare function useBlocFeed(): BlocFeedApi;
29
+
30
+ export { type BlocFeedApi, BlocFeedConfig, BlocFeedProvider, BlocFeedState, BlocFeedWidget, CaptureConfig, SubmitResult, useBlocFeed };
package/dist/main.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { B as BlocFeedConfig, a as BlocFeedState, b as BlocFeedController, C as CaptureConfig, S as SubmitResult } from './controller-5O3OHUFm.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, l as SubmitConfig, T as TransportResult, W as WebhookConfig } from './controller-5O3OHUFm.js';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import * as react from 'react';
5
+ import { ReactNode } from 'react';
6
+
7
+ type BlocFeedProviderProps = {
8
+ config?: BlocFeedConfig;
9
+ children: ReactNode;
10
+ };
11
+ declare function BlocFeedProvider(props: BlocFeedProviderProps): react_jsx_runtime.JSX.Element;
12
+
13
+ type BlocFeedWidgetProps = {
14
+ config?: BlocFeedConfig;
15
+ };
16
+ declare function BlocFeedWidget(props: BlocFeedWidgetProps): react.ReactPortal | null;
17
+
18
+ type BlocFeedApi = {
19
+ state: BlocFeedState;
20
+ controller: BlocFeedController;
21
+ start: () => void;
22
+ stop: () => void;
23
+ clearSelection: () => void;
24
+ submit: (message: string, options?: {
25
+ capture?: CaptureConfig;
26
+ }) => Promise<SubmitResult>;
27
+ };
28
+ declare function useBlocFeed(): BlocFeedApi;
29
+
30
+ export { type BlocFeedApi, BlocFeedConfig, BlocFeedProvider, BlocFeedState, BlocFeedWidget, CaptureConfig, SubmitResult, useBlocFeed };
package/dist/main.js ADDED
@@ -0,0 +1,223 @@
1
+ "use client";
2
+ import {f,a,b}from'./chunk-4EGJPVEG.js';import {createContext,useMemo,useState,useEffect,useContext}from'react';import {jsx,jsxs,Fragment}from'react/jsx-runtime';import {createPortal}from'react-dom';var m=createContext(null);function w(t){let e=useMemo(()=>f(t.config??{}),[]),[a,l]=useState(()=>e.getState());return useEffect(()=>e.subscribe(l),[e]),useEffect(()=>e.setConfig(t.config??{}),[e,t.config]),useEffect(()=>()=>e.destroy(),[e]),jsx(m.Provider,{value:{controller:e,state:a},children:t.children})}var T="blocfeed-styles-v1",_=`
3
+ :where([data-blocfeed-ui-root]),
4
+ :where([data-blocfeed-ui-root]) * {
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ :where([data-blocfeed-ui-root]) {
9
+ --bf-z: 2147483646;
10
+ --bf-font: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
11
+ --bf-panel-bg: rgba(17, 24, 39, 0.98);
12
+ --bf-panel-fg: rgb(243, 244, 246);
13
+ --bf-muted: rgba(243, 244, 246, 0.72);
14
+ --bf-border: rgba(255, 255, 255, 0.14);
15
+ --bf-accent: rgb(99, 102, 241);
16
+ --bf-danger: rgb(239, 68, 68);
17
+ --bf-shadow: 0 18px 55px rgba(0, 0, 0, 0.45);
18
+
19
+ font-family: var(--bf-font);
20
+ color: var(--bf-panel-fg);
21
+ }
22
+
23
+ :where([data-blocfeed-ui-root]) .bf-trigger {
24
+ position: fixed;
25
+ right: 18px;
26
+ bottom: 18px;
27
+ z-index: var(--bf-z);
28
+
29
+ display: inline-flex;
30
+ align-items: center;
31
+ gap: 10px;
32
+ padding: 10px 12px;
33
+ border-radius: 999px;
34
+ border: 1px solid var(--bf-border);
35
+ background: var(--bf-panel-bg);
36
+ color: var(--bf-panel-fg);
37
+ box-shadow: var(--bf-shadow);
38
+ cursor: pointer;
39
+ user-select: none;
40
+ -webkit-tap-highlight-color: transparent;
41
+ }
42
+
43
+ :where([data-blocfeed-ui-root]) .bf-trigger:focus-visible {
44
+ outline: 2px solid var(--bf-accent);
45
+ outline-offset: 2px;
46
+ }
47
+
48
+ :where([data-blocfeed-ui-root]) .bf-dot {
49
+ width: 10px;
50
+ height: 10px;
51
+ border-radius: 999px;
52
+ background: var(--bf-accent);
53
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.18);
54
+ }
55
+
56
+ :where([data-blocfeed-ui-root]) .bf-overlay {
57
+ position: fixed;
58
+ inset: 0;
59
+ z-index: var(--bf-z);
60
+ pointer-events: none;
61
+ }
62
+
63
+ :where([data-blocfeed-ui-root]) .bf-blocker {
64
+ position: fixed;
65
+ inset: 0;
66
+ background: rgba(0, 0, 0, 0.35);
67
+ pointer-events: auto;
68
+ }
69
+
70
+ :where([data-blocfeed-ui-root]) .bf-highlight {
71
+ position: fixed;
72
+ border: 2px solid var(--bf-accent);
73
+ border-radius: 10px;
74
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.42);
75
+ pointer-events: none;
76
+ }
77
+
78
+ :where([data-blocfeed-ui-root]) .bf-hint {
79
+ position: fixed;
80
+ top: 16px;
81
+ left: 50%;
82
+ transform: translateX(-50%);
83
+ max-width: min(640px, calc(100vw - 24px));
84
+ padding: 10px 12px;
85
+ border-radius: 12px;
86
+ border: 1px solid var(--bf-border);
87
+ background: var(--bf-panel-bg);
88
+ box-shadow: var(--bf-shadow);
89
+ pointer-events: auto;
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: space-between;
93
+ gap: 12px;
94
+ }
95
+
96
+ :where([data-blocfeed-ui-root]) .bf-hint p {
97
+ margin: 0;
98
+ font-size: 13px;
99
+ color: var(--bf-muted);
100
+ }
101
+
102
+ :where([data-blocfeed-ui-root]) .bf-btn {
103
+ border: 1px solid var(--bf-border);
104
+ background: transparent;
105
+ color: var(--bf-panel-fg);
106
+ padding: 8px 10px;
107
+ border-radius: 10px;
108
+ cursor: pointer;
109
+ user-select: none;
110
+ }
111
+
112
+ :where([data-blocfeed-ui-root]) .bf-btn[disabled] {
113
+ opacity: 0.6;
114
+ cursor: not-allowed;
115
+ }
116
+
117
+ :where([data-blocfeed-ui-root]) .bf-btnPrimary {
118
+ background: var(--bf-accent);
119
+ border-color: rgba(99, 102, 241, 0.6);
120
+ color: white;
121
+ }
122
+
123
+ :where([data-blocfeed-ui-root]) .bf-panel {
124
+ position: fixed;
125
+ width: 360px;
126
+ max-width: calc(100vw - 24px);
127
+ border-radius: 14px;
128
+ border: 1px solid var(--bf-border);
129
+ background: var(--bf-panel-bg);
130
+ box-shadow: var(--bf-shadow);
131
+ pointer-events: auto;
132
+ overflow: hidden;
133
+ }
134
+
135
+ :where([data-blocfeed-ui-root]) .bf-panelHeader {
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: space-between;
139
+ gap: 8px;
140
+ padding: 12px 12px 10px;
141
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
142
+ }
143
+
144
+ :where([data-blocfeed-ui-root]) .bf-title {
145
+ font-size: 13px;
146
+ letter-spacing: 0.3px;
147
+ text-transform: uppercase;
148
+ color: rgba(243, 244, 246, 0.85);
149
+ }
150
+
151
+ :where([data-blocfeed-ui-root]) .bf-panelBody {
152
+ padding: 12px;
153
+ display: grid;
154
+ gap: 10px;
155
+ }
156
+
157
+ :where([data-blocfeed-ui-root]) .bf-textarea {
158
+ width: 100%;
159
+ min-height: 96px;
160
+ resize: vertical;
161
+ padding: 10px 10px;
162
+ border-radius: 12px;
163
+ border: 1px solid rgba(255, 255, 255, 0.14);
164
+ background: rgba(0, 0, 0, 0.18);
165
+ color: var(--bf-panel-fg);
166
+ font-size: 14px;
167
+ line-height: 1.4;
168
+ }
169
+
170
+ :where([data-blocfeed-ui-root]) .bf-textarea:focus-visible {
171
+ outline: 2px solid rgba(99, 102, 241, 0.9);
172
+ outline-offset: 2px;
173
+ }
174
+
175
+ :where([data-blocfeed-ui-root]) .bf-row {
176
+ display: flex;
177
+ align-items: center;
178
+ justify-content: space-between;
179
+ gap: 10px;
180
+ }
181
+
182
+ :where([data-blocfeed-ui-root]) .bf-row label {
183
+ font-size: 13px;
184
+ color: var(--bf-muted);
185
+ display: inline-flex;
186
+ align-items: center;
187
+ gap: 8px;
188
+ user-select: none;
189
+ }
190
+
191
+ :where([data-blocfeed-ui-root]) .bf-actions {
192
+ display: flex;
193
+ gap: 10px;
194
+ justify-content: flex-end;
195
+ padding-top: 4px;
196
+ }
197
+
198
+ :where([data-blocfeed-ui-root]) .bf-status {
199
+ font-size: 12px;
200
+ color: var(--bf-muted);
201
+ }
202
+
203
+ :where([data-blocfeed-ui-root]) .bf-error {
204
+ font-size: 12px;
205
+ color: rgb(254, 202, 202);
206
+ }
207
+
208
+ :where([data-blocfeed-ui-root]) .bf-toast {
209
+ position: fixed;
210
+ left: 50%;
211
+ bottom: 18px;
212
+ transform: translateX(-50%);
213
+ z-index: var(--bf-z);
214
+ padding: 10px 12px;
215
+ border-radius: 12px;
216
+ border: 1px solid var(--bf-border);
217
+ background: var(--bf-panel-bg);
218
+ box-shadow: var(--bf-shadow);
219
+ pointer-events: none;
220
+ font-size: 13px;
221
+ color: rgba(243, 244, 246, 0.9);
222
+ }
223
+ `;function A(){if(!a()||document.getElementById(T))return;let t=document.createElement("style");t.id=T,t.textContent=_,document.head.appendChild(t);}function y(){let t=useContext(m);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 G(t,e,a){return Math.max(e,Math.min(a,t))}function O(t,e){let l=window.innerWidth,r=window.innerHeight,d=G(t.x,12,Math.max(12,l-e-12)),f=t.y+t.height+12,g=Math.max(12,t.y-240);return {top:f+240<=r?f:g,left:d}}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:d,submit:f}=y(),[g,C]=useState(null),[h,F]=useState(""),[B,k]=useState(t.config.capture?.element??true),[S,P]=useState(t.config.capture?.fullPage??false),[I,N]=useState(null);useEffect(()=>a.subscribeHover(C),[a]),useEffect(()=>{let i=a.__unsafeGetSelectedElement();if(!i||e.phase==="idle"||e.phase==="picking"){N(null);return}let z=()=>{N(b(i.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 i=window.setTimeout(()=>r(),1200);return ()=>window.clearTimeout(i)},[e.phase,r]);let c=e.phase==="capturing"||e.phase==="submitting",s=e.phase==="picking"?g?.rect??null:I??e.selection?.rect??null,x=useMemo(()=>s?O(s,360):null,[s?.x,s?.y,s?.width,s?.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()}),s&&jsx(V,{rect:s}),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:()=>d(),disabled:c,children:"Re-pick"}),jsx("button",{type:"button",className:"bf-btn",onClick:()=>r(),disabled:c,children:"Close"})]})]}),jsxs("div",{className:"bf-panelBody",children:[jsx("textarea",{className:"bf-textarea",placeholder:"What\u2019s happening? What did you expect?",value:h,onChange:i=>F(i.target.value),disabled:c}),jsxs("div",{className:"bf-row",children:[jsxs("label",{children:[jsx("input",{type:"checkbox",checked:B,onChange:i=>k(i.target.checked),disabled:c}),"Screenshot element"]}),jsxs("label",{children:[jsx("input",{type:"checkbox",checked:S,onChange:i=>P(i.target.checked),disabled:c}),"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:c,children:"Cancel"}),jsx("button",{type:"button",className:"bf-btn bf-btnPrimary",onClick:()=>f(h,{capture:{element:B,fullPage:S}}),disabled:c||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??{},[a,l]=useState(null);return useEffect(()=>{A();let r=document.createElement("div");r.setAttribute("data-blocfeed-ui-root","true"),r.setAttribute("data-blocfeed-ui","true");let d=e.ui?.zIndex;return typeof d=="number"&&r.style.setProperty("--bf-z",String(d)),document.body.appendChild(r),l(r),()=>{r.remove(),l(null);}},[e.ui?.zIndex]),a?createPortal(jsx(w,{config:e,children:jsx(Y,{config:e})}),a):null}export{w as BlocFeedProvider,q as BlocFeedWidget,y as useBlocFeed};
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "blocfeed",
3
+ "version": "0.1.0",
4
+ "description": "Drop-in feedback + screenshot widget for React.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "keywords": [
9
+ "react",
10
+ "nextjs",
11
+ "feedback",
12
+ "widget",
13
+ "screenshot",
14
+ "bug-report",
15
+ "product-feedback",
16
+ "element-picker"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "files": [
22
+ "dist/**/*.js",
23
+ "dist/**/*.cjs",
24
+ "dist/**/*.d.ts",
25
+ "dist/**/*.d.cts",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "main": "./dist/main.cjs",
30
+ "module": "./dist/main.js",
31
+ "types": "./dist/main.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/main.d.ts",
35
+ "import": "./dist/main.js",
36
+ "require": "./dist/main.cjs"
37
+ },
38
+ "./engine": {
39
+ "types": "./dist/engine.d.ts",
40
+ "import": "./dist/engine.js",
41
+ "require": "./dist/engine.cjs"
42
+ }
43
+ },
44
+ "scripts": {
45
+ "build": "tsup",
46
+ "build:debug": "tsup --sourcemap",
47
+ "dev": "tsup --watch --sourcemap",
48
+ "playground:install": "npm --prefix playground install",
49
+ "playground:dev": "npm --prefix playground run dev",
50
+ "playground:build": "npm --prefix playground run build",
51
+ "typecheck": "tsc -p tsconfig.json --noEmit",
52
+ "test": "vitest run",
53
+ "prepack": "npm run build",
54
+ "prepublishOnly": "npm run typecheck && npm test"
55
+ },
56
+ "peerDependencies": {
57
+ "react": ">=17.0.0",
58
+ "react-dom": ">=17.0.0"
59
+ },
60
+ "dependencies": {
61
+ "html-to-image": "^1.11.11"
62
+ },
63
+ "devDependencies": {
64
+ "@types/react": "^18.2.55",
65
+ "@types/react-dom": "^18.2.19",
66
+ "jsdom": "^24.0.0",
67
+ "tsup": "^8.0.2",
68
+ "typescript": "^5.5.4",
69
+ "vitest": "^1.6.0"
70
+ }
71
+ }